Compare commits
48 Commits
capability
...
0.12.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd2082e8c5 | ||
|
|
8f188acd3c | ||
|
|
0303ef2fd9 | ||
|
|
cb21a970b6 | ||
|
|
203daeb4a2 | ||
|
|
60dde11311 | ||
|
|
8b930fc57c | ||
|
|
f810ed0c14 | ||
|
|
53222fd83c | ||
|
|
a1f2497381 | ||
|
|
c27172c32e | ||
|
|
6e9b65d8b1 | ||
|
|
6d301f9961 | ||
|
|
659fea7afc | ||
|
|
e0303058a3 | ||
|
|
65f121c451 | ||
|
|
821cc62c4c | ||
|
|
4ce327eb6a | ||
|
|
4beb1bb574 | ||
|
|
0a2a1a026b | ||
|
|
eb096d07e2 | ||
|
|
19e42c9c52 | ||
|
|
f4d86fccfd | ||
|
|
ad0576a752 | ||
|
|
35be05df20 | ||
|
|
dae6d114da | ||
|
|
ecce47fdcd | ||
|
|
e9e0001ef8 | ||
|
|
62f8bfac2f | ||
|
|
75c9b51bf2 | ||
|
|
ec98d8417f | ||
|
|
736590558b | ||
|
|
6e40edd3f8 | ||
|
|
5f11c8d98f | ||
|
|
08737698c2 | ||
|
|
04b3133500 | ||
|
|
0d15992d56 | ||
|
|
a14929a13c | ||
|
|
6a20d804dc | ||
|
|
ae75059cc1 | ||
|
|
39e650cf11 | ||
|
|
bc91237d7c | ||
|
|
9e5491fdbf | ||
|
|
986ba078a6 | ||
|
|
061afad967 | ||
|
|
3bc8da4cbf | ||
|
|
1b9be8fe2d | ||
|
|
3a594ac7fd |
@@ -1 +0,0 @@
|
|||||||
dist/
|
|
||||||
@@ -6,10 +6,10 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
|
es2022: true, // add globals and sets parserOptions.ecmaVersion to 2022
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
// Vue specific rules, eslint-plugin-vue
|
// Vue specific rules, eslint-plugin-vue
|
||||||
// Added by Vue CLI
|
|
||||||
'plugin:vue/essential',
|
'plugin:vue/essential',
|
||||||
|
|
||||||
// Extends eslint-config-airbnb
|
// Extends eslint-config-airbnb
|
||||||
@@ -17,42 +17,14 @@ module.exports = {
|
|||||||
|
|
||||||
// Extends @typescript-eslint/recommended
|
// Extends @typescript-eslint/recommended
|
||||||
// Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
// Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||||
// Added by Vue CLI
|
|
||||||
'@vue/typescript/recommended',
|
'@vue/typescript/recommended',
|
||||||
],
|
],
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 12, // ECMA 2021
|
|
||||||
/*
|
|
||||||
Having 'latest' leads to:
|
|
||||||
```
|
|
||||||
Parsing error: ecmaVersion must be a number. Received value of type string instead
|
|
||||||
```
|
|
||||||
For .js files in the project
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
rules: {
|
rules: {
|
||||||
...getOwnRules(),
|
...getOwnRules(),
|
||||||
...getTurnedOffBrokenRules(),
|
...getTurnedOffBrokenRules(),
|
||||||
...getOpinionatedRuleOverrides(),
|
...getOpinionatedRuleOverrides(),
|
||||||
...getTodoRules(),
|
...getTodoRules(),
|
||||||
},
|
},
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
'**/__tests__/*.{j,t}s?(x)',
|
|
||||||
'**/tests/unit/**/*.spec.{j,t}s?(x)',
|
|
||||||
],
|
|
||||||
env: {
|
|
||||||
mocha: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['**/tests/**/*.{j,t}s?(x)'],
|
|
||||||
rules: {
|
|
||||||
'no-console': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getOwnRules() {
|
function getOwnRules() {
|
||||||
11
.github/actions/npm-install-dependencies/action.yml
vendored
Normal file
11
.github/actions/npm-install-dependencies/action.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
inputs:
|
||||||
|
working-directory:
|
||||||
|
required: false
|
||||||
|
default: '.'
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Run `npm ci` with retries
|
||||||
|
shell: bash
|
||||||
|
run: npm run install-deps -- --ci --root-directory "${{ inputs.working-directory }}"
|
||||||
57
.github/workflows/checks.build.yaml
vendored
57
.github/workflows/checks.build.yaml
vendored
@@ -9,7 +9,13 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos, ubuntu, windows ]
|
os: [ macos, ubuntu, windows ]
|
||||||
mode: [ development, test, production ]
|
mode: [
|
||||||
|
# Vite mode: https://vitejs.dev/guide/env-and-mode.html
|
||||||
|
development, # Used by `dev` command
|
||||||
|
production, # Used by `build` command
|
||||||
|
# Vitest mode: https://vitest.dev/guide/cli.html
|
||||||
|
test, # Used by Vitest
|
||||||
|
]
|
||||||
fail-fast: false # Allows to see results from other combinations
|
fail-fast: false # Allows to see results from other combinations
|
||||||
runs-on: ${{ matrix.os }}-latest
|
runs-on: ${{ matrix.os }}-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -21,17 +27,23 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Build
|
name: Build web
|
||||||
run: npm run build -- --mode ${{ matrix.mode }}
|
run: npm run build -- --mode ${{ matrix.mode }}
|
||||||
|
-
|
||||||
|
name: Verify web build artifacts
|
||||||
|
run: npm run check:verify-build-artifacts -- --web
|
||||||
|
|
||||||
# A new job is used due to environments/modes different from Vue CLI, https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1626
|
|
||||||
build-desktop:
|
build-desktop:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos, ubuntu, windows ]
|
os: [ macos, ubuntu, windows ]
|
||||||
mode: [ development, production ] # "test" is not supported https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1627
|
mode: [
|
||||||
|
# electron-vite modes: https://electron-vite.org/guide/env-and-mode.html#global-env-variables
|
||||||
|
development, # Used by `dev` command
|
||||||
|
production, # Used by `build` and `preview` commands
|
||||||
|
]
|
||||||
fail-fast: false # Allows to see results from other combinations
|
fail-fast: false # Allows to see results from other combinations
|
||||||
runs-on: ${{ matrix.os }}-latest
|
runs-on: ${{ matrix.os }}-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -43,33 +55,16 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Install cross-env
|
name: Prebuild desktop
|
||||||
# Used to set NODE_ENV due to https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1626
|
run: npm run electron:prebuild -- --mode ${{ matrix.mode }}
|
||||||
run: npm install --global cross-env
|
|
||||||
-
|
-
|
||||||
name: Build
|
name: Verify unbundled desktop build artifacts
|
||||||
run: |-
|
run: npm run check:verify-build-artifacts -- --electron-unbundled
|
||||||
cross-env-shell NODE_ENV=${{ matrix.mode }}
|
|
||||||
npm run electron:build -- --publish never --mode ${{ matrix.mode }}
|
|
||||||
|
|
||||||
create-icons:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ macos, ubuntu, windows ]
|
|
||||||
fail-fast: false # Allows to see results from other combinations
|
|
||||||
runs-on: ${{ matrix.os }}-latest
|
|
||||||
steps:
|
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Build (bundle and package) desktop application
|
||||||
uses: actions/checkout@v2
|
run: npm run electron:build -- --publish never
|
||||||
-
|
-
|
||||||
name: Setup node
|
name: Verify bundled desktop build artifacts
|
||||||
uses: ./.github/actions/setup-node
|
run: npm run check:verify-build-artifacts -- --electron-bundled
|
||||||
-
|
|
||||||
name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
-
|
|
||||||
name: Create icons
|
|
||||||
run: npm run create-icons
|
|
||||||
|
|||||||
72
.github/workflows/checks.desktop-runtime-errors.yaml
vendored
Normal file
72
.github/workflows/checks.desktop-runtime-errors.yaml
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: checks.desktop-runtime-errors
|
||||||
|
# Verifies desktop builds for Electron applications across multiple OS platforms (macOS ,Ubuntu, and Windows).
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-check:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ macos, ubuntu, windows ]
|
||||||
|
fail-fast: false # Allows to see results from other combinations
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Setup node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Configure Ubuntu
|
||||||
|
if: matrix.os == 'ubuntu'
|
||||||
|
shell: bash
|
||||||
|
run: |-
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
# Configure AppImage dependencies
|
||||||
|
sudo apt install -y libfuse2
|
||||||
|
|
||||||
|
# Configure DBUS (fixes `Failed to connect to the bus: Could not parse server address: Unknown address type`)
|
||||||
|
if ! command -v 'dbus-launch' &> /dev/null; then
|
||||||
|
echo 'DBUS does not exist, installing...'
|
||||||
|
sudo apt install -y dbus-x11 # Gives both dbus and dbus-launch utility
|
||||||
|
fi
|
||||||
|
sudo systemctl start dbus
|
||||||
|
DBUS_LAUNCH_OUTPUT=$(dbus-launch)
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "${DBUS_LAUNCH_OUTPUT}" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo 'Error: dbus-launch command did not execute successfully. Exiting.' >&2
|
||||||
|
echo "${DBUS_LAUNCH_OUTPUT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure fake (virtual) display
|
||||||
|
sudo apt install -y xvfb
|
||||||
|
sudo Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
|
||||||
|
echo "DISPLAY=:99" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Install ImageMagick for screenshots
|
||||||
|
sudo apt install -y imagemagick
|
||||||
|
|
||||||
|
# Install xdotool and xprop (from x11-utils) for window title capturing
|
||||||
|
sudo apt install -y xdotool x11-utils
|
||||||
|
-
|
||||||
|
name: Test
|
||||||
|
shell: bash
|
||||||
|
run: |-
|
||||||
|
export SCREENSHOT=true
|
||||||
|
npm run check:desktop
|
||||||
|
-
|
||||||
|
name: Upload screenshot
|
||||||
|
if: always() # Run even if previous step fails
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: screenshot-${{ matrix.os }}
|
||||||
|
path: screenshot.png
|
||||||
22
.github/workflows/checks.external-urls.yaml
vendored
Normal file
22
.github/workflows/checks.external-urls.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: checks.external-urls
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * 0' # at 00:00 on every Sunday
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Setup node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Test
|
||||||
|
run: npm run check:external-urls
|
||||||
14
.github/workflows/checks.quality.yaml
vendored
14
.github/workflows/checks.quality.yaml
vendored
@@ -16,11 +16,15 @@ jobs:
|
|||||||
os: [ macos, ubuntu, windows ]
|
os: [ macos, ubuntu, windows ]
|
||||||
fail-fast: false # Still interested to see results from other combinations
|
fail-fast: false # Still interested to see results from other combinations
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
-
|
||||||
|
name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Setup node
|
-
|
||||||
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
- name: Install dependencies
|
-
|
||||||
run: npm ci
|
name: Install dependencies
|
||||||
- name: Lint
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Lint
|
||||||
run: ${{ matrix.lint-command }}
|
run: ${{ matrix.lint-command }}
|
||||||
|
|||||||
55
.github/workflows/checks.scripts.yaml
vendored
Normal file
55
.github/workflows/checks.scripts.yaml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: checks.scripts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
icons-build:
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ macos, ubuntu, windows ]
|
||||||
|
fail-fast: false # Still interested to see results from other combinations
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Setup node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Create icons
|
||||||
|
run: npm run icons:build
|
||||||
|
|
||||||
|
install-deps:
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
install-deps-before: [true, false]
|
||||||
|
install-command:
|
||||||
|
- npm run install-deps
|
||||||
|
- npm run install-deps -- --no-errors
|
||||||
|
- npm run install-deps -- --ci
|
||||||
|
- npm run install-deps -- --fresh --non-deterministic
|
||||||
|
- npm run install-deps -- --fresh
|
||||||
|
- npm run install-deps -- --non-deterministic
|
||||||
|
os: [ macos, ubuntu, windows ]
|
||||||
|
fail-fast: false # Still interested to see results from other combinations
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Setup node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
if: matrix.install-deps-before == true
|
||||||
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Run install-deps
|
||||||
|
run: ${{ matrix.install-command }}
|
||||||
2
.github/workflows/checks.security.yaml
vendored
2
.github/workflows/checks.security.yaml
vendored
@@ -19,4 +19,4 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: NPM audit
|
name: NPM audit
|
||||||
run: exit "$(npm audit)" # Since node 15.x, it does not fail with error if we don't explicitly exit
|
run: npm audit --omit=dev
|
||||||
|
|||||||
25
.github/workflows/release.desktop.yaml
vendored
25
.github/workflows/release.desktop.yaml
vendored
@@ -13,20 +13,29 @@ jobs:
|
|||||||
fail-fast: false # So publish runs for other OSes if one fails
|
fail-fast: false # So publish runs for other OSes if one fails
|
||||||
runs-on: ${{ matrix.os }}-latest
|
runs-on: ${{ matrix.os }}-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
-
|
||||||
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
ref: master # otherwise it defaults to the version tag missing bump commit
|
ref: master # otherwise it defaults to the version tag missing bump commit
|
||||||
fetch-depth: 0 # fetch all history
|
fetch-depth: 0 # fetch all history
|
||||||
- name: Checkout to bump commit
|
-
|
||||||
|
name: Checkout to bump commit
|
||||||
run: git checkout "$(git rev-list "${{ github.event.release.tag_name }}"..master | tail -1)"
|
run: git checkout "$(git rev-list "${{ github.event.release.tag_name }}"..master | tail -1)"
|
||||||
- name: Setup node
|
-
|
||||||
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
- name: Install dependencies
|
-
|
||||||
run: npm ci
|
name: Install dependencies
|
||||||
- name: Run unit tests
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Run unit tests
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
- name: Publish desktop app
|
-
|
||||||
run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github
|
name: Prebuild
|
||||||
|
run: npm run electron:prebuild
|
||||||
|
-
|
||||||
|
name: Build and publish
|
||||||
|
run: npm run electron:build -- --publish always
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
EP_GH_IGNORE_TIME: true # Otherwise publishing fails if GitHub release is more than 2 hours old https://github.com/electron-userland/electron-builder/issues/2074
|
EP_GH_IGNORE_TIME: true # Otherwise publishing fails if GitHub release is more than 2 hours old https://github.com/electron-userland/electron-builder/issues/2074
|
||||||
|
|||||||
17
.github/workflows/release.site.yaml
vendored
17
.github/workflows/release.site.yaml
vendored
@@ -84,8 +84,9 @@ jobs:
|
|||||||
uses: ./app/.github/actions/setup-node
|
uses: ./app/.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: "App: Install dependencies"
|
name: "App: Install dependencies"
|
||||||
run: npm ci
|
uses: ./app/.github/actions/npm-install-dependencies
|
||||||
working-directory: app
|
with:
|
||||||
|
working-directory: app
|
||||||
-
|
-
|
||||||
name: "App: Run unit tests"
|
name: "App: Run unit tests"
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
@@ -94,11 +95,21 @@ jobs:
|
|||||||
name: "App: Build"
|
name: "App: Build"
|
||||||
run: npm run build
|
run: npm run build
|
||||||
working-directory: app
|
working-directory: app
|
||||||
|
-
|
||||||
|
name: "App: Verify web build artifacts"
|
||||||
|
run: npm run check:verify-build-artifacts -- --web
|
||||||
|
working-directory: app
|
||||||
-
|
-
|
||||||
name: "App: Deploy to S3"
|
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.'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
bash "aws/scripts/deploy/deploy-to-s3.sh" \
|
bash "aws/scripts/deploy/deploy-to-s3.sh" \
|
||||||
--folder app/dist \
|
--folder "${web_output_dir}" \
|
||||||
--web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \
|
--web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \
|
||||||
--storage-class ONEZONE_IA \
|
--storage-class ONEZONE_IA \
|
||||||
--role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \
|
--role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \
|
||||||
|
|||||||
4
.github/workflows/tests.e2e.yaml
vendored
4
.github/workflows/tests.e2e.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Run e2e tests
|
name: Run e2e tests
|
||||||
run: npm run test:e2e -- --headless
|
run: npm run test:cy:run
|
||||||
|
|||||||
2
.github/workflows/tests.integration.yaml
vendored
2
.github/workflows/tests.integration.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Run integration tests
|
name: Run integration tests
|
||||||
run: npm run test:integration
|
run: npm run test:integration
|
||||||
|
|||||||
2
.github/workflows/tests.unit.yaml
vendored
2
.github/workflows/tests.unit.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Run unit tests
|
name: Run unit tests
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,10 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
dist/
|
/dist-*/
|
||||||
.vs
|
.vs
|
||||||
.vscode/**/*
|
.vscode/**/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
#Electron-builder output
|
|
||||||
/dist_electron
|
|
||||||
# Cypress
|
|
||||||
/tests/e2e/screenshots
|
|
||||||
/tests/e2e/videos
|
|
||||||
|
|||||||
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -11,8 +11,8 @@
|
|||||||
"dbaeumer.vscode-eslint", // Lints JavaScript/TypeScript.
|
"dbaeumer.vscode-eslint", // Lints JavaScript/TypeScript.
|
||||||
"pmneo.tsimporter", // Provides better auto-complete for TypeScripts imports.
|
"pmneo.tsimporter", // Provides better auto-complete for TypeScripts imports.
|
||||||
// Vue
|
// Vue
|
||||||
"jcbuisson.vue", // Highlights syntax.
|
"Vue.volar", // Official Vue extensions
|
||||||
"octref.vetur", // Adds Vetur, Vue tooling support.
|
"Vue.vscode-typescript-vue-plugin", // Official TypeScript Vue Plugin
|
||||||
// Scripting
|
// Scripting
|
||||||
"timonwong.shellcheck", // Lints bash files.
|
"timonwong.shellcheck", // Lints bash files.
|
||||||
"ms-vscode.powershell", // Lints PowerShell files.
|
"ms-vscode.powershell", // Lints PowerShell files.
|
||||||
|
|||||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,5 +1,52 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
* win: fix automatic updates revert #234 | [0873769](https://github.com/undergroundwires/privacy.sexy/commit/08737698c2283bdf535d1611a730031ebfc7c0df)
|
||||||
|
* Migrate unit/integration tests to Vitest with Vite | [5f11c8d](https://github.com/undergroundwires/privacy.sexy/commit/5f11c8d98f782dd7c77f27649a1685fb7bd06e13)
|
||||||
|
* Remove Vue ESLint plugin for Vite compatibility | [6e40edd](https://github.com/undergroundwires/privacy.sexy/commit/6e40edd3f8a063c1b7482c27d8368e14c2fbcfbf)
|
||||||
|
* Migrate web builds from Vue CLI to Vite | [7365905](https://github.com/undergroundwires/privacy.sexy/commit/736590558be51a09435bb87e78b6655e8533bc2e)
|
||||||
|
* Migrate Cypress (E2E) tests to Vite and TypeScript | [ec98d84](https://github.com/undergroundwires/privacy.sexy/commit/ec98d8417f779fa818ccdda6bb90f521e1738002)
|
||||||
|
* Migrate to `electron-vite` and `electron-builder` | [75c9b51](https://github.com/undergroundwires/privacy.sexy/commit/75c9b51bf2d1dc7269adfd7b5ed71acfb5031299)
|
||||||
|
* Fix searching/filtering bugs #235 | [62f8bfa](https://github.com/undergroundwires/privacy.sexy/commit/62f8bfac2f481c93598fe19a51594769f522d684)
|
||||||
|
* Improve desktop security by isolating Electron | [e9e0001](https://github.com/undergroundwires/privacy.sexy/commit/e9e0001ef845fa6935c59a4e20a89aac9e71756a)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.1...0.12.2)
|
||||||
|
|
||||||
|
## 0.12.1 (2023-08-17)
|
||||||
|
|
||||||
|
* Transition to eslint-config-airbnb-with-typescript | [ff84f56](https://github.com/undergroundwires/privacy.sexy/commit/ff84f5676e496dd7ec5b3599e34ec9627d181ea2)
|
||||||
|
* Improve user privacy with secure outbound links | [3a594ac](https://github.com/undergroundwires/privacy.sexy/commit/3a594ac7fd708dc1e98155ffb9b21acd4e1fcf2d)
|
||||||
|
* Refactor Vue components using Composition API #230 | [1b9be8f](https://github.com/undergroundwires/privacy.sexy/commit/1b9be8fe2d72d8fb5cf1fed6dcc0b9777171aa98)
|
||||||
|
* Fix failing security tests | [3bc8da4](https://github.com/undergroundwires/privacy.sexy/commit/3bc8da4cbf1e2bd758dc3fffe4b1e62dc3beb7b3)
|
||||||
|
* Improve Defender scripts #201 | [061afad](https://github.com/undergroundwires/privacy.sexy/commit/061afad9673a41454c2421c318898f2b4f4cf504)
|
||||||
|
* Fix failing tests due to failed error logging | [986ba07](https://github.com/undergroundwires/privacy.sexy/commit/986ba078a643de6acbee50fff9cf77494ca7ea7f)
|
||||||
|
* Implement custom lightweight modal #230 | [9e5491f](https://github.com/undergroundwires/privacy.sexy/commit/9e5491fdbf2d9d40d974f5ad0e879a6d5c6d1e55)
|
||||||
|
* Refactor usage of tooltips for flexibility | [bc91237](https://github.com/undergroundwires/privacy.sexy/commit/bc91237d7c54bdcd15c5c39a55def50d172bb659)
|
||||||
|
* Fix revert toggle partial rendering | [39e650c](https://github.com/undergroundwires/privacy.sexy/commit/39e650cf110bee6b1b21d9b2902b36b0e2568d54)
|
||||||
|
* Increase testability through dependency injection | [ae75059](https://github.com/undergroundwires/privacy.sexy/commit/ae75059cc14db41f55dd2056f528442c7d319dd2)
|
||||||
|
* Refactor filter (search query) event handling | [6a20d80](https://github.com/undergroundwires/privacy.sexy/commit/6a20d804dc365d22c1248d787f9912271f508eeb)
|
||||||
|
* Migrate to ES6 modules | [a14929a](https://github.com/undergroundwires/privacy.sexy/commit/a14929a13cc6260b514692d9b4f1cdf5fb85d8b2)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.0...0.12.1)
|
||||||
|
|
||||||
## 0.12.0 (2023-08-03)
|
## 0.12.0 (2023-08-03)
|
||||||
|
|
||||||
* Improve script/category name validation | [b210aad](https://github.com/undergroundwires/privacy.sexy/commit/b210aaddf26629179f77fe19f62f65d8a0ca2b87)
|
* Improve script/category name validation | [b210aad](https://github.com/undergroundwires/privacy.sexy/commit/b210aaddf26629179f77fe19f62f65d8a0ca2b87)
|
||||||
|
|||||||
141
LICENSE
141
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,17 +7,15 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
software for all its users.
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
|||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -72,7 +60,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
under version 3 of the GNU General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU General Public License from time to time. Such new versions will
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU General
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU General Public License, you may choose any version ever published
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
Copyright (C) <year> <name of author>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
|
|||||||
48
README.md
48
README.md
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
<!-- markdownlint-disable MD033 -->
|
<!-- markdownlint-disable MD033 -->
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://undergroundwires.dev/donate?project=privacy.sexy">
|
<a href="https://undergroundwires.dev/donate?project=privacy.sexy" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="donation badge"
|
alt="donation badge"
|
||||||
src="https://undergroundwires.dev/img/badges/donate/flat.svg"
|
src="https://undergroundwires.dev/img/badges/donate/flat.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md">
|
<a href="https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="contributions are welcome"
|
alt="contributions are welcome"
|
||||||
src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"
|
src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"
|
||||||
@@ -18,13 +18,13 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Code quality -->
|
<!-- Code quality -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript">
|
<a href="https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Language grade: JavaScript/TypeScript"
|
alt="Language grade: JavaScript/TypeScript"
|
||||||
src="https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18"
|
src="https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability">
|
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Maintainability"
|
alt="Maintainability"
|
||||||
src="https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability"
|
src="https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability"
|
||||||
@@ -32,19 +32,19 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Tests -->
|
<!-- Tests -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.unit.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.unit.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Unit tests status"
|
alt="Unit tests status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/unit-tests/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/unit-tests/badge.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.integration.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.integration.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Integration tests status"
|
alt="Integration tests status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/integration-tests/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/integration-tests/badge.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.e2e.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.e2e.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="E2E tests status"
|
alt="E2E tests status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg"
|
||||||
@@ -52,39 +52,57 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Checks -->
|
<!-- Checks -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Quality checks status"
|
alt="Quality checks status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Security checks status"
|
alt="Security checks status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/security-checks/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/security-checks/badge.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Build checks status"
|
alt="Build checks status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/build-checks/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/build-checks/badge.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.desktop-runtime-errors.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img
|
||||||
|
alt="Status of runtime error checks for the desktop application"
|
||||||
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.desktop-runtime-errors/badge.svg"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.scripts.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img
|
||||||
|
alt="Status of script checks"
|
||||||
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.scripts/badge.svg"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.external-urls.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img
|
||||||
|
alt="Status of external URL checks"
|
||||||
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.external-urls/badge.svg"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
<!-- Release -->
|
<!-- Release -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Git release status"
|
alt="Git release status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-git/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-git/badge.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.site.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.site.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Site release status"
|
alt="Site release status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-site/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-site/badge.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.desktop.yaml">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.desktop.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Desktop application release status"
|
alt="Desktop application release status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-desktop/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-desktop/badge.svg"
|
||||||
@@ -92,7 +110,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Others -->
|
<!-- Others -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/bump-everywhere">
|
<a href="https://github.com/undergroundwires/bump-everywhere" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
alt="Auto-versioned by bump-everywhere"
|
alt="Auto-versioned by bump-everywhere"
|
||||||
src="https://github.com/undergroundwires/bump-everywhere/blob/master/badge.svg?raw=true"
|
src="https://github.com/undergroundwires/bump-everywhere/blob/master/badge.svg?raw=true"
|
||||||
@@ -104,7 +122,7 @@
|
|||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
- 🌍️ **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**: Check [releases page](https://github.com/undergroundwires/privacy.sexy/releases), or download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-Setup-0.12.3.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-0.12.3.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-0.12.3.AppImage).
|
||||||
|
|
||||||
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
|
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import { defineConfig } from 'cypress'
|
import { defineConfig } from 'cypress';
|
||||||
|
import ViteConfig from './vite.config';
|
||||||
|
|
||||||
|
const CYPRESS_BASE_DIR = 'tests/e2e/';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
fixturesFolder: 'tests/e2e/fixtures',
|
fixturesFolder: `${CYPRESS_BASE_DIR}/fixtures`,
|
||||||
screenshotsFolder: 'tests/e2e/screenshots',
|
screenshotsFolder: `${CYPRESS_BASE_DIR}/screenshots`,
|
||||||
videosFolder: 'tests/e2e/videos',
|
videosFolder: `${CYPRESS_BASE_DIR}/videos`,
|
||||||
e2e: {
|
e2e: {
|
||||||
setupNodeEvents(on, config) {
|
baseUrl: `http://localhost:${ViteConfig.server.port}/`,
|
||||||
return require('./tests/e2e/plugins/index.js')(on, config)
|
specPattern: `${CYPRESS_BASE_DIR}/**/*.cy.{js,jsx,ts,tsx}`, // Default: cypress/e2e/**/*.cy.{js,jsx,ts,tsx}
|
||||||
},
|
supportFile: `${CYPRESS_BASE_DIR}/support/e2e.ts`,
|
||||||
specPattern: 'tests/e2e/specs/**/*.cy.{js,jsx,ts,tsx}',
|
|
||||||
supportFile: 'tests/e2e/support/index.js',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
5
dist-dirs.json
Normal file
5
dist-dirs.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"electronUnbundled": "dist-electron-unbundled",
|
||||||
|
"electronBundled": "dist-electron-bundled",
|
||||||
|
"web": "dist-web"
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ Application layer enables [data-driven programming](https://en.wikipedia.org/wik
|
|||||||
|
|
||||||
Application layer parses the application data to compile the domain object [`Application.ts`](./../src/domain/Application.ts).
|
Application layer parses the application data to compile the domain object [`Application.ts`](./../src/domain/Application.ts).
|
||||||
|
|
||||||
A webpack loader loads (or injects) application data ([collection yaml files](./../src/application/collections/)) into the application layer in compile time. Application layer ([`ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts)) parses and compiles this data in runtime.
|
The build tool loads (or injects) application data ([collection yaml files](./../src/application/collections/)) into the application layer in compile time. Application layer ([`ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts)) parses and compiles this data in runtime.
|
||||||
|
|
||||||
Application layer compiles templating syntax during parsing to create the end scripts. You can read more about templating syntax in [templating.md](./templating.md) and how application data uses them through functions in [collection-files.md | Function](./collection-files.md#function).
|
Application layer compiles templating syntax during parsing to create the end scripts. You can read more about templating syntax in [templating.md](./templating.md) and how application data uses them through functions in [collection-files.md | Function](./collection-files.md#function).
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,23 @@ Application is
|
|||||||
|
|
||||||
Application uses highly decoupled models & services in different DDD layers:
|
Application uses highly decoupled models & services in different DDD layers:
|
||||||
|
|
||||||
- presentation layer (see [presentation.md](./presentation.md)),
|
**Application layer** (see [application.md](./application.md)):
|
||||||
- application layer (see [application.md](./application.md)),
|
|
||||||
- and domain layer.
|
|
||||||
|
|
||||||
Application layer depends on and consumes domain layer. [Presentation layer](./presentation.md) consumes and depends on application layer along with domain layer. Application and presentation layers can communicate through domain model.
|
- Coordinates application activities and consumes the domain layer.
|
||||||
|
|
||||||
|
**Presentation layer** (see [presentation.md](./presentation.md)):
|
||||||
|
|
||||||
|
- Handles UI/UX, consumes both the application and domain layers.
|
||||||
|
- May communicate directly with the infrastructure layer for technical needs, but avoids domain logic.
|
||||||
|
|
||||||
|
**Domain layer**:
|
||||||
|
|
||||||
|
- Serves as the system's core and central truth.
|
||||||
|
- Facilitates communication between the application and presentation layers through the domain model.
|
||||||
|
|
||||||
|
**Infrastructure layer**:
|
||||||
|
|
||||||
|
- Manages technical implementations without dependencies on other layers or domain knowledge.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -27,6 +39,8 @@ Application layer depends on and consumes domain layer. [Presentation layer](./p
|
|||||||
|
|
||||||
State handling uses an event-driven subscription model to signal state changes and special functions to register changes. It does not depend on third party packages.
|
State handling uses an event-driven subscription model to signal state changes and special functions to register changes. It does not depend on third party packages.
|
||||||
|
|
||||||
|
The presentation layer can read and modify state through the context. State changes trigger events that components can subscribe to for reactivity.
|
||||||
|
|
||||||
Each layer treat application layer differently.
|
Each layer treat application layer differently.
|
||||||
|
|
||||||

|

|
||||||
@@ -45,7 +59,7 @@ Each layer treat application layer differently.
|
|||||||
- So state is mutable, and fires related events when mutated.
|
- So state is mutable, and fires related events when mutated.
|
||||||
- 📖 Read more: [application.md | Application state](./application.md#application-state).
|
- 📖 Read more: [application.md | Application state](./application.md#application-state).
|
||||||
|
|
||||||
It's comparable with flux ([`redux`](https://redux.js.org/)) or flux-like ([`vuex`](https://vuex.vuejs.org/)) patterns. Flux component "view" is [presentation layer](./presentation.md) in Vue. Flux functions "dispatcher", "store" and "action creation" functions lie in the [application layer](./application.md). A difference is that application state in privacy.sexy is mutable and lies in single flux "store" that holds app state and logic. The "actions" mutate the state directly which in turns act as dispatcher to notify its own event subscriptions (callbacks).
|
It's comparable with `flux`, `vuex`, and `pinia`. A difference is that mutable application layer state in privacy.sexy is mutable and lies in single "store" that holds app state and logic. The "actions" mutate the state directly which in turns act as dispatcher to notify its own event subscriptions (callbacks).
|
||||||
|
|
||||||
## AWS infrastructure
|
## AWS infrastructure
|
||||||
|
|
||||||
|
|||||||
@@ -5,22 +5,28 @@ Before your commit, a good practice is to:
|
|||||||
1. [Run unit tests](#testing)
|
1. [Run unit tests](#testing)
|
||||||
2. [Lint your code](#linting)
|
2. [Lint your code](#linting)
|
||||||
|
|
||||||
You could run other types of tests as well, but they may take longer time and overkill for your changes. Automated actions executes the tests for a pull request or change in the main branch. See [ci-cd.md](./ci-cd.md) for more information.
|
You could run other types of tests as well, but they may take longer time and overkill for your changes.
|
||||||
|
Automated actions are set up to execute these tests as necessary.
|
||||||
|
See [ci-cd.md](./ci-cd.md) for more information.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Install node >15.x.
|
- Install Node >16.x.
|
||||||
- Install dependencies using `npm install`.
|
- Install dependencies using `npm install` (or [`npm run install-deps`](#utility-scripts) for more options).
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
- Run unit tests: `npm run test:unit`
|
- Run unit tests: `npm run test:unit`
|
||||||
- Run integration tests: `npm run test:integration`
|
- Run integration tests: `npm run test:integration`
|
||||||
- Run e2e (end-to-end) tests
|
- Run end-to-end (e2e) tests:
|
||||||
- Interactive mode with GUI: `npm run test:e2e`
|
- `npm run test:cy:open`: Run tests interactively using the development server with hot-reloading.
|
||||||
- Headless mode without GUI: `npm run test:e2e -- --headless`
|
- `npm run test:cy:run`: Run tests on the production build in a headless mode.
|
||||||
|
- Run checks:
|
||||||
|
- `npm run check:desktop`: Run runtime checks for packaged desktop applications ([README.md](./../tests/checks/desktop-runtime-errors/check-desktop-runtime-errors/README.md)).
|
||||||
|
- You can set environment variables active its flags such as `BUILD=true SCREENSHOT=true npm run check:desktop`
|
||||||
|
- `npm run check:external-urls`: Test whether external URLs used in applications are alive.
|
||||||
|
|
||||||
📖 Read more about testing in [tests](./tests.md).
|
📖 Read more about testing in [tests](./tests.md).
|
||||||
|
|
||||||
@@ -35,11 +41,25 @@ You could run other types of tests as well, but they may take longer time and ov
|
|||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
- Run in local server: `npm run serve`
|
**Web:**
|
||||||
|
|
||||||
|
- Run in local server: `npm run dev`
|
||||||
- 💡 Meant for local development with features such as hot-reloading.
|
- 💡 Meant for local development with features such as hot-reloading.
|
||||||
- Run using Docker:
|
- Preview production build: `npm run preview`
|
||||||
1. Build: `docker build -t undergroundwires/privacy.sexy:latest .`
|
- Start a local web server that serves the built solution from `./dist`.
|
||||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest`
|
- 💡 Run `npm run build` before `npm run preview`.
|
||||||
|
|
||||||
|
**Desktop apps:**
|
||||||
|
|
||||||
|
- `npm run electron:dev`: The command will build the main process and preload scripts source code, and start a dev server for the renderer, and start the Electron app.
|
||||||
|
- `npm run electron:preview`: The command will build the main process, preload scripts and renderer source code, and start the Electron app to preview.
|
||||||
|
- `npm run electron:prebuild`: The command will build the main process, preload scripts and renderer source code. Usually before packaging the Electron application, you need to execute this command.
|
||||||
|
- `npm run electron:build`: Prebuilds the Electron application, packages and publishes it through `electron-builder`.
|
||||||
|
|
||||||
|
**Docker:**
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
@@ -47,13 +67,26 @@ You could run other types of tests as well, but they may take longer time and ov
|
|||||||
- Build desktop application: `npm run electron:build`
|
- Build desktop application: `npm run electron:build`
|
||||||
- (Re)create icons (see [documentation](../img/README.md)): `npm run create-icons`
|
- (Re)create icons (see [documentation](../img/README.md)): `npm run create-icons`
|
||||||
|
|
||||||
### Utility Scripts
|
### Scripts
|
||||||
|
|
||||||
- Run fresh NPM install: [`./scripts/fresh-npm-install.sh`](../scripts/fresh-npm-install.sh)
|
📖 For detailed options and behavior for any of the following scripts, please refer to the script file itself.
|
||||||
- This script provides a clean NPM install, removing existing node modules and optionally the package-lock.json (when run with -n), then installs dependencies and runs unit tests.
|
|
||||||
- Configure VSCode: [`./scripts/configure-vscode.sh`](../scripts/configure-vscode.sh)
|
#### Utility scripts
|
||||||
|
|
||||||
|
- [**`npm run install-deps [-- <options>]`**](../scripts/npm-install.js):
|
||||||
|
- Manages NPM dependency installation, it offers capabilities like doing a fresh install, retries on network errors, and other features.
|
||||||
|
- For example, you can run `npm run install-deps -- --fresh` to do clean installation of dependencies.
|
||||||
|
- [**`./scripts/configure-vscode.sh`**](../scripts/configure-vscode.sh):
|
||||||
- This script checks and sets the necessary configurations for VSCode in `settings.json` file.
|
- This script checks and sets the necessary configurations for VSCode in `settings.json` file.
|
||||||
|
|
||||||
|
#### Automation scripts
|
||||||
|
|
||||||
|
- [**`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.
|
||||||
|
|
||||||
## Recommended extensions
|
## Recommended extensions
|
||||||
|
|
||||||
You should use EditorConfig to follow project style.
|
You should use EditorConfig to follow project style.
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
# Presentation layer
|
# Presentation layer
|
||||||
|
|
||||||
Presentation layer consists of UI-related code. It uses Vue.js as JavaScript framework and includes Vue.js components. It also includes [Electron](https://www.electronjs.org/) to provide functionality to desktop application.
|
The presentation layer handles UI concerns using Vue as JavaScript framework and Electron to provide desktop functionality.
|
||||||
|
|
||||||
It's designed event-driven from bottom to top. It listens user events (from top) and state events (from bottom) to update state or the GUI.
|
It reflects the [application state](./application.md#application-state) and allows user interactions to modify it. Components manage their own local UI state.
|
||||||
|
|
||||||
|
The presentation layer uses an event-driven architecture for bidirectional reactivity between the application state and UI. State change events flow bottom-up to trigger UI updates, while user events flow top-down through components, some ultimately modifying the application state.
|
||||||
|
|
||||||
📖 Refer to [architecture.md (Layered Application)](./architecture.md#layered-application) to read more about the layered architecture.
|
📖 Refer to [architecture.md (Layered Application)](./architecture.md#layered-application) to read more about the layered architecture.
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
- [`/src/` **`presentation/`**](./../src/presentation/): Contains all presentation related code including Vue and Electron configurations
|
- [`/src/` **`presentation/`**](./../src/presentation/): Contains Vue and Electron code.
|
||||||
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue global objects including components and plugins.
|
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue components and plugins.
|
||||||
- [**`components/`**](./../src/presentation/components/): Contains all Vue components and their helper classes.
|
- [**`components/`**](./../src/presentation/components/): Contains Vue components and helpers.
|
||||||
- [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that other components share.
|
- [**`Shared/`**](./../src/presentation/components/Shared): Contains shared Vue components and helpers.
|
||||||
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets that webpack will process.
|
- [**`Hooks`**](../src/presentation/components/Shared/Hooks): Hooks used by components through [dependency injection](#dependency-injections).
|
||||||
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts
|
- [**`/public/`**](../src/presentation/public/): Contains static assets.
|
||||||
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles used throughout different components.
|
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets processed by Vite.
|
||||||
- [**`components/`**](./../src/presentation/assets/styles/components): Contains reusable styles coupled to a Vue/HTML component.
|
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
|
||||||
- [**`vendors-extensions/`**](./../src/presentation/assets/styles/third-party-extensions): Contains styles that override third-party components used.
|
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
|
||||||
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Primary Sass file, passes along all other styles, should be the single file used from other components.
|
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles coupled to Vue components.
|
||||||
- [**`main.ts`**](./../src/presentation/main.ts): Application entry point that mounts and starts Vue application.
|
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint.
|
||||||
- [**`electron/`**](./../src/presentation/electron/): Electron configuration for the desktop application.
|
- [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
|
||||||
- [**`main.ts`**](./../src/presentation/main.ts): Main process of Electron, started as first thing when app starts.
|
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
|
||||||
- [**`/public/`**](./../public/): Contains static assets that are directly copied and do not go through webpack.
|
- [`/main/` **`index.ts`**](./../src/presentation/main.ts): Main entry for Electron, managing application windows and lifecycle events.
|
||||||
- [**`/vue.config.js`**](./../vue.config.js): Global Vue CLI configurations loaded by `@vue/cli-service`.
|
- [`/preload/` **`index.ts`**](./../src/presentation/main.ts): Script executed before the renderer, securing Node.js features for renderer use.
|
||||||
- [**`/postcss.config.js`**](./../postcss.config.js): PostCSS configurations used by Vue CLI internally.
|
- [**`/vite.config.ts`**](./../vite.config.ts): Contains Vite configurations for building web application.
|
||||||
- [**`/babel.config.js`**](./../babel.config.js): Babel configurations for polyfills used by `@vue/cli-plugin-babel`.
|
- [**`/electron.vite.config.ts`**](./../electron.vite.config.ts): Contains Vite configurations for building desktop applications.
|
||||||
|
- [**`/postcss.config.cjs`**](./../postcss.config.cjs): Contains PostCSS configurations for Vite.
|
||||||
|
|
||||||
## Visual design best-practices
|
## Visual design best-practices
|
||||||
|
|
||||||
@@ -32,7 +35,7 @@ Add visual clues for clickable items. It should be as clear as possible that the
|
|||||||
|
|
||||||
## Application data
|
## Application data
|
||||||
|
|
||||||
Components (should) use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain to avoid [parsing and compiling](./application.md#parsing-and-compiling) the application again.
|
Components (should) use [`UseApplication`](./../src/presentation/components/Shared/Hooks/UseApplication.ts) to reach the application domain to avoid [parsing and compiling](./application.md#parsing-and-compiling) the application again.
|
||||||
|
|
||||||
[Application.ts](../src/domain/Application.ts) is an immutable domain model that represents application state. It includes:
|
[Application.ts](../src/domain/Application.ts) is an immutable domain model that represents application state. It includes:
|
||||||
|
|
||||||
@@ -43,34 +46,58 @@ You can read more about how application layer provides application data to he pr
|
|||||||
|
|
||||||
## Application state
|
## Application state
|
||||||
|
|
||||||
Inheritance of a Vue components marks whether it uses application state . Components that does not handle application state extends `Vue`. Stateful components mutate or/and react to state changes (such as user selection or search queries) in [ApplicationContext](./../src/application/Context/ApplicationContext.ts) extend [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts) class to access the context / state.
|
This project uses a singleton instance of the application state, making it available to all Vue components.
|
||||||
|
|
||||||
[`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts) functions include:
|
The decision to not use third-party state management libraries like [`vuex`](https://web.archive.org/web/20230801191617/https://vuex.vuejs.org/) or [`pinia`](https://web.archive.org/web/20230801191743/https://pinia.vuejs.org/) was made to promote code independence and enhance portability.
|
||||||
|
|
||||||
- Creating a singleton of the state and makes it available to presentation layer as single source of truth.
|
Stateful components can mutate and/or react to state changes (e.g., user selection, search queries) in the [ApplicationContext](./../src/application/Context/ApplicationContext.ts). Vue components import [`CollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) to access both the application context and the state.
|
||||||
- Providing virtual abstract `handleCollectionState` callback that it calls when
|
|
||||||
- the Vue loads the component,
|
|
||||||
- and also every time when state changes.
|
|
||||||
- Providing `events` member to make lifecycling of state subscriptions events easier because it ensures that components unsubscribe from listening to state events when
|
|
||||||
- the component is no longer used (destroyed),
|
|
||||||
- an if [ApplicationContext](./../src/application/Context/ApplicationContext.ts) changes the active [collection](./collection-files.md) to a different one.
|
|
||||||
|
|
||||||
📖 Refer to [architecture.md | Application State](./architecture.md#application-state) to get an overview of event handling and [application.md | Application State](./presentation.md#application-state) for deeper look into how the application layer manages state.
|
[`UseCollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) provides several functionalities including:
|
||||||
|
|
||||||
## Modals
|
- **Singleton State Instance**: It creates a singleton instance of the state, which is shared across the presentation layer. The singleton instance ensures that there's a single source of truth for the application's state.
|
||||||
|
- **State Change Callback and Lifecycle Management**: It offers a mechanism to register callbacks, which will be invoked when the state initializes or mutates. It ensures that components unsubscribe from state events when they are no longer in use or when [ApplicationContext](./../src/application/Context/ApplicationContext.ts) switches the active [collection](./collection-files.md).
|
||||||
|
- **State Access and Modification**: It provides functions to read and mutate for accessing and modifying the state, encapsulating the details of these operations.
|
||||||
|
- **Event Subscription Lifecycle Management**: Includes an `events` member that simplifies state subscription lifecycle events. This ensures that components unsubscribe from state events when they are no longer in use, or when [ApplicationContext](./../src/application/Context/ApplicationContext.ts) switches the active [collection](./collection-files.md).
|
||||||
|
|
||||||
[Dialog.vue](./../src/presentation/components/Shared/Dialog.vue) is a shared component that other components used to show modal windows.
|
📖 Refer to [architecture.md | Application State](./architecture.md#application-state) for an overview of event handling and [application.md | Application State](./presentation.md#application-state) for an in-depth understanding of state management in the application layer.
|
||||||
|
|
||||||
You can use it by wrapping the content inside of its `slot` and call `.show()` function on its reference. For example:
|
## Dependency injections
|
||||||
|
|
||||||
```html
|
The presentation layer uses Vue's native dependency injection system to increase testability and decouple components.
|
||||||
<Dialog ref="testDialog">
|
|
||||||
<div>Hello world</div>
|
|
||||||
</Dialog>
|
|
||||||
<div @click="$refs.testDialog.show()">Show dialog</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sass naming convention
|
To add a new dependency:
|
||||||
|
|
||||||
|
1. **Define its symbol**: Define an associated symbol for every dependency in [`injectionSymbols.ts`](./../src/presentation/injectionSymbols.ts). Symbols are grouped into:
|
||||||
|
- **Singletons**: Shared across components, instantiated once.
|
||||||
|
- **Transients**: Factories yielding a new instance on every access.
|
||||||
|
2. **Provide the dependency**: Modify the [`provideDependencies`](./../src/presentation/bootstrapping/DependencyProvider.ts) function to include the new dependency. [`App.vue`](./../src/presentation/components/App.vue) calls this function within its `setup()` hook to register the dependencies.
|
||||||
|
3. **Inject the dependency**: Use Vue's `inject` method alongside the defined symbol to incorporate the dependency into components.
|
||||||
|
- For singletons, invoke the factory method: `inject(symbolKey)()`.
|
||||||
|
- For transients, directly inject: `inject(symbolKey)`.
|
||||||
|
|
||||||
|
## Shared UI components
|
||||||
|
|
||||||
|
Shared UI components promote consistency and simplifies the creation of the front-end.
|
||||||
|
|
||||||
|
In order to maintain portability and easy maintainability, the preference is towards using homegrown components over third-party ones or comprehensive UI frameworks like Quasar.
|
||||||
|
|
||||||
|
Shared components include:
|
||||||
|
|
||||||
|
- [ModalDialog.vue](./../src/presentation/components/Shared/Modal/ModalDialog.vue) is utilized for rendering modal windows.
|
||||||
|
- [TooltipWrapper.vue](./../src/presentation/components/Shared/TooltipWrapper.vue) acts as a wrapper for rendering tooltips.
|
||||||
|
|
||||||
|
## Desktop builds
|
||||||
|
|
||||||
|
Desktop builds uses `electron-vite` to bundle the code, and `electron-builder` to build and publish the packages.
|
||||||
|
|
||||||
|
## Styles
|
||||||
|
|
||||||
|
### Style location
|
||||||
|
|
||||||
|
- **Global styles**: The [`assets/styles/`](#structure) directory is reserved for styles that have a broader scope, affecting multiple components or entire layouts. They are generic and should not be tightly coupled to a specific component's functionality.
|
||||||
|
- **Component-specific styles**: Styles closely tied to a particular component's functionality or appearance should reside near the component they are used by. This makes it easier to locate and modify styles when working on a specific component.
|
||||||
|
|
||||||
|
### Sass naming convention
|
||||||
|
|
||||||
- Use lowercase for variables/functions/mixins, e.g.:
|
- Use lowercase for variables/functions/mixins, e.g.:
|
||||||
- Variable: `$variable: value;`
|
- Variable: `$variable: value;`
|
||||||
|
|||||||
122
docs/tests.md
122
docs/tests.md
@@ -5,77 +5,79 @@ There are different types of tests executed:
|
|||||||
1. [Unit tests](#unit-tests)
|
1. [Unit tests](#unit-tests)
|
||||||
2. [Integration tests](#integration-tests)
|
2. [Integration tests](#integration-tests)
|
||||||
3. [End-to-end (E2E) tests](#e2e-tests)
|
3. [End-to-end (E2E) tests](#e2e-tests)
|
||||||
|
4. [Automated checks](#automated-checks)
|
||||||
|
|
||||||
Common aspects for all tests:
|
## Unit and integration tests
|
||||||
|
|
||||||
- They use [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/).
|
- They utilize [Vitest](https://vitest.dev/).
|
||||||
- Their files end with `.spec.{ts|js}` suffix.
|
- Test files are suffixed with `.spec.ts`.
|
||||||
|
|
||||||
💡 You can use path/module alias `@/tests` in import statements.
|
|
||||||
|
|
||||||
## Unit tests
|
|
||||||
|
|
||||||
- Unit tests test each component in isolation.
|
|
||||||
- All unit tests goes under [`./tests/unit`](./../tests/unit).
|
|
||||||
- They rely on [stubs](./../tests/unit/shared/Stubs) for isolation.
|
|
||||||
|
|
||||||
### Unit tests structure
|
|
||||||
|
|
||||||
- [`./src/`](./../src/)
|
|
||||||
- Includes source code that unit tests will test.
|
|
||||||
- [`./tests/unit/`](./../tests/unit/)
|
|
||||||
- Includes test code.
|
|
||||||
- Tests follow same folder structure as [`./src/`](./../src).
|
|
||||||
- E.g. if system under test lies in [`./src/application/ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts) then its tests would be in test would be at [`./tests/unit/application/ApplicationFactory.spec.ts`](./../tests/unit/application/ApplicationFactory.spec.ts).
|
|
||||||
- [`shared/`](./../tests/unit/shared/)
|
|
||||||
- Includes common functionality that's shared across unit tests.
|
|
||||||
- [`Assertions/`](./../tests/unit/shared/Assertions):
|
|
||||||
- Common assertions that extend [Chai Assertion Library](https://www.chaijs.com/).
|
|
||||||
- Asserting functions should start with `expect` prefix.
|
|
||||||
- [`TestCases/`](./../tests/unit/shared/TestCases/)
|
|
||||||
- Shared test cases.
|
|
||||||
- Functions that calls `it()` from [Mocha test framework](https://mochajs.org/) should have `it` prefix.
|
|
||||||
- E.g. `itEachAbsentCollectionValue()`.
|
|
||||||
- [`Stubs/`](./../tests/unit/shared/Stubs)
|
|
||||||
- Includes stubs to be able to test components in isolation.
|
|
||||||
- Stubs have minimal and dummy behavior to be functional, they may also have spying or mocking functions.
|
|
||||||
|
|
||||||
### Unit tests naming
|
|
||||||
|
|
||||||
- Each test suite first describe the system under test.
|
|
||||||
- E.g. tests for class `Application.ts` are all inside `Application.spec.ts`.
|
|
||||||
- `describe` blocks tests for same function (if applicable).
|
|
||||||
- E.g. test for `run()` are inside `describe('run', () => ..)`.
|
|
||||||
|
|
||||||
### Act, arrange, assert
|
### Act, arrange, assert
|
||||||
|
|
||||||
- Tests use act, arrange and assert (AAA) pattern when applicable.
|
- Tests implement the act, arrange, and assert (AAA) pattern.
|
||||||
- **Arrange**
|
- **Arrange**
|
||||||
- Sets up the test case.
|
- Sets up the test scenario and environment.
|
||||||
- Starts with comment line `// arrange`.
|
- Begins with comment line `// arrange`.
|
||||||
- **Act**
|
- **Act**
|
||||||
- Executes the actual test.
|
- Executes the actual test.
|
||||||
- Starts with comment line `// act`.
|
- Begins with comment line `// act`.
|
||||||
- **Assert**
|
- **Assert**
|
||||||
- Elicit some sort of expectation.
|
- Sets an expectation for the test's outcome.
|
||||||
- Starts with comment line `// assert`.
|
- Begins with comment line `// assert`.
|
||||||
|
|
||||||
## Integration tests
|
### Unit tests
|
||||||
|
|
||||||
- Tests functionality of a component in combination with others (not isolated).
|
- Evaluate individual components in isolation.
|
||||||
- Ensure dependencies to third parties work as expected.
|
- Located in [`./tests/unit`](./../tests/unit).
|
||||||
- Defined in [./tests/integration](./../tests/integration).
|
- Achieve isolation using [stubs](./../tests/unit/shared/Stubs).
|
||||||
|
- Include Vue component tests, enabled by `@vue/test-utils`.
|
||||||
|
|
||||||
|
#### Unit tests naming
|
||||||
|
|
||||||
|
- Test suites start with a description of the component or system under test.
|
||||||
|
- E.g., tests for `Application.ts` are contained in `Application.spec.ts`.
|
||||||
|
- Whenever possible, `describe` blocks group tests of the same function.
|
||||||
|
- E.g., tests for `run()` are inside `describe('run', () => ...)`.
|
||||||
|
|
||||||
|
### Integration tests
|
||||||
|
|
||||||
|
- Assess the combined functionality of components.
|
||||||
|
- They verify that third-party dependencies function as anticipated.
|
||||||
|
|
||||||
## E2E tests
|
## E2E tests
|
||||||
|
|
||||||
- Test the functionality and performance of a running application.
|
- Examine the live web application's functionality and performance.
|
||||||
- Vue CLI plugin [`e2e-cypress`](https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-plugin-e2e-cypress#readme) configures E2E tests.
|
- Uses Cypress to run the tests.
|
||||||
- Test names and folders have logical structure based on tests executed.
|
|
||||||
- The structure is following:
|
## Automated checks
|
||||||
- [`cypress.config.ts`](./../cypress.config.ts): Cypress configuration file.
|
|
||||||
- [`./tests/e2e/`](./../tests/e2e/): Base Cypress folder.
|
These checks validate various qualities like runtime execution, building process, security testing, etc.
|
||||||
- [`/specs/`](./../tests/e2e/specs/): Test files named with `.spec.js` extension.
|
|
||||||
- [`/plugins/index.js`](./../tests/e2e/plugins/index.js): Plugin file executed before loading project.
|
- Use [various tools](./../package.json) and [scripts](./../scripts).
|
||||||
- [`/support/index.js`](./../tests/e2e/support/index.js): Support file, runs before every single spec file.
|
- Are automatically executed as [GitHub workflows](./../.github/workflows).
|
||||||
- *(Ignored)* `/videos`: Asset folder for videos taken during tests.
|
|
||||||
- *(Ignored)* `/screenshots`: Asset folder for Screenshots taken during tests.
|
## Tests structure
|
||||||
|
|
||||||
|
- [`package.json`](./../package.json): Defines test commands and includes tools used in tests.
|
||||||
|
- [`vite.config.ts`](./../vite.config.ts): Configures `vitest` for unit and integration tests.
|
||||||
|
- [`./src/`](./../src/): Contains the code subject to testing.
|
||||||
|
- [`./tests/shared/`](./../tests/shared/): Contains code shared by different test categories.
|
||||||
|
- [`bootstrap/setup.ts`](./../tests/shared/bootstrap/setup.ts): Initializes unit and integration tests.
|
||||||
|
- [`./tests/unit/`](./../tests/unit/)
|
||||||
|
- Stores unit test code.
|
||||||
|
- The directory structure mirrors [`./src/`](./../src).
|
||||||
|
- E.g., tests for [`./src/application/ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts) reside in [`./tests/unit/application/ApplicationFactory.spec.ts`](./../tests/unit/application/ApplicationFactory.spec.ts).
|
||||||
|
- [`shared/`](./../tests/unit/shared/)
|
||||||
|
- Contains shared unit test functionalities.
|
||||||
|
- [`Assertions/`](./../tests/unit/shared/Assertions): Contains common assertion functions, prefixed with `expect`.
|
||||||
|
- [`TestCases/`](./../tests/unit/shared/TestCases/)
|
||||||
|
- Shared test cases.
|
||||||
|
- Functions that calls `it()` from [Vitest](https://vitest.dev/) should have `it` prefix.
|
||||||
|
- [`Stubs/`](./../tests/unit/shared/Stubs): Maintains stubs for component isolation, equipped with basic functionalities and, when necessary, spying or mocking capabilities.
|
||||||
|
- [`./tests/integration/`](./../tests/integration/): Contains integration test files.
|
||||||
|
- [`cypress.config.ts`](./../cypress.config.ts): Cypress (E2E tests) configuration file.
|
||||||
|
- [`./tests/e2e/`](./../tests/e2e/): Base Cypress folder, includes tests with `.cy.ts` extension.
|
||||||
|
- [`/support/e2e.ts`](./../tests/e2e/support/e2e.ts): Support file, runs before every single spec file.
|
||||||
|
- [`/tsconfig.json`]: TypeScript configuration for file Cypress code, improves IDE support, recommended to have by official documentation.
|
||||||
|
- *(git ignored)* `/videos`: Asset folder for videos taken during tests.
|
||||||
|
- *(git ignored)* `/screenshots`: Asset folder for Screenshots taken during tests.
|
||||||
|
|||||||
43
electron-builder.cjs
Normal file
43
electron-builder.cjs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/* eslint-disable no-template-curly-in-string */
|
||||||
|
|
||||||
|
const { join } = require('path');
|
||||||
|
const { electronBundled, electronUnbundled } = require('./dist-dirs.json');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// Common options
|
||||||
|
publish: {
|
||||||
|
provider: 'github',
|
||||||
|
vPrefixedTagName: false, // default: true
|
||||||
|
releaseType: 'release', // default: draft
|
||||||
|
},
|
||||||
|
directories: {
|
||||||
|
output: electronBundled,
|
||||||
|
},
|
||||||
|
extraMetadata: {
|
||||||
|
main: join(electronUnbundled, 'main/index.cjs'), // do not `path.resolve`, it expects a relative path
|
||||||
|
},
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
win: {
|
||||||
|
target: 'nsis',
|
||||||
|
},
|
||||||
|
nsis: {
|
||||||
|
artifactName: '${name}-Setup-${version}.${ext}',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Linux
|
||||||
|
linux: {
|
||||||
|
target: 'AppImage',
|
||||||
|
},
|
||||||
|
appImage: {
|
||||||
|
artifactName: '${name}-${version}.${ext}',
|
||||||
|
},
|
||||||
|
|
||||||
|
// macOS
|
||||||
|
mac: {
|
||||||
|
target: 'dmg',
|
||||||
|
},
|
||||||
|
dmg: {
|
||||||
|
artifactName: '${name}-${version}.${ext}',
|
||||||
|
},
|
||||||
|
};
|
||||||
69
electron.vite.config.ts
Normal file
69
electron.vite.config.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { mergeConfig, UserConfig } from 'vite';
|
||||||
|
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
|
||||||
|
import { getAliasesFromTsConfig, getClientEnvironmentVariables } from './vite-config-helper';
|
||||||
|
import { createVueConfig } from './vite.config';
|
||||||
|
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);
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
main: getSharedElectronConfig({
|
||||||
|
distDirSubfolder: 'main',
|
||||||
|
entryFilePath: MAIN_ENTRY_FILE,
|
||||||
|
}),
|
||||||
|
preload: getSharedElectronConfig({
|
||||||
|
distDirSubfolder: 'preload',
|
||||||
|
entryFilePath: PRELOAD_ENTRY_FILE,
|
||||||
|
}),
|
||||||
|
renderer: mergeConfig(
|
||||||
|
createVueConfig({
|
||||||
|
supportLegacyBrowsers: false,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
build: {
|
||||||
|
outDir: resolve(DIST_DIR, 'renderer'),
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
index: WEB_INDEX_HTML_PATH,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
function getSharedElectronConfig(options: {
|
||||||
|
readonly distDirSubfolder: string;
|
||||||
|
readonly entryFilePath: string;
|
||||||
|
}): UserConfig {
|
||||||
|
return {
|
||||||
|
build: {
|
||||||
|
outDir: resolve(DIST_DIR, options.distDirSubfolder),
|
||||||
|
lib: {
|
||||||
|
entry: options.entryFilePath,
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
entryFileNames: '[name].cjs', // This is needed so `type="module"` works
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [externalizeDepsPlugin()],
|
||||||
|
define: {
|
||||||
|
...getClientEnvironmentVariables(),
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
...getAliasesFromTsConfig(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePathFromProjectRoot(pathSegment: string) {
|
||||||
|
return resolve(__dirname, pathSegment);
|
||||||
|
}
|
||||||
@@ -4,9 +4,6 @@ This folder contains image files and other resources related to images.
|
|||||||
|
|
||||||
## logo.svg
|
## logo.svg
|
||||||
|
|
||||||
[logo.svg](./logo.svg) is the master logo from which all other icons or images are created from.
|
[`logo.svg`](./logo.svg) serves as the primary logo from which all other icons and images are derived.
|
||||||
It should be the only file that will be changed manually.
|
Only modify this file manually.
|
||||||
|
After making changes, execute `npm run build:icons` to regenerate logo files in various formats.
|
||||||
[`logo-update.mjs`](./logo-update.mjs) script in this folder updates all the logo files.
|
|
||||||
It should be executed everytime the logo is changed.
|
|
||||||
It automates recreation of logo files in different formats.
|
|
||||||
|
|||||||
26107
package-lock.json
generated
26107
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
81
package.json
81
package.json
@@ -1,30 +1,40 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.12.0",
|
"version": "0.12.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"slogan": "Now you have the choice",
|
"slogan": "Now you have the choice",
|
||||||
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
|
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
|
||||||
"author": "undergroundwires",
|
"author": "undergroundwires",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist-electron-unbundled/main/index.cjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"dev": "vite",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"test:unit": "vue-cli-service test:unit",
|
"preview": "vite preview",
|
||||||
"test:e2e": "vue-cli-service test:e2e",
|
"test:unit": "vitest run --dir tests/unit",
|
||||||
|
"test:integration": "vitest run --dir tests/integration",
|
||||||
|
"test:cy:run": "start-server-and-test \"vite build && vite preview --port 7070\" http://localhost:7070 \"cypress run --config baseUrl=http://localhost:7070\"",
|
||||||
|
"test:cy:open": "start-server-and-test \"vite --port 7070 --mode production\" http://localhost:7070 \"cypress open --config baseUrl=http://localhost:7070\"",
|
||||||
"lint": "npm run lint:md && npm run lint:md:consistency && npm run lint:md:relative-urls && npm run lint:eslint && npm run lint:yaml",
|
"lint": "npm run lint:md && npm run lint:md:consistency && npm run lint:md:relative-urls && npm run lint:eslint && npm run lint:yaml",
|
||||||
"create-icons": "node img/logo-update.mjs",
|
"install-deps": "node scripts/npm-install.js",
|
||||||
"electron:build": "vue-cli-service electron:build",
|
"icons:build": "node scripts/logo-update.js",
|
||||||
"electron:serve": "vue-cli-service electron:serve",
|
"check:desktop": "vitest run --dir tests/checks/desktop-runtime-errors --environment node",
|
||||||
"lint:eslint": "vue-cli-service lint --no-fix --mode production",
|
"check:external-urls": "vitest run --dir tests/checks/external-urls --environment node",
|
||||||
|
"check:verify-build-artifacts": "node scripts/verify-build-artifacts",
|
||||||
|
"electron:dev": "electron-vite dev",
|
||||||
|
"electron:preview": "electron-vite preview",
|
||||||
|
"electron:prebuild": "electron-vite build",
|
||||||
|
"electron:build": "electron-builder",
|
||||||
|
"lint:eslint": "eslint . --ignore-path .gitignore",
|
||||||
"lint:md": "markdownlint **/*.md --ignore node_modules",
|
"lint:md": "markdownlint **/*.md --ignore node_modules",
|
||||||
"lint:md:consistency": "remark . --frail --use remark-preset-lint-consistent",
|
"lint:md:consistency": "remark . --frail --use remark-preset-lint-consistent",
|
||||||
"lint:md:relative-urls": "remark . --frail --use remark-validate-links",
|
"lint:md:relative-urls": "remark . --frail --use remark-validate-links",
|
||||||
"lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml",
|
"lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"postuninstall": "electron-builder install-app-deps",
|
"postuninstall": "electron-builder install-app-deps"
|
||||||
"test:integration": "vue-cli-service test:unit \"tests/integration/**/*.spec.ts\""
|
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/vue": "^1.0.2",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||||
@@ -32,37 +42,26 @@
|
|||||||
"@fortawesome/vue-fontawesome": "^2.0.9",
|
"@fortawesome/vue-fontawesome": "^2.0.9",
|
||||||
"@juggle/resize-observer": "^3.4.0",
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
"ace-builds": "^1.23.4",
|
"ace-builds": "^1.23.4",
|
||||||
"core-js": "^3.32.0",
|
|
||||||
"cross-fetch": "^4.0.0",
|
"cross-fetch": "^4.0.0",
|
||||||
"electron-progressbar": "^2.1.0",
|
"electron-progressbar": "^2.1.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"install": "^0.13.0",
|
|
||||||
"liquor-tree": "^0.2.70",
|
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"npm": "^9.8.1",
|
"npm": "^9.8.1",
|
||||||
"v-tooltip": "2.1.3",
|
"vue": "^2.7.14"
|
||||||
"vue": "^2.7.14",
|
|
||||||
"vue-class-component": "^7.2.6",
|
|
||||||
"vue-js-modal": "^2.0.1",
|
|
||||||
"vue-property-decorator": "^9.1.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@modyfi/vite-plugin-yaml": "^1.0.4",
|
||||||
"@rushstack/eslint-patch": "^1.3.2",
|
"@rushstack/eslint-patch": "^1.3.2",
|
||||||
"@types/ace": "^0.0.48",
|
"@types/ace": "^0.0.48",
|
||||||
"@types/chai": "^4.3.5",
|
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.5",
|
||||||
"@types/mocha": "^10.0.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"@vue/cli-plugin-babel": "~5.0.8",
|
"@vitejs/plugin-legacy": "^4.1.1",
|
||||||
"@vue/cli-plugin-e2e-cypress": "~5.0.8",
|
"@vitejs/plugin-vue2": "^2.2.0",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-typescript": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-unit-mocha": "~5.0.8",
|
|
||||||
"@vue/cli-service": "~5.0.8",
|
|
||||||
"@vue/eslint-config-airbnb-with-typescript": "^7.0.0",
|
"@vue/eslint-config-airbnb-with-typescript": "^7.0.0",
|
||||||
"@vue/eslint-config-typescript": "^11.0.3",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"chai": "^4.3.7",
|
"@vue/test-utils": "^1.3.6",
|
||||||
|
"autoprefixer": "^10.4.15",
|
||||||
"cypress": "^12.17.2",
|
"cypress": "^12.17.2",
|
||||||
"electron": "^25.3.2",
|
"electron": "^25.3.2",
|
||||||
"electron-builder": "^24.6.3",
|
"electron-builder": "^24.6.3",
|
||||||
@@ -70,32 +69,32 @@
|
|||||||
"electron-icon-builder": "^2.0.1",
|
"electron-icon-builder": "^2.0.1",
|
||||||
"electron-log": "^4.4.8",
|
"electron-log": "^4.4.8",
|
||||||
"electron-updater": "^6.1.4",
|
"electron-updater": "^6.1.4",
|
||||||
|
"electron-vite": "^1.0.27",
|
||||||
"eslint": "^8.46.0",
|
"eslint": "^8.46.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-cypress": "^2.14.0",
|
||||||
"eslint-plugin-vue": "^9.6.0",
|
"eslint-plugin-vue": "^9.6.0",
|
||||||
"eslint-plugin-vuejs-accessibility": "^1.2.0",
|
"eslint-plugin-vuejs-accessibility": "^1.2.0",
|
||||||
"icon-gen": "^3.0.1",
|
"icon-gen": "^3.0.1",
|
||||||
"js-yaml-loader": "^1.2.2",
|
"jsdom": "^22.1.0",
|
||||||
"markdownlint-cli": "^0.35.0",
|
"markdownlint-cli": "^0.35.0",
|
||||||
|
"postcss": "^8.4.28",
|
||||||
"remark-cli": "^11.0.0",
|
"remark-cli": "^11.0.0",
|
||||||
"remark-lint-no-dead-urls": "^1.1.0",
|
"remark-lint-no-dead-urls": "^1.1.0",
|
||||||
"remark-preset-lint-consistent": "^5.1.2",
|
"remark-preset-lint-consistent": "^5.1.2",
|
||||||
"remark-validate-links": "^12.1.1",
|
"remark-validate-links": "^12.1.1",
|
||||||
"sass": "^1.64.1",
|
"sass": "^1.64.1",
|
||||||
"sass-loader": "^13.3.2",
|
"start-server-and-test": "^2.0.0",
|
||||||
"svgexport": "^0.4.2",
|
"svgexport": "^0.4.2",
|
||||||
"ts-loader": "^9.4.4",
|
"terser": "^5.19.2",
|
||||||
|
"tslib": "~2.4.0",
|
||||||
"typescript": "~4.6.2",
|
"typescript": "~4.6.2",
|
||||||
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4",
|
"vite": "^4.4.9",
|
||||||
"yaml-lint": "^1.7.0",
|
"vitest": "^0.34.2",
|
||||||
"tslib": "~2.4.0"
|
"vue-tsc": "^1.8.8",
|
||||||
},
|
"yaml-lint": "^1.7.0"
|
||||||
"overrides": {
|
|
||||||
"vue-cli-plugin-electron-builder": {
|
|
||||||
"electron-builder": "^24.6.3"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"//devDependencies": {
|
"//devDependencies": {
|
||||||
|
"terser": "Used by @vitejs/plugin-legacy for minification",
|
||||||
"typescript": [
|
"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 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"
|
"Cannot upgrade to > 4.6.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252"
|
||||||
|
|||||||
9
postcss.config.cjs
Normal file
9
postcss.config.cjs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const autoprefixer = require('autoprefixer');
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
autoprefixer(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Description:
|
|
||||||
# This script ensures npm is available, removes existing node modules, optionally
|
|
||||||
# removes package-lock.json (when -n flag is used), installs dependencies and runs unit tests.
|
|
||||||
# Usage:
|
|
||||||
# ./fresh-npm-install.sh # Regular execution
|
|
||||||
# ./fresh-npm-install.sh -n # Non-deterministic mode (removes package-lock.json)
|
|
||||||
|
|
||||||
declare NON_DETERMINISTIC_FLAG=0
|
|
||||||
|
|
||||||
|
|
||||||
main() {
|
|
||||||
parse_args "$@"
|
|
||||||
ensure_npm_is_available
|
|
||||||
ensure_npm_root
|
|
||||||
remove_existing_modules
|
|
||||||
if [[ $NON_DETERMINISTIC_FLAG -eq 1 ]]; then
|
|
||||||
remove_package_lock_json
|
|
||||||
fi
|
|
||||||
install_dependencies
|
|
||||||
run_unit_tests
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_npm_is_available() {
|
|
||||||
if ! command -v npm &> /dev/null; then
|
|
||||||
log::fatal 'npm could not be found, please install it first.'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_npm_root() {
|
|
||||||
if [ ! -f package.json ]; then
|
|
||||||
log::fatal 'Current directory is not a npm root. Please run the script in a npm root directory.'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_existing_modules() {
|
|
||||||
if [ -d ./node_modules ]; then
|
|
||||||
log::info 'Removing existing node modules...'
|
|
||||||
if ! rm -rf ./node_modules; then
|
|
||||||
log::fatal 'Could not remove existing node modules.'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
install_dependencies() {
|
|
||||||
log::info 'Installing dependencies...'
|
|
||||||
if ! npm install; then
|
|
||||||
log::fatal 'Failed to install dependencies.'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_package_lock_json() {
|
|
||||||
if [ -f ./package-lock.json ]; then
|
|
||||||
log::info 'Removing package-lock.json...'
|
|
||||||
if ! rm -rf ./package-lock.json; then
|
|
||||||
log::fatal 'Could not remove package-lock.json.'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
run_unit_tests() {
|
|
||||||
log::info 'Running unit tests...'
|
|
||||||
if ! npm run test:unit; then
|
|
||||||
pwd
|
|
||||||
log::fatal 'Failed to run unit tests.'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info() {
|
|
||||||
local -r message="$1"
|
|
||||||
echo "📣 ${message}"
|
|
||||||
}
|
|
||||||
|
|
||||||
log::fatal() {
|
|
||||||
local -r message="$1"
|
|
||||||
echo "❌ ${message}" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_args() {
|
|
||||||
while getopts "n" opt; do
|
|
||||||
case ${opt} in
|
|
||||||
n)
|
|
||||||
NON_DETERMINISTIC_FLAG=1
|
|
||||||
;;
|
|
||||||
\?)
|
|
||||||
echo "Invalid option: $OPTARG" 1>&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$1"
|
|
||||||
@@ -8,7 +8,7 @@ class Paths {
|
|||||||
constructor(selfDirectory) {
|
constructor(selfDirectory) {
|
||||||
const projectRoot = resolve(selfDirectory, '../');
|
const projectRoot = resolve(selfDirectory, '../');
|
||||||
this.sourceImage = join(projectRoot, 'img/logo.svg');
|
this.sourceImage = join(projectRoot, 'img/logo.svg');
|
||||||
this.publicDirectory = join(projectRoot, 'public');
|
this.publicDirectory = join(projectRoot, 'src/presentation/public');
|
||||||
this.electronBuildDirectory = join(projectRoot, 'build');
|
this.electronBuildDirectory = join(projectRoot, 'build');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ async function updateDesktopIcons(sourceImage, electronIconsDir) {
|
|||||||
await ensureFolderExists(electronIconsDir);
|
await ensureFolderExists(electronIconsDir);
|
||||||
const temporaryDir = await mkdtemp('icon-');
|
const temporaryDir = await mkdtemp('icon-');
|
||||||
const temporaryPngFile = join(temporaryDir, 'icon.png');
|
const temporaryPngFile = join(temporaryDir, 'icon.png');
|
||||||
console.log(`Converting from SVG (${sourceImage}) to PNG: ${temporaryPngFile}`); // required by icon-builder
|
console.log(`Converting from SVG (${sourceImage}) to PNG: ${temporaryPngFile}`); // required by `icon-builder`
|
||||||
await runCommand(
|
await runCommand(
|
||||||
'npx',
|
'npx',
|
||||||
'svgexport',
|
'svgexport',
|
||||||
199
scripts/npm-install.js
Normal file
199
scripts/npm-install.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
Description:
|
||||||
|
This script manages NPM dependencies for a project.
|
||||||
|
It offers capabilities like doing a fresh install, retries on network errors, and other features.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
npm run install-deps [-- <options>]
|
||||||
|
node scripts/npm-install.js [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--root-directory <path>
|
||||||
|
Specifies the root directory where package.json resides
|
||||||
|
Defaults to the current working directory.
|
||||||
|
Example: npm run install-deps -- --root-directory /your/path/here
|
||||||
|
|
||||||
|
--no-errors
|
||||||
|
Ignores errors and continues the execution.
|
||||||
|
Example: npm run install-deps -- --no-errors
|
||||||
|
|
||||||
|
--ci
|
||||||
|
Uses 'npm ci' for dependency installation instead of 'npm install'.
|
||||||
|
Example: npm run install-deps -- --ci
|
||||||
|
|
||||||
|
--fresh
|
||||||
|
Removes the existing node_modules directory before installing dependencies.
|
||||||
|
Example: npm run install-deps -- --fresh
|
||||||
|
|
||||||
|
--non-deterministic
|
||||||
|
Removes package-lock.json for a non-deterministic installation.
|
||||||
|
Example: npm run install-deps -- --non-deterministic
|
||||||
|
|
||||||
|
Note:
|
||||||
|
|
||||||
|
Flags can be combined as needed.
|
||||||
|
Example: npm run install-deps -- --fresh --non-deterministic
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { access, rm, unlink } from 'fs/promises';
|
||||||
|
import { constants } from 'fs';
|
||||||
|
|
||||||
|
const MAX_RETRIES = 5;
|
||||||
|
const RETRY_DELAY_IN_MS = 5 /* seconds */ * 1000;
|
||||||
|
const ARG_NAMES = {
|
||||||
|
rootDirectory: '--root-directory',
|
||||||
|
ignoreErrors: '--no-errors',
|
||||||
|
ci: '--ci',
|
||||||
|
fresh: '--fresh',
|
||||||
|
nonDeterministic: '--non-deterministic',
|
||||||
|
};
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const options = getOptions();
|
||||||
|
console.log('Options:', options);
|
||||||
|
await ensureNpmRootDirectory(options.rootDirectory);
|
||||||
|
await ensureNpmIsAvailable();
|
||||||
|
if (options.fresh) {
|
||||||
|
await removeNodeModules(options.rootDirectory);
|
||||||
|
}
|
||||||
|
if (options.nonDeterministic) {
|
||||||
|
await removePackageLockJson(options.rootDirectory);
|
||||||
|
}
|
||||||
|
const command = buildCommand(options.ci, options.outputErrors);
|
||||||
|
console.log('Starting dependency installation...');
|
||||||
|
const exitCode = await executeWithRetry(
|
||||||
|
command,
|
||||||
|
options.workingDirectory,
|
||||||
|
MAX_RETRIES,
|
||||||
|
RETRY_DELAY_IN_MS,
|
||||||
|
);
|
||||||
|
if (exitCode === 0) {
|
||||||
|
console.log('🎊 Installed dependencies...');
|
||||||
|
} else {
|
||||||
|
console.error(`💀 Failed to install dependencies, exit code: ${exitCode}`);
|
||||||
|
}
|
||||||
|
process.exit(exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeNodeModules(workingDirectory) {
|
||||||
|
const nodeModulesDirectory = resolve(workingDirectory, 'node_modules');
|
||||||
|
if (await exists('./node_modules')) {
|
||||||
|
console.log('Removing node_modules...');
|
||||||
|
await rm(nodeModulesDirectory, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removePackageLockJson(workingDirectory) {
|
||||||
|
const packageLockJsonFile = resolve(workingDirectory, 'package-lock.json');
|
||||||
|
if (await exists(packageLockJsonFile)) {
|
||||||
|
console.log('Removing package-lock.json...');
|
||||||
|
await unlink(packageLockJsonFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureNpmIsAvailable() {
|
||||||
|
const exitCode = await executeCommand('npm --version');
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error('`npm` in not available!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureNpmRootDirectory(workingDirectory) {
|
||||||
|
const packageJsonPath = resolve(workingDirectory, 'package.json');
|
||||||
|
if (!await exists(packageJsonPath)) {
|
||||||
|
throw new Error(`Not an NPM project root: ${workingDirectory}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCommand(ci, outputErrors) {
|
||||||
|
const baseCommand = ci ? 'npm ci' : 'npm install';
|
||||||
|
if (!outputErrors) {
|
||||||
|
return `${baseCommand} --loglevel=error`;
|
||||||
|
}
|
||||||
|
return baseCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions() {
|
||||||
|
const processArgs = process.argv.slice(2); // Slice off the node and script name
|
||||||
|
return {
|
||||||
|
rootDirectory: processArgs.includes('--root-directory') ? processArgs[processArgs.indexOf('--root-directory') + 1] : process.cwd(),
|
||||||
|
outputErrors: !processArgs.includes(ARG_NAMES.ignoreErrors),
|
||||||
|
ci: processArgs.includes(ARG_NAMES.ci),
|
||||||
|
fresh: processArgs.includes(ARG_NAMES.fresh),
|
||||||
|
nonDeterministic: processArgs.includes(ARG_NAMES.nonDeterministic),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeWithRetry(
|
||||||
|
command,
|
||||||
|
workingDirectory,
|
||||||
|
maxRetries,
|
||||||
|
retryDelayInMs,
|
||||||
|
currentAttempt = 1,
|
||||||
|
) {
|
||||||
|
const statusCode = await executeCommand(command, workingDirectory, true, true);
|
||||||
|
if (statusCode === 0 || currentAttempt >= maxRetries) {
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`⚠️🔄 Attempt ${currentAttempt} failed. Retrying in ${retryDelayInMs / 1000} seconds...`);
|
||||||
|
await sleep(retryDelayInMs);
|
||||||
|
|
||||||
|
const retryResult = await executeWithRetry(
|
||||||
|
command,
|
||||||
|
workingDirectory,
|
||||||
|
maxRetries,
|
||||||
|
retryDelayInMs,
|
||||||
|
currentAttempt + 1,
|
||||||
|
);
|
||||||
|
return retryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeCommand(
|
||||||
|
command,
|
||||||
|
workingDirectory = process.cwd(),
|
||||||
|
logStdout = false,
|
||||||
|
logCommand = false,
|
||||||
|
) {
|
||||||
|
if (logCommand) {
|
||||||
|
console.log(`▶️ Executing command "${command}" at "${workingDirectory}"`);
|
||||||
|
}
|
||||||
|
const process = exec(
|
||||||
|
command,
|
||||||
|
{
|
||||||
|
cwd: workingDirectory,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (logStdout) {
|
||||||
|
process.stdout.on('data', (data) => {
|
||||||
|
console.log(data.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.stderr.on('data', (data) => {
|
||||||
|
console.error(data.toString());
|
||||||
|
});
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
process.on('exit', (code) => {
|
||||||
|
resolve(code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(milliseconds) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, milliseconds);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exists(path) {
|
||||||
|
try {
|
||||||
|
await access(path, constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await main();
|
||||||
58
scripts/print-dist-dir.js
Normal file
58
scripts/print-dist-dir.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Description:
|
||||||
|
* This script determines the absolute path of a distribution directory based on CLI arguments
|
||||||
|
* and outputs its absolute path. It is designed to be run programmatically by other scripts.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/print-dist-dir.js [options]
|
||||||
|
*
|
||||||
|
* Options:
|
||||||
|
* --electron-unbundled Path for the unbundled Electron application
|
||||||
|
* --electron-bundled Path for the bundled Electron application
|
||||||
|
* --web Path for the web application
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { readFile } from 'fs/promises';
|
||||||
|
|
||||||
|
const DIST_DIRS_JSON_FILE_PATH = resolve(process.cwd(), 'dist-dirs.json'); // cannot statically import because ESLint does not support it https://github.com/eslint/eslint/discussions/15305
|
||||||
|
const CLI_ARGUMENTS = process.argv.slice(2);
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const distDirs = await readDistDirsJsonFile(DIST_DIRS_JSON_FILE_PATH);
|
||||||
|
const relativeDistDir = determineRelativeDistDir(distDirs, CLI_ARGUMENTS);
|
||||||
|
const absoluteDistDir = resolve(process.cwd(), relativeDistDir);
|
||||||
|
console.log(absoluteDistDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapCliFlagsToDistDirs(distDirs) {
|
||||||
|
return {
|
||||||
|
'--electron-unbundled': distDirs.electronUnbundled,
|
||||||
|
'--electron-bundled': distDirs.electronBundled,
|
||||||
|
'--web': distDirs.web,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function determineRelativeDistDir(distDirsJsonObject, cliArguments) {
|
||||||
|
const cliFlagDistDirMap = mapCliFlagsToDistDirs(distDirsJsonObject);
|
||||||
|
const availableCliFlags = Object.keys(cliFlagDistDirMap);
|
||||||
|
const requestedCliFlags = cliArguments.filter((arg) => {
|
||||||
|
return availableCliFlags.includes(arg);
|
||||||
|
});
|
||||||
|
if (!requestedCliFlags.length) {
|
||||||
|
throw new Error(`No distribution directory was requested. Please use one of these flags: ${availableCliFlags.join(', ')}`);
|
||||||
|
}
|
||||||
|
if (requestedCliFlags.length > 1) {
|
||||||
|
throw new Error(`Multiple distribution directories were requested, but this script only supports one: ${requestedCliFlags.join(', ')}`);
|
||||||
|
}
|
||||||
|
const selectedCliFlag = requestedCliFlags[0];
|
||||||
|
return cliFlagDistDirMap[selectedCliFlag];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readDistDirsJsonFile(absoluteConfigJsonFilePath) {
|
||||||
|
const fileContentAsText = await readFile(absoluteConfigJsonFilePath, 'utf8');
|
||||||
|
const parsedJsonData = JSON.parse(fileContentAsText);
|
||||||
|
return parsedJsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
await main();
|
||||||
133
scripts/verify-build-artifacts.js
Normal file
133
scripts/verify-build-artifacts.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Description:
|
||||||
|
* This script verifies the existence and content of build artifacts based on the
|
||||||
|
* provided CLI flags. It exists with exit code `0` if all verifications pass, otherwise
|
||||||
|
* with exit code `1`.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/verify-build-artifacts.js [options]
|
||||||
|
*
|
||||||
|
* Options:
|
||||||
|
* --electron-unbundled Verify artifacts for the unbundled Electron application.
|
||||||
|
* --electron-bundled Verify artifacts for the bundled Electron application.
|
||||||
|
* --web Verify artifacts for the web application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { access, readdir } from 'fs/promises';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
const PROCESS_ARGUMENTS = process.argv.slice(2);
|
||||||
|
const PRINT_DIST_DIR_SCRIPT_BASE_COMMAND = 'node scripts/print-dist-dir';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const buildConfigs = getBuildVerificationConfigs();
|
||||||
|
if (!anyCommandsFound(Object.keys(buildConfigs))) {
|
||||||
|
die(`No valid command found in process arguments. Expected one of: ${Object.keys(buildConfigs).join(', ')}`);
|
||||||
|
}
|
||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
for (const [command, config] of Object.entries(buildConfigs)) {
|
||||||
|
if (PROCESS_ARGUMENTS.includes(command)) {
|
||||||
|
const distDir = await executePrintDistDirScript(config.printDistDirScriptArgument);
|
||||||
|
await verifyDirectoryExists(distDir);
|
||||||
|
await verifyNonEmptyDirectory(distDir);
|
||||||
|
await verifyFilesExist(distDir, config.filePatterns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* eslint-enable no-await-in-loop */
|
||||||
|
console.log('✅ Build completed successfully and all expected artifacts are in place.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBuildVerificationConfigs() {
|
||||||
|
return {
|
||||||
|
'--electron-unbundled': {
|
||||||
|
printDistDirScriptArgument: '--electron-unbundled',
|
||||||
|
filePatterns: [
|
||||||
|
/main[/\\]index\.cjs/,
|
||||||
|
/preload[/\\]index\.cjs/,
|
||||||
|
/renderer[/\\]index\.htm(l)?/,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'--electron-bundled': {
|
||||||
|
printDistDirScriptArgument: '--electron-bundled',
|
||||||
|
filePatterns: [
|
||||||
|
/latest.*\.yml/, // generates latest.yml for auto-updates
|
||||||
|
/.*-\d+\.\d+\.\d+\..*/, // a file with extension and semantic version (packaged application)
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'--web': {
|
||||||
|
printDistDirScriptArgument: '--web',
|
||||||
|
filePatterns: [
|
||||||
|
/index\.htm(l)?/,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function anyCommandsFound(commands) {
|
||||||
|
return PROCESS_ARGUMENTS.some((arg) => commands.includes(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyDirectoryExists(directoryPath) {
|
||||||
|
try {
|
||||||
|
await access(directoryPath);
|
||||||
|
} catch (error) {
|
||||||
|
die(`Directory does not exist at \`${directoryPath}\`:\n\t${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyNonEmptyDirectory(directoryPath) {
|
||||||
|
const files = await readdir(directoryPath);
|
||||||
|
if (files.length === 0) {
|
||||||
|
die(`Directory is empty at \`${directoryPath}\``);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyFilesExist(directoryPath, filePatterns) {
|
||||||
|
const files = await listAllFilesRecursively(directoryPath);
|
||||||
|
for (const pattern of filePatterns) {
|
||||||
|
const match = files.some((file) => pattern.test(file));
|
||||||
|
if (!match) {
|
||||||
|
die(
|
||||||
|
`No file matches the pattern ${pattern.source} in directory \`${directoryPath}\``,
|
||||||
|
`\nFiles in directory:\n${files.map((file) => `\t- ${file}`).join('\n')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listAllFilesRecursively(directoryPath) {
|
||||||
|
const dir = await readdir(directoryPath, { withFileTypes: true });
|
||||||
|
const files = await Promise.all(dir.map(async (dirent) => {
|
||||||
|
const absolutePath = resolve(directoryPath, dirent.name);
|
||||||
|
if (dirent.isDirectory()) {
|
||||||
|
return listAllFilesRecursively(absolutePath);
|
||||||
|
}
|
||||||
|
return absolutePath;
|
||||||
|
}));
|
||||||
|
return files.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executePrintDistDirScript(flag) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const commandToRun = `${PRINT_DIST_DIR_SCRIPT_BASE_COMMAND} ${flag}`;
|
||||||
|
|
||||||
|
exec(commandToRun, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(`Execution failed with error: ${error}`));
|
||||||
|
} else if (stderr) {
|
||||||
|
reject(new Error(`Execution failed with stderr: ${stderr}`));
|
||||||
|
} else {
|
||||||
|
resolve(stdout.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function die(...message) {
|
||||||
|
console.error(...message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await main();
|
||||||
16
src/TypeHelpers.ts
Normal file
16
src/TypeHelpers.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export type Constructible<T, TArgs extends unknown[] = never> = {
|
||||||
|
prototype: T;
|
||||||
|
apply: (this: unknown, args: TArgs) => void;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PropertyKeys<T> = {
|
||||||
|
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? never : K;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
export type ConstructorArguments<T> =
|
||||||
|
T extends new (...args: infer U) => unknown ? U : never;
|
||||||
|
|
||||||
|
export type FunctionKeys<T> = {
|
||||||
|
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never;
|
||||||
|
}[keyof T];
|
||||||
50
src/application/Common/CustomError.ts
Normal file
50
src/application/Common/CustomError.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
Provides a unified and resilient way to extend errors across platforms.
|
||||||
|
|
||||||
|
Rationale:
|
||||||
|
- Babel:
|
||||||
|
> "Built-in classes cannot be properly subclassed due to limitations in ES5"
|
||||||
|
> https://web.archive.org/web/20230810014108/https://babeljs.io/docs/caveats#classes
|
||||||
|
- TypeScript:
|
||||||
|
> "Extending built-ins like Error, Array, and Map may no longer work"
|
||||||
|
> https://web.archive.org/web/20230810014143/https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
||||||
|
*/
|
||||||
|
export abstract class CustomError extends Error {
|
||||||
|
constructor(message?: string, options?: ErrorOptions) {
|
||||||
|
super(message, options);
|
||||||
|
|
||||||
|
fixPrototype(this, new.target.prototype);
|
||||||
|
ensureStackTrace(this);
|
||||||
|
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Environment = {
|
||||||
|
getSetPrototypeOf: () => Object.setPrototypeOf,
|
||||||
|
getCaptureStackTrace: () => Error.captureStackTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
function fixPrototype(target: Error, prototype: CustomError) {
|
||||||
|
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
||||||
|
const setPrototypeOf = Environment.getSetPrototypeOf();
|
||||||
|
if (!functionExists(setPrototypeOf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPrototypeOf(target, prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureStackTrace(target: Error) {
|
||||||
|
const captureStackTrace = Environment.getCaptureStackTrace();
|
||||||
|
if (!functionExists(captureStackTrace)) {
|
||||||
|
// captureStackTrace is only available on V8, if it's not available
|
||||||
|
// modern JS engines will usually generate a stack trace on error objects when they're thrown.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
captureStackTrace(target, target.constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function functionExists(func: unknown): boolean {
|
||||||
|
// Not doing truthy/falsy check i.e. if(func) as most values are truthy in JS for robustness
|
||||||
|
return typeof func === 'function';
|
||||||
|
}
|
||||||
@@ -1,25 +1,23 @@
|
|||||||
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
import { Environment } from '../Environment/Environment';
|
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
||||||
import { IEnvironment } from '../Environment/IEnvironment';
|
|
||||||
import { IApplicationFactory } from '../IApplicationFactory';
|
import { IApplicationFactory } from '../IApplicationFactory';
|
||||||
import { ApplicationFactory } from '../ApplicationFactory';
|
import { ApplicationFactory } from '../ApplicationFactory';
|
||||||
import { ApplicationContext } from './ApplicationContext';
|
import { ApplicationContext } from './ApplicationContext';
|
||||||
|
|
||||||
export async function buildContext(
|
export async function buildContext(
|
||||||
factory: IApplicationFactory = ApplicationFactory.Current,
|
factory: IApplicationFactory = ApplicationFactory.Current,
|
||||||
environment = Environment.CurrentEnvironment,
|
environment = RuntimeEnvironment.CurrentEnvironment,
|
||||||
): Promise<IApplicationContext> {
|
): Promise<IApplicationContext> {
|
||||||
if (!factory) { throw new Error('missing factory'); }
|
if (!factory) { throw new Error('missing factory'); }
|
||||||
if (!environment) { throw new Error('missing environment'); }
|
if (!environment) { throw new Error('missing environment'); }
|
||||||
const app = await factory.getApp();
|
const app = await factory.getApp();
|
||||||
const os = getInitialOs(app, environment);
|
const os = getInitialOs(app, environment.os);
|
||||||
return new ApplicationContext(app, os);
|
return new ApplicationContext(app, os);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitialOs(app: IApplication, environment: IEnvironment): OperatingSystem {
|
function getInitialOs(app: IApplication, currentOs: OperatingSystem): OperatingSystem {
|
||||||
const currentOs = environment.os;
|
|
||||||
const supportedOsList = app.getSupportedOsList();
|
const supportedOsList = app.getSupportedOsList();
|
||||||
if (supportedOsList.includes(currentOs)) {
|
if (supportedOsList.includes(currentOs)) {
|
||||||
return currentOs;
|
return currentOs;
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export enum FilterActionType {
|
||||||
|
Apply,
|
||||||
|
Clear,
|
||||||
|
}
|
||||||
37
src/application/Context/State/Filter/Event/FilterChange.ts
Normal file
37
src/application/Context/State/Filter/Event/FilterChange.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
|
import { FilterActionType } from './FilterActionType';
|
||||||
|
import { IFilterChangeDetails, IFilterChangeDetailsVisitor } from './IFilterChangeDetails';
|
||||||
|
|
||||||
|
export class FilterChange implements IFilterChangeDetails {
|
||||||
|
public static forApply(filter: IFilterResult) {
|
||||||
|
if (!filter) {
|
||||||
|
throw new Error('missing filter');
|
||||||
|
}
|
||||||
|
return new FilterChange(FilterActionType.Apply, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static forClear() {
|
||||||
|
return new FilterChange(FilterActionType.Clear);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
public readonly actionType: FilterActionType,
|
||||||
|
public readonly filter?: IFilterResult,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public visit(visitor: IFilterChangeDetailsVisitor): void {
|
||||||
|
if (!visitor) {
|
||||||
|
throw new Error('missing visitor');
|
||||||
|
}
|
||||||
|
switch (this.actionType) {
|
||||||
|
case FilterActionType.Apply:
|
||||||
|
visitor.onApply(this.filter);
|
||||||
|
break;
|
||||||
|
case FilterActionType.Clear:
|
||||||
|
visitor.onClear();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown action type: ${this.actionType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
|
import { FilterActionType } from './FilterActionType';
|
||||||
|
|
||||||
|
export interface IFilterChangeDetails {
|
||||||
|
readonly actionType: FilterActionType;
|
||||||
|
readonly filter?: IFilterResult;
|
||||||
|
|
||||||
|
visit(visitor: IFilterChangeDetailsVisitor): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFilterChangeDetailsVisitor {
|
||||||
|
onClear(): void;
|
||||||
|
onApply(filter: IFilterResult): void;
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
import { IFilterResult } from './IFilterResult';
|
import { IFilterResult } from './IFilterResult';
|
||||||
|
import { IFilterChangeDetails } from './Event/IFilterChangeDetails';
|
||||||
|
|
||||||
export interface IReadOnlyUserFilter {
|
export interface IReadOnlyUserFilter {
|
||||||
readonly currentFilter: IFilterResult | undefined;
|
readonly currentFilter: IFilterResult | undefined;
|
||||||
readonly filtered: IEventSource<IFilterResult>;
|
readonly filterChanged: IEventSource<IFilterChangeDetails>;
|
||||||
readonly filterRemoved: IEventSource<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserFilter extends IReadOnlyUserFilter {
|
export interface IUserFilter extends IReadOnlyUserFilter {
|
||||||
setFilter(filter: string): void;
|
applyFilter(filter: string): void;
|
||||||
removeFilter(): void;
|
clearFilter(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
|||||||
import { FilterResult } from './FilterResult';
|
import { FilterResult } from './FilterResult';
|
||||||
import { IFilterResult } from './IFilterResult';
|
import { IFilterResult } from './IFilterResult';
|
||||||
import { IUserFilter } from './IUserFilter';
|
import { IUserFilter } from './IUserFilter';
|
||||||
|
import { IFilterChangeDetails } from './Event/IFilterChangeDetails';
|
||||||
|
import { FilterChange } from './Event/FilterChange';
|
||||||
|
|
||||||
export class UserFilter implements IUserFilter {
|
export class UserFilter implements IUserFilter {
|
||||||
public readonly filtered = new EventSource<IFilterResult>();
|
public readonly filterChanged = new EventSource<IFilterChangeDetails>();
|
||||||
|
|
||||||
public readonly filterRemoved = new EventSource<void>();
|
|
||||||
|
|
||||||
public currentFilter: IFilterResult | undefined;
|
public currentFilter: IFilterResult | undefined;
|
||||||
|
|
||||||
@@ -16,9 +16,9 @@ export class UserFilter implements IUserFilter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFilter(filter: string): void {
|
public applyFilter(filter: string): void {
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter');
|
throw new Error('Filter must be defined and not empty. Use clearFilter() to remove the filter');
|
||||||
}
|
}
|
||||||
const filterLowercase = filter.toLocaleLowerCase();
|
const filterLowercase = filter.toLocaleLowerCase();
|
||||||
const filteredScripts = this.collection.getAllScripts().filter(
|
const filteredScripts = this.collection.getAllScripts().filter(
|
||||||
@@ -33,12 +33,12 @@ export class UserFilter implements IUserFilter {
|
|||||||
filter,
|
filter,
|
||||||
);
|
);
|
||||||
this.currentFilter = matches;
|
this.currentFilter = matches;
|
||||||
this.filtered.notify(matches);
|
this.filterChanged.notify(FilterChange.forApply(this.currentFilter));
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFilter(): void {
|
public clearFilter(): void {
|
||||||
this.currentFilter = undefined;
|
this.currentFilter = undefined;
|
||||||
this.filterRemoved.notify();
|
this.filterChanged.notify(FilterChange.forClear());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { BrowserOsDetector } from './BrowserOs/BrowserOsDetector';
|
|
||||||
import { IBrowserOsDetector } from './BrowserOs/IBrowserOsDetector';
|
|
||||||
import { IEnvironment } from './IEnvironment';
|
|
||||||
|
|
||||||
export interface IEnvironmentVariables {
|
|
||||||
readonly window: Window & typeof globalThis;
|
|
||||||
readonly process: NodeJS.Process;
|
|
||||||
readonly navigator: Navigator;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Environment implements IEnvironment {
|
|
||||||
public static readonly CurrentEnvironment: IEnvironment = new Environment({
|
|
||||||
window,
|
|
||||||
process: typeof process !== 'undefined' ? process /* electron only */ : undefined,
|
|
||||||
navigator,
|
|
||||||
});
|
|
||||||
|
|
||||||
public readonly isDesktop: boolean;
|
|
||||||
|
|
||||||
public readonly os: OperatingSystem;
|
|
||||||
|
|
||||||
protected constructor(
|
|
||||||
variables: IEnvironmentVariables,
|
|
||||||
browserOsDetector: IBrowserOsDetector = new BrowserOsDetector(),
|
|
||||||
) {
|
|
||||||
if (!variables) {
|
|
||||||
throw new Error('variables is null or empty');
|
|
||||||
}
|
|
||||||
this.isDesktop = isDesktop(variables);
|
|
||||||
if (this.isDesktop) {
|
|
||||||
this.os = getDesktopOsType(getProcessPlatform(variables));
|
|
||||||
} else {
|
|
||||||
const userAgent = getUserAgent(variables);
|
|
||||||
this.os = !userAgent ? undefined : browserOsDetector.detect(userAgent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserAgent(variables: IEnvironmentVariables): string {
|
|
||||||
if (!variables.window || !variables.window.navigator) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return variables.window.navigator.userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProcessPlatform(variables: IEnvironmentVariables): string {
|
|
||||||
if (!variables.process || !variables.process.platform) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return variables.process.platform;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDesktopOsType(processPlatform: string): OperatingSystem | undefined {
|
|
||||||
// https://nodejs.org/api/process.html#process_process_platform
|
|
||||||
switch (processPlatform) {
|
|
||||||
case 'darwin':
|
|
||||||
return OperatingSystem.macOS;
|
|
||||||
case 'win32':
|
|
||||||
return OperatingSystem.Windows;
|
|
||||||
case 'linux':
|
|
||||||
return OperatingSystem.Linux;
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDesktop(variables: IEnvironmentVariables): boolean {
|
|
||||||
// More: https://github.com/electron/electron/issues/2288
|
|
||||||
// Renderer process
|
|
||||||
if (variables.window
|
|
||||||
&& variables.window.process
|
|
||||||
&& variables.window.process.type === 'renderer') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Main process
|
|
||||||
if (variables.process
|
|
||||||
&& variables.process.versions
|
|
||||||
&& Boolean(variables.process.versions.electron)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Detect the user agent when the `nodeIntegration` option is set to true
|
|
||||||
if (variables.navigator
|
|
||||||
&& variables.navigator.userAgent
|
|
||||||
&& variables.navigator.userAgent.includes('Electron')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
|
|
||||||
export interface IEnvironment {
|
|
||||||
readonly isDesktop: boolean;
|
|
||||||
readonly os: OperatingSystem;
|
|
||||||
}
|
|
||||||
@@ -7,16 +7,19 @@ import MacOsData from '@/application/collections/macos.yaml';
|
|||||||
import LinuxData from '@/application/collections/linux.yaml';
|
import LinuxData from '@/application/collections/linux.yaml';
|
||||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||||
import { Application } from '@/domain/Application';
|
import { Application } from '@/domain/Application';
|
||||||
|
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
||||||
|
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||||
import { parseCategoryCollection } from './CategoryCollectionParser';
|
import { parseCategoryCollection } from './CategoryCollectionParser';
|
||||||
|
|
||||||
export function parseApplication(
|
export function parseApplication(
|
||||||
parser = CategoryCollectionParser,
|
categoryParser = parseCategoryCollection,
|
||||||
processEnv: NodeJS.ProcessEnv = process.env,
|
informationParser = parseProjectInformation,
|
||||||
|
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
|
||||||
collectionsData = PreParsedCollections,
|
collectionsData = PreParsedCollections,
|
||||||
): IApplication {
|
): IApplication {
|
||||||
validateCollectionsData(collectionsData);
|
validateCollectionsData(collectionsData);
|
||||||
const information = parseProjectInformation(processEnv);
|
const information = informationParser(metadata);
|
||||||
const collections = collectionsData.map((collection) => parser(collection, information));
|
const collections = collectionsData.map((collection) => categoryParser(collection, information));
|
||||||
const app = new Application(information, collections);
|
const app = new Application(information, collections);
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
@@ -24,16 +27,12 @@ export function parseApplication(
|
|||||||
export type CategoryCollectionParserType
|
export type CategoryCollectionParserType
|
||||||
= (file: CollectionData, info: IProjectInformation) => ICategoryCollection;
|
= (file: CollectionData, info: IProjectInformation) => ICategoryCollection;
|
||||||
|
|
||||||
const CategoryCollectionParser: CategoryCollectionParserType = (file, info) => {
|
|
||||||
return parseCategoryCollection(file, info);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PreParsedCollections: readonly CollectionData [] = [
|
const PreParsedCollections: readonly CollectionData [] = [
|
||||||
WindowsData, MacOsData, LinuxData,
|
WindowsData, MacOsData, LinuxData,
|
||||||
];
|
];
|
||||||
|
|
||||||
function validateCollectionsData(collections: readonly CollectionData[]) {
|
function validateCollectionsData(collections: readonly CollectionData[]) {
|
||||||
if (!collections || !collections.length) {
|
if (!collections?.length) {
|
||||||
throw new Error('missing collections');
|
throw new Error('missing collections');
|
||||||
}
|
}
|
||||||
if (collections.some((collection) => !collection)) {
|
if (collections.some((collection) => !collection)) {
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import { CustomError } from '@/application/Common/CustomError';
|
||||||
import { NodeType } from './NodeType';
|
import { NodeType } from './NodeType';
|
||||||
import { NodeData } from './NodeData';
|
import { NodeData } from './NodeData';
|
||||||
|
|
||||||
export class NodeDataError extends Error {
|
export class NodeDataError extends CustomError {
|
||||||
constructor(message: string, public readonly context: INodeDataErrorContext) {
|
constructor(message: string, public readonly context: INodeDataErrorContext) {
|
||||||
super(createMessage(message, context));
|
super(createMessage(message, context));
|
||||||
Object.setPrototypeOf(this, new.target.prototype); // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
|
||||||
this.name = new.target.name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||||
|
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
||||||
import { Version } from '@/domain/Version';
|
import { Version } from '@/domain/Version';
|
||||||
|
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||||
|
import { ConstructorArguments } from '@/TypeHelpers';
|
||||||
|
|
||||||
export function parseProjectInformation(
|
export function
|
||||||
environment: NodeJS.ProcessEnv | VueAppEnvironment,
|
parseProjectInformation(
|
||||||
|
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
|
||||||
|
createProjectInformation: ProjectInformationFactory = (
|
||||||
|
...args
|
||||||
|
) => new ProjectInformation(...args),
|
||||||
): IProjectInformation {
|
): IProjectInformation {
|
||||||
const version = new Version(environment[VueAppEnvironmentKeys.VUE_APP_VERSION]);
|
const version = new Version(
|
||||||
return new ProjectInformation(
|
metadata.version,
|
||||||
environment[VueAppEnvironmentKeys.VUE_APP_NAME],
|
);
|
||||||
|
return createProjectInformation(
|
||||||
|
metadata.name,
|
||||||
version,
|
version,
|
||||||
environment[VueAppEnvironmentKeys.VUE_APP_SLOGAN],
|
metadata.slogan,
|
||||||
environment[VueAppEnvironmentKeys.VUE_APP_REPOSITORY_URL],
|
metadata.repositoryUrl,
|
||||||
environment[VueAppEnvironmentKeys.VUE_APP_HOMEPAGE_URL],
|
metadata.homepageUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VueAppEnvironmentKeys = {
|
export type ProjectInformationFactory = (
|
||||||
VUE_APP_VERSION: 'VUE_APP_VERSION',
|
...args: ConstructorArguments<typeof ProjectInformation>
|
||||||
VUE_APP_NAME: 'VUE_APP_NAME',
|
) => IProjectInformation;
|
||||||
VUE_APP_SLOGAN: 'VUE_APP_SLOGAN',
|
|
||||||
VUE_APP_REPOSITORY_URL: 'VUE_APP_REPOSITORY_URL',
|
|
||||||
VUE_APP_HOMEPAGE_URL: 'VUE_APP_HOMEPAGE_URL',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type VueAppEnvironment = {
|
|
||||||
[K in keyof typeof VueAppEnvironmentKeys]: string;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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 code: string;
|
||||||
readonly revertCode?: 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 { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
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 { FunctionCall } from '../FunctionCall';
|
import { FunctionCall } from '../FunctionCall';
|
||||||
import { FunctionCallArgumentCollection } from '../Argument/FunctionCallArgumentCollection';
|
import { CompiledCode } from './CompiledCode';
|
||||||
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
|
||||||
import { ICompiledCode } from './ICompiledCode';
|
|
||||||
|
|
||||||
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
export interface FunctionCallCompiler {
|
||||||
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
compileFunctionCalls(
|
||||||
|
calls: readonly FunctionCall[],
|
||||||
protected constructor(
|
|
||||||
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public compileCall(
|
|
||||||
calls: IFunctionCall[],
|
|
||||||
functions: ISharedFunctionCollection,
|
functions: ISharedFunctionCollection,
|
||||||
): ICompiledCode {
|
): CompiledCode;
|
||||||
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'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
import { IFunctionCall } from './IFunctionCall';
|
|
||||||
|
|
||||||
export class FunctionCall implements IFunctionCall {
|
export interface FunctionCall {
|
||||||
constructor(
|
readonly functionName: string;
|
||||||
public readonly functionName: string,
|
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
||||||
public readonly args: IReadOnlyFunctionCallArgumentCollection,
|
|
||||||
) {
|
|
||||||
if (!functionName) {
|
|
||||||
throw new Error('missing function name in function call');
|
|
||||||
}
|
|
||||||
if (!args) {
|
|
||||||
throw new Error('missing args');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
||||||
import { IFunctionCall } from './IFunctionCall';
|
import { FunctionCall } from './FunctionCall';
|
||||||
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
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) {
|
if (calls === undefined) {
|
||||||
throw new Error('missing call data');
|
throw new Error('missing call data');
|
||||||
}
|
}
|
||||||
@@ -22,12 +22,12 @@ function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
|
|||||||
return [calls as FunctionCallData];
|
return [calls as FunctionCallData];
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
function parseFunctionCall(call: FunctionCallData): FunctionCall {
|
||||||
if (!call) {
|
if (!call) {
|
||||||
throw new Error('missing call data');
|
throw new Error('missing call data');
|
||||||
}
|
}
|
||||||
const callArgs = parseArgs(call.parameters);
|
const callArgs = parseArgs(call.parameters);
|
||||||
return new FunctionCall(call.function, callArgs);
|
return new ParsedFunctionCall(call.function, callArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseArgs(
|
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 { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
import { IFunctionCall } from './Call/IFunctionCall';
|
import { FunctionCall } from './Call/FunctionCall';
|
||||||
|
|
||||||
export interface ISharedFunction {
|
export interface ISharedFunction {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
@@ -9,8 +9,8 @@ export interface ISharedFunction {
|
|||||||
|
|
||||||
export interface ISharedFunctionBody {
|
export interface ISharedFunctionBody {
|
||||||
readonly type: FunctionBodyType;
|
readonly type: FunctionBodyType;
|
||||||
readonly code: IFunctionCode;
|
readonly code: IFunctionCode | undefined;
|
||||||
readonly calls: readonly IFunctionCall[];
|
readonly calls: readonly FunctionCall[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FunctionBodyType {
|
export enum FunctionBodyType {
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ import { ISharedFunction } from './ISharedFunction';
|
|||||||
|
|
||||||
export interface ISharedFunctionCollection {
|
export interface ISharedFunctionCollection {
|
||||||
getFunctionByName(name: string): ISharedFunction;
|
getFunctionByName(name: string): ISharedFunction;
|
||||||
|
getRequiredParameterNames(functionName: string): string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IFunctionCall } from './Call/IFunctionCall';
|
import { FunctionCall } from './Call/FunctionCall';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FunctionBodyType, IFunctionCode, ISharedFunction, ISharedFunctionBody,
|
FunctionBodyType, IFunctionCode, ISharedFunction, ISharedFunctionBody,
|
||||||
@@ -8,7 +8,7 @@ import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParam
|
|||||||
export function createCallerFunction(
|
export function createCallerFunction(
|
||||||
name: string,
|
name: string,
|
||||||
parameters: IReadOnlyFunctionParameterCollection,
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
callSequence: readonly IFunctionCall[],
|
callSequence: readonly FunctionCall[],
|
||||||
): ISharedFunction {
|
): ISharedFunction {
|
||||||
if (!callSequence || !callSequence.length) {
|
if (!callSequence || !callSequence.length) {
|
||||||
throw new Error(`missing call sequence in function "${name}"`);
|
throw new Error(`missing call sequence in function "${name}"`);
|
||||||
@@ -38,7 +38,7 @@ class SharedFunction implements ISharedFunction {
|
|||||||
constructor(
|
constructor(
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
||||||
content: IFunctionCode | readonly IFunctionCall[],
|
content: IFunctionCode | readonly FunctionCall[],
|
||||||
bodyType: FunctionBodyType,
|
bodyType: FunctionBodyType,
|
||||||
) {
|
) {
|
||||||
if (!name) { throw new Error('missing function name'); }
|
if (!name) { throw new Error('missing function name'); }
|
||||||
@@ -46,7 +46,7 @@ class SharedFunction implements ISharedFunction {
|
|||||||
this.body = {
|
this.body = {
|
||||||
type: bodyType,
|
type: bodyType,
|
||||||
code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined,
|
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;
|
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) {
|
private has(functionName: string) {
|
||||||
return this.functionsByName.has(functionName);
|
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 { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
|
||||||
import { IScriptCompiler } from './IScriptCompiler';
|
import { IScriptCompiler } from './IScriptCompiler';
|
||||||
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
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 { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
|
||||||
import { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
|
import { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
|
||||||
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
||||||
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
|
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 {
|
export class ScriptCompiler implements IScriptCompiler {
|
||||||
private readonly functions: ISharedFunctionCollection;
|
private readonly functions: ISharedFunctionCollection;
|
||||||
@@ -21,7 +21,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
functions: readonly FunctionData[] | undefined,
|
functions: readonly FunctionData[] | undefined,
|
||||||
syntax: ILanguageSyntax,
|
syntax: ILanguageSyntax,
|
||||||
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
||||||
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
private readonly callCompiler: FunctionCallCompiler = FunctionCallSequenceCompiler.instance,
|
||||||
private readonly codeValidator: ICodeValidator = CodeValidator.instance,
|
private readonly codeValidator: ICodeValidator = CodeValidator.instance,
|
||||||
) {
|
) {
|
||||||
if (!syntax) { throw new Error('missing syntax'); }
|
if (!syntax) { throw new Error('missing syntax'); }
|
||||||
@@ -40,7 +40,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
if (!script) { throw new Error('missing script'); }
|
if (!script) { throw new Error('missing script'); }
|
||||||
try {
|
try {
|
||||||
const calls = parseFunctionCalls(script.call);
|
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);
|
validateCompiledCode(compiledCode, this.codeValidator);
|
||||||
return new ScriptCode(
|
return new ScriptCode(
|
||||||
compiledCode.code,
|
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(
|
[compiledCode.code, compiledCode.revertCode].forEach(
|
||||||
(code) => validator.throwIfInvalid(code, [new NoEmptyLines()]),
|
(code) => validator.throwIfInvalid(code, [new NoEmptyLines()]),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3558,82 +3558,98 @@ functions:
|
|||||||
parameters:
|
parameters:
|
||||||
- name: prefName
|
- name: prefName
|
||||||
- name: jsonValue
|
- name: jsonValue
|
||||||
# prefs.js file (https://web.archive.org/web/20221029211757/https://kb.mozillazine.org/Prefs.js_file) exists at
|
docs: |-
|
||||||
# - Default installation:
|
This script either creates or updates the `user.js` file to set specific Mozilla Firefox preferences.
|
||||||
# ~/.mozilla/firefox/<profile-name>/prefs.js
|
|
||||||
# - Flatpak installation:
|
The `user.js` file can be found in a Firefox profile folder [1] and its location depends on the type of installation:
|
||||||
# ~/.var/app/org.mozilla.firefox/.mozilla/firefox/<profile-name>/prefs.js
|
|
||||||
# - Snap installation:
|
- Default: `~/.mozilla/firefox/<profile-name>/user.js`
|
||||||
# ~/snap/firefox/common/.mozilla/firefox/<profile-name>/prefs.js
|
- Flatpak: `~/.var/app/org.mozilla.firefox/.mozilla/firefox/<profile-name>/user.js`
|
||||||
|
- Snap: `~/snap/firefox/common/.mozilla/firefox/<profile-name>/user.js`
|
||||||
|
|
||||||
|
While the `user.js` file is optional [2], if it's present, the Firefox application will prioritize its settings over
|
||||||
|
those in `prefs.js` upon startup [1][2]. To prevent potential profile corruption, Mozilla advises against editing
|
||||||
|
`prefs.js` directly [2].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230811005205/https://kb.mozillazine.org/User.js_file "User.js file - MozillaZine Knowledge Base"
|
||||||
|
[2]: https://web.archive.org/web/20221029211757/https://kb.mozillazine.org/Prefs.js_file "Prefs.js file - MozillaZine Knowledge Base"
|
||||||
code: |-
|
code: |-
|
||||||
pref_name='{{ $prefName }}'
|
pref_name='{{ $prefName }}'
|
||||||
pref_value='{{ $jsonValue }}'
|
pref_value='{{ $jsonValue }}'
|
||||||
echo "Setting preference \"$pref_name\" to \"$pref_value\"."
|
echo "Setting preference \"$pref_name\" to \"$pref_value\"."
|
||||||
pref_file_paths=(
|
declare -a profile_paths=(
|
||||||
~/.mozilla/firefox/*/prefs.js
|
~/.mozilla/firefox/*/
|
||||||
~/.var/app/org.mozilla.firefox/.mozilla/firefox/*/prefs.js
|
~/.var/app/org.mozilla.firefox/.mozilla/firefox/*/
|
||||||
~/snap/firefox/common/.mozilla/firefox/*/prefs.js
|
~/snap/firefox/common/.mozilla/firefox/*/
|
||||||
)
|
)
|
||||||
declare -i total_files_found=0
|
declare -i total_profiles_found=0
|
||||||
for pref_file in "${pref_file_paths[@]}"; do
|
for profile_dir in "${profile_paths[@]}"; do
|
||||||
if [ -f "$pref_file" ]; then
|
if [ ! -d "$profile_dir" ]; then
|
||||||
((total_files_found++))
|
continue
|
||||||
echo "$pref_file:"
|
fi
|
||||||
pref_start="user_pref(\"$pref_name\","
|
if [[ ! "$(basename "$profile_dir")" =~ ^[a-z0-9]{8}\..+ ]]; then
|
||||||
pref_line="user_pref(\"$pref_name\", $pref_value);"
|
continue # Not a profile folder
|
||||||
if ! grep --quiet "^$pref_start" "${pref_file}"; then
|
fi
|
||||||
echo $'\t'"Preference is not configured before."
|
((total_profiles_found++))
|
||||||
echo -n $'\n'"$pref_line" >> "$pref_file"
|
user_js_file="${profile_dir}user.js"
|
||||||
echo $'\t'"Successfully configured."
|
echo "$user_js_file:"
|
||||||
else
|
if [ ! -f "$user_js_file" ]; then
|
||||||
if grep --quiet "^$pref_line$" "${pref_file}"; then
|
touch "$user_js_file"
|
||||||
echo $'\t'"Skipping. Preference is already configured as expected."
|
echo $'\t''Created new user.js file'
|
||||||
else
|
fi
|
||||||
sed --in-place "/^$pref_start/d" "$pref_file"
|
pref_start="user_pref(\"$pref_name\","
|
||||||
echo $'\t'"Deleted assignment with unexpected value."
|
pref_line="user_pref(\"$pref_name\", $pref_value);"
|
||||||
echo -n $'\n'"$pref_line" >> "$pref_file"
|
if ! grep --quiet "^$pref_start" "${user_js_file}"; then
|
||||||
echo $'\t'"Successfully reconfigured with expected value."
|
echo -n $'\n'"$pref_line" >> "$user_js_file"
|
||||||
fi
|
echo $'\t'"Successfully added a new preference in $user_js_file."
|
||||||
fi
|
elif grep --quiet "^$pref_line$" "$user_js_file"; then
|
||||||
|
echo $'\t'"Skipping, preference is already set as expected in $user_js_file."
|
||||||
|
else
|
||||||
|
sed --in-place "/^$pref_start/c\\$pref_line" "$user_js_file"
|
||||||
|
echo $'\t'"Successfully replaced the existing incorrect preference in $user_js_file."
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [ "$total_files_found" -eq 0 ]; then
|
if [ "$total_profiles_found" -eq 0 ]; then
|
||||||
echo "No changes, no preference file is found."
|
echo 'No profile folders are found, no changes are made.'
|
||||||
else
|
else
|
||||||
echo "Ensured that $total_files_found profiles are compilant."
|
echo "Preferences verified in $total_profiles_found profiles."
|
||||||
fi
|
fi
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
pref_name='{{ $prefName }}'
|
pref_name='{{ $prefName }}'
|
||||||
pref_value='{{ $jsonValue }}'
|
pref_value='{{ $jsonValue }}'
|
||||||
echo "Restoring \"$pref_name\" to its default."
|
echo "Reverting preference: \"$pref_name\" to its default."
|
||||||
pref_file_paths=(
|
declare -a profile_paths=(
|
||||||
~/.mozilla/firefox/*/prefs.js
|
~/.mozilla/firefox/*/
|
||||||
~/.var/app/org.mozilla.firefox/.mozilla/firefox/*/prefs.js
|
~/.var/app/org.mozilla.firefox/.mozilla/firefox/*/
|
||||||
~/snap/firefox/common/.mozilla/firefox/*/prefs.js
|
~/snap/firefox/common/.mozilla/firefox/*/
|
||||||
)
|
)
|
||||||
declare -i total_files_found=0
|
declare -i total_profiles_found=0
|
||||||
for pref_file in "${pref_file_paths[@]}"; do
|
for profile_dir in "${profile_paths[@]}"; do
|
||||||
if [ -f "$pref_file" ]; then
|
user_js_file="${profile_dir}user.js"
|
||||||
((total_files_found++))
|
if [ ! -f "$user_js_file" ]; then
|
||||||
echo "$pref_file:"
|
continue
|
||||||
pref_start="user_pref(\"$pref_name\","
|
fi
|
||||||
pref_line="user_pref(\"$pref_name\", $pref_value);"
|
((total_profiles_found++))
|
||||||
if ! grep --quiet "^$pref_start" "${pref_file}"; then
|
echo "$user_js_file:"
|
||||||
echo $'\t'"Skipping. Preference is not configured before."
|
pref_start="user_pref(\"$pref_name\","
|
||||||
else
|
pref_line="user_pref(\"$pref_name\", $pref_value);"
|
||||||
if grep --quiet "^$pref_line$" "${pref_file}"; then
|
if ! grep --quiet "^$pref_start" "${user_js_file}"; then
|
||||||
sed --in-place "/^$pref_line/d" "$pref_file"
|
echo $'\t''Skipping, preference was not configured before.'
|
||||||
echo $'\t'"Successfully restored preference value to its default."
|
elif grep --quiet "^$pref_line$" "${user_js_file}"; then
|
||||||
else
|
sed --in-place "/^$pref_line/d" "$user_js_file"
|
||||||
echo $'\t'"Skipping, the preference has value that is not configured by privacy.sexy."
|
echo $'\t''Successfully reverted preference to default.'
|
||||||
fi
|
if ! grep --quiet '[^[:space:]]' "$user_js_file"; then
|
||||||
|
rm "$user_js_file"
|
||||||
|
echo $'\t''Removed user.js file as it became empty.'
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo $'\t''Skipping, the preference has value that is not configured by privacy.sexy.'
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [ "$total_files_found" -eq 0 ]; then
|
if [ "$total_profiles_found" -eq 0 ]; then
|
||||||
echo "No changes, no preference file is found."
|
echo 'No reversion was necessary.'
|
||||||
else
|
else
|
||||||
echo "Ensured that $total_files_found profiles are compilant."
|
echo "Preferences verified in $total_profiles_found profiles."
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: RenameFile
|
name: RenameFile
|
||||||
|
|||||||
@@ -606,9 +606,46 @@ actions:
|
|||||||
wevtutil.exe cl %1 "%%i"
|
wevtutil.exe cl %1 "%%i"
|
||||||
)
|
)
|
||||||
-
|
-
|
||||||
name: Clean Windows Defender scan history
|
name: Clear Defender scan (protection) history
|
||||||
docs: https://www.thewindowsclub.com/clear-windows-defender-protection-history
|
docs: |-
|
||||||
code: del "%ProgramData%\Microsoft\Windows Defender\Scans\History\" /s /f /q
|
This script deletes the scan history kept by Windows Defender on your computer. Windows Defender logs detected threats but also gathers
|
||||||
|
and stores data about various other files it scans [1] [2]. While removing this history enhances your privacy, it might decrease security,
|
||||||
|
as these logs assist in monitoring threats. By eliminating traces of your system's files, activities and any threats detected, you ensure
|
||||||
|
no residual data can be utilized to study or analyze your computer's activities, thus protecting your privacy.
|
||||||
|
|
||||||
|
Defender keeps a log of various details whenever it scans your computer for threats. This includes [3] [4]:
|
||||||
|
|
||||||
|
- **Time**: The moment the threat was discovered.
|
||||||
|
- **Threat Status**: The action carried out against the threat.
|
||||||
|
- **Virus Type**: The type or category of the virus.
|
||||||
|
- **Threat ID**: A unique identifier for the threat.
|
||||||
|
- **Virus Name**: The name of the virus.
|
||||||
|
- **File Path**: The location of the threat on your computer.
|
||||||
|
- **File Hash**: A unique code representing the file.
|
||||||
|
- **Quarantine File Name (GUID)**: The name given to the quarantined threat.
|
||||||
|
- **File Size**: The size of the file.
|
||||||
|
|
||||||
|
When you first set up Windows, it conducts an initial scan [1]. This scan identifies system files that won't require future
|
||||||
|
scans [1]. These 'safe' files are saved in a unique folder, which becomes a part of the scan history [1].
|
||||||
|
|
||||||
|
If a threat is recognized, Windows Defender will notify you [4]. Regardless of whether you choose to run the file or not, a
|
||||||
|
`DetectionHistory` file is created [2]. This file is stored in a specific folder
|
||||||
|
(`%ProgramData%\Microsoft\Windows Defender\Scans\History\Service\DetectionHistory\[numbered folder]\`), and it contains a
|
||||||
|
system-generated ID for the event [2].
|
||||||
|
|
||||||
|
> **Caution**: Deleting these logs may decrease your security. These logs help in keeping track of potential threats and their sources,
|
||||||
|
allowing for a more proactive response in future encounters. Without this history, Windows Defender might not recognize recurring threats
|
||||||
|
as quickly, possibly leaving your system more vulnerable. It's essential to understand that you're making a trade-off between enhanced
|
||||||
|
privacy and potentially reduced security.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230829142700/https://download.microsoft.com/download/7/e/7/7e7662cf-cbea-470b-a97e-ce7ce0d98dc2/win7perf.docx "Performance Testing Guide for Windows | Microsoft"
|
||||||
|
[2]: https://web.archive.org/web/20230829143754/https://www.sans.org/blog/uncovering-windows-defender-real-time-protection-history-with-dhparser/ "Uncovering Windows Defender Real-time Protection History with DHParser | SANS Alumni Blog"
|
||||||
|
[3]: https://web.archive.org/web/20230829144957/https://learn.microsoft.com/en-us/previous-versions/windows/desktop/defender/msft-mpthreatdetection "MSFT\_MpThreatDetection class | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230829144434/https://forensafe.com/blogs/windows_defender.html "Windows Defender | Forensafe"
|
||||||
|
call:
|
||||||
|
function: RunInlineCodeAsTrustedInstaller # Otherwise it cannot access/delete files under `Scans\History`, see https://github.com/undergroundwires/privacy.sexy/issues/246
|
||||||
|
parameters:
|
||||||
|
code: del "%ProgramData%\Microsoft\Windows Defender\Scans\History" /s /f /q
|
||||||
-
|
-
|
||||||
name: Clear credentials from Windows Credential Manager
|
name: Clear credentials from Windows Credential Manager
|
||||||
code: |-
|
code: |-
|
||||||
@@ -1094,32 +1131,130 @@ actions:
|
|||||||
serviceName: wercplsupport # Check: (Get-Service -Name wercplsupport).StartType
|
serviceName: wercplsupport # Check: (Get-Service -Name wercplsupport).StartType
|
||||||
defaultStartupMode: Manual # Allowed values: Automatic | Manual
|
defaultStartupMode: Manual # Allowed values: Automatic | Manual
|
||||||
-
|
-
|
||||||
category: Disable automatic driver updates by Windows Update
|
category: Disable Windows Update data collection
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable device metadata retrieval (breaks auto updates)
|
category: Disable automatic driver updates by Windows Update
|
||||||
recommend: strict
|
children:
|
||||||
docs:
|
-
|
||||||
- https://www.stigviewer.com/stig/windows_server_2012_member_server/2014-01-07/finding/V-21964
|
name: Disable device metadata retrieval (breaks auto updates)
|
||||||
- https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-deviceinstallation#deviceinstallation-preventdevicemetadatafromnetwork
|
recommend: strict
|
||||||
code: |-
|
docs:
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 1 /f
|
- https://www.stigviewer.com/stig/windows_server_2012_member_server/2014-01-07/finding/V-21964
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 1 /f
|
- https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-deviceinstallation#deviceinstallation-preventdevicemetadatafromnetwork
|
||||||
revertCode: |-
|
code: |-
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 0 /f
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 1 /f
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 0 /f
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 1 /f
|
||||||
|
revertCode: |-
|
||||||
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 0 /f
|
||||||
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 0 /f
|
||||||
|
-
|
||||||
|
name: Do not include drivers with Windows Updates
|
||||||
|
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsUpdate::ExcludeWUDriversInQualityUpdate
|
||||||
|
recommend: strict
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v "ExcludeWUDriversInQualityUpdate" /t REG_DWORD /d 1 /f
|
||||||
|
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v "ExcludeWUDriversInQualityUpdate" /t REG_DWORD /d 0 /f
|
||||||
|
-
|
||||||
|
name: Prevent Windows Update for device driver search
|
||||||
|
docs: https://www.stigviewer.com/stig/windows_7/2018-02-12/finding/V-21965
|
||||||
|
recommend: strict
|
||||||
|
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 0 /f
|
||||||
|
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 1 /f
|
||||||
-
|
-
|
||||||
name: Do not include drivers with Windows Updates
|
category: Disable obtaining updates from other PCs on the Internet (delivery optimization)
|
||||||
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsUpdate::ExcludeWUDriversInQualityUpdate
|
docs: |-
|
||||||
recommend: strict
|
Windows Delivery Optimization is a feature introduced by Microsoft to facilitate a more efficient downloading process for Windows
|
||||||
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v "ExcludeWUDriversInQualityUpdate" /t REG_DWORD /d 1 /f
|
updates, upgrades, and applications [1] [2]. Instead of exclusively relying on Microsoft's servers, this feature identifies other
|
||||||
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v "ExcludeWUDriversInQualityUpdate" /t REG_DWORD /d 0 /f
|
PCs on a user's local network or even across the internet that already possess the desired updates or applications [2]. By breaking
|
||||||
-
|
the download into smaller segments and fetching each from the fastest and most reliable source, which can include other PCs, the
|
||||||
name: Prevent Windows Update for device driver search
|
system ensures more efficient downloads [2]. To support this process, Delivery Optimization uses a local cache to temporarily store
|
||||||
docs: https://www.stigviewer.com/stig/windows_7/2018-02-12/finding/V-21965
|
downloaded files [2].
|
||||||
recommend: strict
|
|
||||||
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 0 /f
|
While Delivery Optimization is designed for speed and reliability, its operation raises privacy concerns. Specifically, when enabled,
|
||||||
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 1 /f
|
it can distribute updates and applications from one user's PC to others [2], sharing users' data such as their IP addresses [3].
|
||||||
|
|
||||||
|
Benefits of disabling Delivery Optimization for privacy:
|
||||||
|
|
||||||
|
- **Minimizing Data Sharing**: By turning off Delivery Optimization, users ensure that updates and apps are neither downloaded from nor sent
|
||||||
|
to other devices [2]. This guarantees that all data remains strictly on the user's device [2] and the user IP is not shared [3].
|
||||||
|
- **Storage Conservation**: Users can save storage space by eliminating the local cache utilized by Delivery Optimization.
|
||||||
|
- **Guaranteed Source Authenticity**: Although Microsoft ensures the authenticity of updates and apps shared via Delivery Optimization [2],
|
||||||
|
disabling the feature guarantees that all updates and apps come directly from Microsoft's servers, eliminating potential intermediaries.
|
||||||
|
- **Bandwidth Conservation**: With the feature off, updates are restricted to direct downloads from Microsoft [1]. This is beneficial
|
||||||
|
for users on metered or capped internet connections, as it allows for more effective bandwidth monitoring [2].
|
||||||
|
- **Enhanced Security**: Devices using Delivery Optimization open port 7680 to accept peer requests [4]. Disabling the feature avoids this,
|
||||||
|
ensuring users are not exposed to unwanted inbound traffic and enhancing security [5].
|
||||||
|
- **VPN Protection**: Although Delivery Optimization attempts to detect VPNs and halts uploads when a VPN connection is detected [4], disabling
|
||||||
|
it removes any risk of unintended data sharing over a VPN.
|
||||||
|
|
||||||
|
Notably, the USA government [5] and Department of Defense (DoD) in the USA [6] recommends disabling this feature.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230914164204/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization "What is Delivery Optimization? - Windows Deployment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230914164355/https://support.microsoft.com/en-us/windows/windows-update-delivery-optimization-and-privacy-bf86a244-8f26-a3c7-a137-a43bfbe688e8 "Windows Update Delivery Optimization and privacy - Microsoft Support"
|
||||||
|
[3]: https://web.archive.org/web/20230914164646/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-monitor "Monitor Delivery Optimization - Windows Deployment | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230905120220/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-faq "Delivery Optimization Frequently Asked Questions - Windows Deployment | Microsoft Learn"
|
||||||
|
[5]: https://web.archive.org/web/20230914171139/https://www.irs.gov/pub/irs-utl/win10.xlsx "Internal Revenue Service Office of Safeguards - Windows 10 | irs.gov"
|
||||||
|
[6]: https://web.archive.org/web/20230914171410/https://www.stigviewer.com/stig/windows_10/2019-01-04/finding/V-65681 "Windows Update must not obtain updates from other PCs on the Internet | stigviewer.com"
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable peering download method for Windows Updates
|
||||||
|
recommend: standard
|
||||||
|
docs: |-
|
||||||
|
This script modifies Delivery Optimization's download method for Windows Updates [1] to disable peering. When this script is run, it sets the
|
||||||
|
download method to `0`, which means "HTTP only, no peering" [1] [2]. As a result, Windows Updates are downloaded solely from the internet and
|
||||||
|
not from other computers on the network (referred to as "peer-to-peer") [3].
|
||||||
|
|
||||||
|
Peer-to-peer is a method where multiple computers share data amongst themselves. For Windows Updates, the default setting is for computers
|
||||||
|
within a network to share updates (called LAN mode, represented by the value `1`) [1] [2].
|
||||||
|
|
||||||
|
Changing the setting to "HTTP only" reduces potential vulnerabilities [3]. When updates are fetched only from official servers, there's
|
||||||
|
less chance of unwanted or malicious data entering the system. This is why the Department of Defense (DoD) in the USA [4] and USA government [3]
|
||||||
|
recommends this setting. They assert that leaving it in its default configuration could expose the system to additional risks [3].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230914171524/https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-deliveryoptimization "DeliveryOptimization Policy CSP - Windows Client Management | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230914171842/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-reference "Delivery Optimization reference - Windows Deployment | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230914171139/https://www.irs.gov/pub/irs-utl/win10.xlsx "Internal Revenue Service Office of Safeguards - Windows 10 | irs.gov"
|
||||||
|
[4]: https://web.archive.org/web/20230914171410/https://www.stigviewer.com/stig/windows_10/2019-01-04/finding/V-65681 "Windows Update must not obtain updates from other PCs on the Internet | stigviewer.com"
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization" /v "DODownloadMode" /t "REG_DWORD" /d 0 /f
|
||||||
|
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization" /v "DODownloadMode" /f 2>nul # Key does not exists since Windows 10 21H2, Windows 11 22H2
|
||||||
|
-
|
||||||
|
name: Disable "Delivery Optimization" service (breaks Microsoft Store downloads)
|
||||||
|
recommend: strict
|
||||||
|
docs: |-
|
||||||
|
Delivery Optimization is a Windows feature that provides the Windows Updates through peer-to-peer sharing [1]. In simple terms, instead of solely
|
||||||
|
relying on Microsoft's servers for updates, your computer can also fetch them from other devices that already possess the necessary files.
|
||||||
|
|
||||||
|
The "Delivery Optimization" service manages these content delivery tasks [2] [3]. It orchestrates the retrieval of updates both from other Windows users [3].
|
||||||
|
In doing so, it connects to various Microsoft service points to collect data, such as policies, content details, device specifications, and information about
|
||||||
|
other Windows users [3]. This data sharing raises privacy concerns.
|
||||||
|
|
||||||
|
This service also logs IP addresses [4] of peers which can be considered personal data. It listens on port 7680 for TCP/UDP traffic [5] that may expose the user
|
||||||
|
to unwanted inbound traffic and enhancing security [6].
|
||||||
|
|
||||||
|
By default, the "Delivery Optimization" service is set to start automatically when Windows boots up [2]. This script alters that behavior, ensuring
|
||||||
|
it doesn't run unless explicitly started by the user.
|
||||||
|
|
||||||
|
Taking control of this service prevents Microsoft from activating peer-to-peer sharing, enhancing user privacy. It ensures your device doesn't share update data
|
||||||
|
or fetch it from arbitrary peers.
|
||||||
|
|
||||||
|
> **Caution**: Disabling this service affects the functionality of Windows Store. It plays a role not just in Windows Updates but also in Microsoft Store app
|
||||||
|
downloads, especially since Windows 11 [7]. There have been reported issues with some app downloads on Windows 10 [8].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230914164204/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization "What is Delivery Optimization? - Windows Deployment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230905120815/https://learn.microsoft.com/en-us/windows/iot/iot-enterprise/optimize/services#delivery-optimization "Guidance on disabling system services on Windows IoT Enterprise | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230914172129/https://learn.microsoft.com/en-us/windows/deployment/do/delivery-optimization-workflow "Delivery Optimization client-service communication explained - Windows Deployment | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230914164646/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-monitor "Monitor Delivery Optimization - Windows Deployment | Microsoft Learn"
|
||||||
|
[5]: https://web.archive.org/web/20230914172319/https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-deployment "Deploying a privileged access solution | Microsoft Learn"
|
||||||
|
[6]: https://web.archive.org/web/20230914171139/https://www.irs.gov/pub/irs-utl/win10.xlsx "Internal Revenue Service Office of Safeguards - Windows 10 | irs.gov"
|
||||||
|
[7]: https://web.archive.org/web/20230914164355/https://support.microsoft.com/en-us/windows/windows-update-delivery-optimization-and-privacy-bf86a244-8f26-a3c7-a137-a43bfbe688e8 "Windows Update Delivery Optimization and privacy - Microsoft Support"
|
||||||
|
[8]: https://github.com/undergroundwires/privacy.sexy/issues/173 "[BUG] Error 0x80004002 on Microsoft Store when attempting to download an app · Issue #173 · undergroundwires/privacy.sexy"
|
||||||
|
call:
|
||||||
|
function: DisableServiceInRegistry
|
||||||
|
# Using registry way because because other options such as "sc config" or
|
||||||
|
# "Set-Service" returns "Access is denied" since Windows 10 1809.
|
||||||
|
parameters:
|
||||||
|
serviceName: DoSvc # Check: (Get-Service -Name 'DoSvc').StartType
|
||||||
|
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
|
||||||
-
|
-
|
||||||
name: Disable cloud speech recognition
|
name: Disable cloud speech recognition
|
||||||
recommend: standard
|
recommend: standard
|
||||||
@@ -1776,12 +1911,26 @@ actions:
|
|||||||
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "0" /f
|
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "0" /f
|
||||||
-
|
-
|
||||||
name: Disable Windows Spotlight (random wallpaper on lock screen)
|
name: Disable Windows Spotlight (random wallpaper on lock screen)
|
||||||
recommend: standard
|
recommend: strict
|
||||||
docs:
|
docs: |-
|
||||||
- https://docs.microsoft.com/en-us/windows/configuration/windows-spotlight
|
The script disables the Windows Spotlight feature. Windows Spotlight is a feature in Windows 10 and Windows 11 [1] that automatically downloads
|
||||||
- https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#25-windows-spotlight
|
and displays random wallpapers on the lock screen [1] [2]. These images are sourced from the internet [1] [2] [3]. At times, it might also promote
|
||||||
|
various Microsoft products, services [1] [2], or even third-party apps and content [4].
|
||||||
|
|
||||||
|
When the lock screen fetches images from the internet, there's a silent data exchange happening. This can inadvertently reveal details about the
|
||||||
|
user's device or their preferences.
|
||||||
|
|
||||||
|
To mitigate this potential privacy risk, the script makes a change to a key (`DisableWindowsSpotlightFeatures`) in the Windows operating system [3].
|
||||||
|
Originally, Windows Spotlight is turned on unless the user decides otherwise [2].
|
||||||
|
By applying this script, users can be sure their lock screen remains private and doesn't retrieve wallpapers from the internet, eliminating potential
|
||||||
|
data leaks.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230911110727/https://support.microsoft.com/en-us/windows/personalize-your-lock-screen-81dab9b0-35cf-887c-84a0-6de8ef72bea0 "Personalize your lock screen - Microsoft Support"
|
||||||
|
[2]: https://web.archive.org/web/20230911110748/https://learn.microsoft.com/en-us/windows/configuration/windows-spotlight "Configure Windows Spotlight on the lock screen - Configure Windows | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230911110911/https://learn.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#25-windows-spotlight "Manage connections from Windows 10 and Windows 11 Server/Enterprise editions operating system components to Microsoft services - Windows Privacy | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230911110921/https://download.microsoft.com/download/8/F/B/8FBD2E85-8852-45EC-8465-92756EBD9365/Windows10andWindowsServer2016PolicySettings.xlsx "Group Policy Settings Reference - Microsoft"
|
||||||
code: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "1" /f
|
code: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "1" /f
|
||||||
revertCode: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "0" /f
|
revertCode: reg delete "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /f 2>nul # Key does not exists since Windows 10 21H2, Windows 11 22H2
|
||||||
-
|
-
|
||||||
name: Disable Microsoft consumer experiences
|
name: Disable Microsoft consumer experiences
|
||||||
recommend: standard
|
recommend: standard
|
||||||
@@ -2272,8 +2421,7 @@ actions:
|
|||||||
function: SetVsCodeSetting
|
function: SetVsCodeSetting
|
||||||
parameters:
|
parameters:
|
||||||
setting: update.mode
|
setting: update.mode
|
||||||
powerShellValue: >-
|
powerShellValue: manual
|
||||||
'manual'
|
|
||||||
-
|
-
|
||||||
name: Show Release Notes from Microsoft online service after an update
|
name: Show Release Notes from Microsoft online service after an update
|
||||||
call:
|
call:
|
||||||
@@ -2415,21 +2563,25 @@ actions:
|
|||||||
category: Chromium Edge settings
|
category: Chromium Edge settings
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable Edge usage and crash-related data reporting (shows "Your browser is managed") # Obselete since Microsoft Edge version 89
|
name: Disable Edge diagnostic data sending (shows "Your browser is managed")
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs:
|
docs:
|
||||||
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled
|
- http://archive.today/2023.08.26-152941/https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::DiagnosticData
|
||||||
- https://docs.microsoft.com/en-us/DeployEdge/microsoft-edge-policies#metricsreportingenabled
|
- https://learn.microsoft.com/DeployEdge/microsoft-edge-policies#diagnosticdata
|
||||||
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
|
- http://archive.today/2023.08.26-152952/https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled
|
||||||
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /f
|
- https://learn.microsoft.com/en-gb/DeployEdge/microsoft-edge-policies#metricsreportingenabled
|
||||||
-
|
- http://archive.today/2023.08.26-153019/https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices
|
||||||
name: Disable sending site information (shows "Your browser is managed") # Obselete since Microsoft Edge version 89
|
- https://learn.microsoft.com/DeployEdge/microsoft-edge-policies#sendsiteinfotoimproveservices
|
||||||
recommend: standard
|
code: |-
|
||||||
docs:
|
:: Disabling metrics and site info sending for Edge v88 ≥
|
||||||
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
|
||||||
- https://docs.microsoft.com/en-us/DeployEdge/microsoft-edge-policies#sendsiteinfotoimproveservices
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /t REG_DWORD /d 0 /f
|
||||||
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /t REG_DWORD /d 0 /f
|
:: Disabling diagnostic data (replacing metrics and site info sending since Edge v89 ≤)
|
||||||
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /f
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "DiagnosticData" /t REG_DWORD /d 0 /f
|
||||||
|
revertCode: |-
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /f 2>nul
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /f 2>nul
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "DiagnosticData" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Disable Automatic Installation of Microsoft Edge Chromium
|
name: Disable Automatic Installation of Microsoft Edge Chromium
|
||||||
docs:
|
docs:
|
||||||
@@ -2456,7 +2608,7 @@ actions:
|
|||||||
recommend: standard
|
recommend: standard
|
||||||
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.EdgeUI::DisableRecentApps
|
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.EdgeUI::DisableRecentApps
|
||||||
code: reg add "HKCU\Software\Policies\Microsoft\Windows\EdgeUI" /v "DisableRecentApps" /t REG_DWORD /d 1 /f
|
code: reg add "HKCU\Software\Policies\Microsoft\Windows\EdgeUI" /v "DisableRecentApps" /t REG_DWORD /d 1 /f
|
||||||
revertCode: reg add "HKCU\Software\Policies\Microsoft\Windows\EdgeUI" /v "DisableRecentApps" /t REG_DWORD /d 0/f
|
revertCode: reg add "HKCU\Software\Policies\Microsoft\Windows\EdgeUI" /v "DisableRecentApps" /t REG_DWORD /d 0 /f
|
||||||
-
|
-
|
||||||
name: Turn off backtracking
|
name: Turn off backtracking
|
||||||
recommend: standard
|
recommend: standard
|
||||||
@@ -3139,6 +3291,68 @@ actions:
|
|||||||
-
|
-
|
||||||
category: Disable Windows Defender Firewall # Also known as Windows Firewall, Microsoft Defender Firewall
|
category: Disable Windows Defender Firewall # Also known as Windows Firewall, Microsoft Defender Firewall
|
||||||
children:
|
children:
|
||||||
|
-
|
||||||
|
category: Disable Windows Defender Firewall Services and Drivers (breaks Microsoft Store and `netsh advfirewall` CLI)
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable Windows Defender Firewall Authorization Driver service
|
||||||
|
docs:
|
||||||
|
- http://batcmd.com/windows/10/services/mpsdrv/
|
||||||
|
# ❗️ Breaks: `netsh advfirewall set`
|
||||||
|
# Disabling and stopping it breaks "netsh advfirewall set" commands such as
|
||||||
|
# `netsh advfirewall set allprofiles state on`, `netsh advfirewall set allprofiles state off`.
|
||||||
|
# More about `netsh firewall` context: https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/netsh-advfirewall-firewall-control-firewall-behavior
|
||||||
|
# ! Breaks: Windows Store
|
||||||
|
# The Windows Defender Firewall service depends on this service.
|
||||||
|
# Disabling this will also disable the Windows Defender Firewall service, breaking Microsoft Store.
|
||||||
|
# https://i.imgur.com/zTmtSwT.png
|
||||||
|
call:
|
||||||
|
-
|
||||||
|
function: DisableServiceInRegistry # We must disable it on registry level, "Access is denied" for sc config
|
||||||
|
parameters:
|
||||||
|
serviceName: mpsdrv # Check: (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\mpsdrv").Start
|
||||||
|
defaultStartupMode: Manual # Alowed values: Boot | System | Automatic | Manual
|
||||||
|
-
|
||||||
|
function: RenameSystemFile
|
||||||
|
parameters:
|
||||||
|
filePath: '%SystemRoot%\System32\drivers\mpsdrv.sys'
|
||||||
|
-
|
||||||
|
name: Disable Windows Defender Firewall service
|
||||||
|
docs:
|
||||||
|
- http://batcmd.com/windows/10/services/mpssvc/
|
||||||
|
- https://en.wikipedia.org/wiki/Windows_Firewall
|
||||||
|
# More information about MpsSvc:
|
||||||
|
- https://web.archive.org/web/20110203202612/http://technet.microsoft.com/en-us/library/dd364391(v=WS.10).aspx
|
||||||
|
# More information about boot time protection and stopping the firewall service:
|
||||||
|
- https://web.archive.org/web/20110131034058/http://blogs.technet.com:80/b/networking/archive/2009/03/24/stopping-the-windows-authenticating-firewall-service-and-the-boot-time-policy.aspx
|
||||||
|
# Stopping the service associated with Windows Firewall is not supported by Microsoft:
|
||||||
|
- https://web.archive.org/web/20121106033255/http://technet.microsoft.com/en-us/library/cc753180.aspx
|
||||||
|
# ❗️ Breaks Microsoft Store
|
||||||
|
# Can no longer update nor install apps, they both fail with 0x80073D0A
|
||||||
|
# Also breaks some of Store apps such as Photos:
|
||||||
|
# - https://answers.microsoft.com/en-us/windows/forum/all/microsoft-store-windows-defender-windows-firewall/f2f68cd7-64ec-4fe1-ade4-9d12cde057f9
|
||||||
|
# - https://github.com/undergroundwires/privacy.sexy/issues/104#issuecomment-962651791
|
||||||
|
# > The MpsSvc service host much more functionality than just windows firewall. For instance, Windows
|
||||||
|
# Service hardening which is a windows protection of system services. It also host network isolatio
|
||||||
|
# which is a crucial part of the confidence model for Windows Store based applications. 3rd party firewalls
|
||||||
|
# know this fact and instead of disabling the firewall service they coordinate through public APIs with Windows
|
||||||
|
# Firewall so that they can have ownership of the firewall policies of the computer. Hence you do not have to do
|
||||||
|
# anything special once you install a 3rd party security product.
|
||||||
|
# Source: https://www.walkernews.net/2012/09/23/how-to-fix-windows-store-app-update-error-code-0x80073d0a/
|
||||||
|
# ❗️ Breaks: `netsh advfirewall set`
|
||||||
|
# Disabling and stopping it breaks "netsh advfirewall set" commands such as
|
||||||
|
# `netsh advfirewall set allprofiles state on`, `netsh advfirewall set allprofiles state off`.
|
||||||
|
# More about `netsh firewall` context: https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/netsh-advfirewall-firewall-control-firewall-behavior
|
||||||
|
call:
|
||||||
|
-
|
||||||
|
function: DisableServiceInRegistry # We must disable it on registry level, "Access is denied" for sc config
|
||||||
|
parameters:
|
||||||
|
serviceName: MpsSvc # Check: (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\MpsSvc").Start
|
||||||
|
defaultStartupMode: Automatic # Alowed values: Boot | System | Automatic | Manual
|
||||||
|
-
|
||||||
|
function: RenameSystemFile
|
||||||
|
parameters:
|
||||||
|
filePath: '%WinDir%\system32\mpssvc.dll'
|
||||||
-
|
-
|
||||||
name: Disable Firewall through command-line utility
|
name: Disable Firewall through command-line utility
|
||||||
# ❗️ Following must be enabled and in running state:
|
# ❗️ Following must be enabled and in running state:
|
||||||
@@ -3214,6 +3428,11 @@ actions:
|
|||||||
reg add "HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile" /v "EnableFirewall" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile" /v "EnableFirewall" /t REG_DWORD /d 1 /f
|
||||||
reg add "HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\PublicProfile" /v "EnableFirewall" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\PublicProfile" /v "EnableFirewall" /t REG_DWORD /d 1 /f
|
||||||
reg add "HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\PrivateProfile" /v "EnableFirewall" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\PrivateProfile" /v "EnableFirewall" /t REG_DWORD /d 1 /f
|
||||||
|
-
|
||||||
|
name: Hide the "Firewall and network protection" area from Windows Defender Security Center
|
||||||
|
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefenderSecurityCenter::FirewallNetworkProtection_UILockdown
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender Security Center\Firewall and network protection" /v "UILockdown" /t REG_DWORD /d "1" /f
|
||||||
|
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender Security Center\Firewall and network protection" /v "UILockdown" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Disable Microsoft Defender Antivirus # Depreciated since Windows 10 version 1903
|
name: Disable Microsoft Defender Antivirus # Depreciated since Windows 10 version 1903
|
||||||
docs:
|
docs:
|
||||||
@@ -3804,7 +4023,7 @@ actions:
|
|||||||
code: reg add "HKLM\Software\Policies\Microsoft\Windows Defender\Scan" /v "DisableRestorePoint" /t REG_DWORD /d "1" /f
|
code: reg add "HKLM\Software\Policies\Microsoft\Windows Defender\Scan" /v "DisableRestorePoint" /t REG_DWORD /d "1" /f
|
||||||
revertCode: reg delete "HKLM\Software\Policies\Microsoft\Windows Defender\Scan" /v "DisableRestorePoint" /f 2>nul
|
revertCode: reg delete "HKLM\Software\Policies\Microsoft\Windows Defender\Scan" /v "DisableRestorePoint" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Set minumum time for keeping files in scan history folder
|
name: Set minimum time for keeping files in scan history folder
|
||||||
docs:
|
docs:
|
||||||
- https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefender::Scan_PurgeItemsAfterDelay
|
- https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefender::Scan_PurgeItemsAfterDelay
|
||||||
# Managing with MpPreference module:
|
# Managing with MpPreference module:
|
||||||
@@ -4504,11 +4723,6 @@ actions:
|
|||||||
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefenderSecurityCenter::DeviceSecurity_DisableTpmFirmwareUpdateWarning
|
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefenderSecurityCenter::DeviceSecurity_DisableTpmFirmwareUpdateWarning
|
||||||
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender Security Center\Device security" /v "DisableTpmFirmwareUpdateWarning" /t REG_DWORD /d "1" /f
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender Security Center\Device security" /v "DisableTpmFirmwareUpdateWarning" /t REG_DWORD /d "1" /f
|
||||||
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender Security Center\Device security" /v "DisableTpmFirmwareUpdateWarning" /f 2>nul
|
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender Security Center\Device security" /v "DisableTpmFirmwareUpdateWarning" /f 2>nul
|
||||||
-
|
|
||||||
name: Hide the "Firewall and network protection" area
|
|
||||||
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefenderSecurityCenter::FirewallNetworkProtection_UILockdown
|
|
||||||
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender Security Center\Firewall and network protection" /v "UILockdown" /t REG_DWORD /d "1" /f
|
|
||||||
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender Security Center\Firewall and network protection" /v "UILockdown" /f 2>nul
|
|
||||||
-
|
-
|
||||||
category: Hide Windows Defender notifications
|
category: Hide Windows Defender notifications
|
||||||
children:
|
children:
|
||||||
@@ -4609,43 +4823,6 @@ actions:
|
|||||||
# 1. Some cannot be disabled (access error) normally but only with DisableServiceInRegistry
|
# 1. Some cannot be disabled (access error) normally but only with DisableServiceInRegistry
|
||||||
# 2. Some cannot be disabled even using DisableServiceInRegistry, must be disabled as TrustedInstaller using RunInlineCodeAsTrustedInstaller
|
# 2. Some cannot be disabled even using DisableServiceInRegistry, must be disabled as TrustedInstaller using RunInlineCodeAsTrustedInstaller
|
||||||
children:
|
children:
|
||||||
-
|
|
||||||
name: Disable Windows Defender Firewall service (breaks Microsoft Store and `netsh advfirewall` CLI)
|
|
||||||
docs:
|
|
||||||
- http://batcmd.com/windows/10/services/mpssvc/
|
|
||||||
- https://en.wikipedia.org/wiki/Windows_Firewall
|
|
||||||
# More information about MpsSvc:
|
|
||||||
- https://web.archive.org/web/20110203202612/http://technet.microsoft.com/en-us/library/dd364391(v=WS.10).aspx
|
|
||||||
# More information about boot time protection and stopping the firewall service:
|
|
||||||
- https://web.archive.org/web/20110131034058/http://blogs.technet.com:80/b/networking/archive/2009/03/24/stopping-the-windows-authenticating-firewall-service-and-the-boot-time-policy.aspx
|
|
||||||
# Stopping the service associated with Windows Firewall is not supported by Microsoft:
|
|
||||||
- https://web.archive.org/web/20121106033255/http://technet.microsoft.com/en-us/library/cc753180.aspx
|
|
||||||
# ❗️ Breaks Microsoft Store
|
|
||||||
# Can no longer update nor install apps, they both fail with 0x80073D0A
|
|
||||||
# Also breaks some of Store apps such as Photos:
|
|
||||||
# - https://answers.microsoft.com/en-us/windows/forum/all/microsoft-store-windows-defender-windows-firewall/f2f68cd7-64ec-4fe1-ade4-9d12cde057f9
|
|
||||||
# - https://github.com/undergroundwires/privacy.sexy/issues/104#issuecomment-962651791
|
|
||||||
# > The MpsSvc service host much more functionality than just windows firewall. For instance, Windows
|
|
||||||
# Service hardening which is a windows protection of system services. It also host network isolatio
|
|
||||||
# which is a crucial part of the confidence model for Windows Store based applications. 3rd party firewalls
|
|
||||||
# know this fact and instead of disabling the firewall service they coordinate through public APIs with Windows
|
|
||||||
# Firewall so that they can have ownership of the firewall policies of the computer. Hence you do not have to do
|
|
||||||
# anything special once you install a 3rd party security product.
|
|
||||||
# Source: https://www.walkernews.net/2012/09/23/how-to-fix-windows-store-app-update-error-code-0x80073d0a/
|
|
||||||
# ❗️ Breaks: `netsh advfirewall set`
|
|
||||||
# Disabling and stopping it breaks "netsh advfirewall set" commands such as
|
|
||||||
# `netsh advfirewall set allprofiles state on`, `netsh advfirewall set allprofiles state off`.
|
|
||||||
# More about `netsh firewall` context: https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/netsh-advfirewall-firewall-control-firewall-behavior
|
|
||||||
call:
|
|
||||||
-
|
|
||||||
function: DisableServiceInRegistry # We must disable it on registry level, "Access is denied" for sc config
|
|
||||||
parameters:
|
|
||||||
serviceName: MpsSvc # Check: (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\MpsSvc").Start
|
|
||||||
defaultStartupMode: Automatic # Alowed values: Boot | System | Automatic | Manual
|
|
||||||
-
|
|
||||||
function: RenameSystemFile
|
|
||||||
parameters:
|
|
||||||
filePath: '%WinDir%\system32\mpssvc.dll'
|
|
||||||
-
|
-
|
||||||
name: Disable Windows Defender Antivirus service
|
name: Disable Windows Defender Antivirus service
|
||||||
# ❗️ Breaks `Set-MpPreference` PowerShell cmdlet that helps to manage Defender
|
# ❗️ Breaks `Set-MpPreference` PowerShell cmdlet that helps to manage Defender
|
||||||
@@ -4657,8 +4834,8 @@ actions:
|
|||||||
-
|
-
|
||||||
function: RunInlineCodeAsTrustedInstaller
|
function: RunInlineCodeAsTrustedInstaller
|
||||||
parameters:
|
parameters:
|
||||||
code: sc stop "WinDefend" >nul & sc config "WinDefend" start=disabled
|
code: sc stop "WinDefend" >nul 2>&1 & reg add "HKLM\SYSTEM\CurrentControlSet\Services\WinDefend" /v "Start" /t REG_DWORD /d "4" /f
|
||||||
revertCode: sc config "WinDefend" start=auto & sc start "WinDefend" >nul
|
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\WinDefend" /v "Start" /t REG_DWORD /d "2" /f & sc start "WinDefend" >nul 2>&1
|
||||||
# - # "Access is denied" when renaming file
|
# - # "Access is denied" when renaming file
|
||||||
# function: RenameSystemFile
|
# function: RenameSystemFile
|
||||||
# parameters:
|
# parameters:
|
||||||
@@ -4666,24 +4843,6 @@ actions:
|
|||||||
-
|
-
|
||||||
category: Disable kernel-level Windows Defender drivers
|
category: Disable kernel-level Windows Defender drivers
|
||||||
children:
|
children:
|
||||||
-
|
|
||||||
name: Disable Windows Defender Firewall Authorization Driver service (breaks `netsh advfirewall` CLI)
|
|
||||||
docs:
|
|
||||||
- http://batcmd.com/windows/10/services/mpsdrv/
|
|
||||||
# ❗️ Breaks: `netsh advfirewall set`
|
|
||||||
# Disabling and stopping it breaks "netsh advfirewall set" commands such as
|
|
||||||
# `netsh advfirewall set allprofiles state on`, `netsh advfirewall set allprofiles state off`.
|
|
||||||
# More about `netsh firewall` context: https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/netsh-advfirewall-firewall-control-firewall-behavior
|
|
||||||
call:
|
|
||||||
-
|
|
||||||
function: DisableServiceInRegistry # We must disable it on registry level, "Access is denied" for sc config
|
|
||||||
parameters:
|
|
||||||
serviceName: mpsdrv # Check: (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\mpsdrv").Start
|
|
||||||
defaultStartupMode: Manual # Alowed values: Boot | System | Automatic | Manual
|
|
||||||
-
|
|
||||||
function: RenameSystemFile
|
|
||||||
parameters:
|
|
||||||
filePath: '%SystemRoot%\System32\drivers\mpsdrv.sys'
|
|
||||||
# - Skipping wdnsfltr "Windows Defender Network Stream Filter Driver" as it's Windows 1709 only
|
# - Skipping wdnsfltr "Windows Defender Network Stream Filter Driver" as it's Windows 1709 only
|
||||||
-
|
-
|
||||||
name: Disable Microsoft Defender Antivirus Network Inspection System Driver service
|
name: Disable Microsoft Defender Antivirus Network Inspection System Driver service
|
||||||
@@ -4693,8 +4852,8 @@ actions:
|
|||||||
function: RunInlineCodeAsTrustedInstaller
|
function: RunInlineCodeAsTrustedInstaller
|
||||||
parameters:
|
parameters:
|
||||||
# "net stop" is used to stop dependend services as well, "sc stop" fails
|
# "net stop" is used to stop dependend services as well, "sc stop" fails
|
||||||
code: net stop "WdNisDrv" /yes >nul & sc config "WdNisDrv" start=disabled
|
code: net stop "WdNisDrv" /yes >nul & reg add "HKLM\SYSTEM\CurrentControlSet\Services\WdNisDrv" /v "Start" /t REG_DWORD /d "4" /f
|
||||||
revertCode: sc config "WdNisDrv" start=demand & sc start "WdNisDrv" >nul
|
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\WdNisDrv" /v "Start" /t REG_DWORD /d "3" /f & sc start "WdNisDrv" >nul
|
||||||
-
|
-
|
||||||
function: RenameSystemFile
|
function: RenameSystemFile
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4712,8 +4871,8 @@ actions:
|
|||||||
-
|
-
|
||||||
function: RunInlineCodeAsTrustedInstaller
|
function: RunInlineCodeAsTrustedInstaller
|
||||||
parameters:
|
parameters:
|
||||||
code: sc stop "WdFilter" >nul & sc config "WdFilter" start=disabled
|
code: sc stop "WdFilter" >nul & reg add "HKLM\SYSTEM\CurrentControlSet\Services\WdFilter" /v "Start" /t REG_DWORD /d "4" /f
|
||||||
revertCode: sc config "WdFilter" start=boot & sc start "WdFilter" >nul
|
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\WdFilter" /v "Start" /t REG_DWORD /d "0" /f & sc start "WdFilter" >nul
|
||||||
-
|
-
|
||||||
function: RenameSystemFile
|
function: RenameSystemFile
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4729,8 +4888,8 @@ actions:
|
|||||||
-
|
-
|
||||||
function: RunInlineCodeAsTrustedInstaller
|
function: RunInlineCodeAsTrustedInstaller
|
||||||
parameters:
|
parameters:
|
||||||
code: sc stop "WdBoot" >nul & sc config "WdBoot" start=disabled
|
code: sc stop "WdBoot" >nul 2>&1 & reg add "HKLM\SYSTEM\CurrentControlSet\Services\WdBoot" /v "Start" /t REG_DWORD /d "4" /f
|
||||||
revertCode: sc config "WdBoot" start=boot & sc start "WdBoot" >nul
|
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\WdBoot" /v "Start" /t REG_DWORD /d "0" /f & sc start "WdBoot" >nul 2>&1
|
||||||
-
|
-
|
||||||
function: RenameSystemFile
|
function: RenameSystemFile
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4748,8 +4907,8 @@ actions:
|
|||||||
-
|
-
|
||||||
function: RunInlineCodeAsTrustedInstaller
|
function: RunInlineCodeAsTrustedInstaller
|
||||||
parameters:
|
parameters:
|
||||||
code: sc stop "WdNisSvc" >nul & sc config "WdNisSvc" start=disabled
|
code: sc stop "WdNisSvc" >nul 2>&1 & reg add "HKLM\SYSTEM\CurrentControlSet\Services\WdNisSvc" /v "Start" /t REG_DWORD /d "4" /f
|
||||||
revertCode: sc config "WdNisSvc" start=auto & sc start "WdNisSvc" >nul
|
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\WdNisSvc" /v "Start" /t REG_DWORD /d "2" /f & sc start "WdNisSvc" >nul 2>&1
|
||||||
# - # "Access is denied" when renaming file
|
# - # "Access is denied" when renaming file
|
||||||
# function: RenameSystemFile
|
# function: RenameSystemFile
|
||||||
# parameters:
|
# parameters:
|
||||||
@@ -4759,10 +4918,10 @@ actions:
|
|||||||
docs: http://batcmd.com/windows/10/services/sense/
|
docs: http://batcmd.com/windows/10/services/sense/
|
||||||
call:
|
call:
|
||||||
-
|
-
|
||||||
function: DisableServiceInRegistry # We must disable it on registry level, "Access is denied" for sc config
|
function: RunInlineCodeAsTrustedInstaller # We must disable it on registry level, "Access is denied" for sc config
|
||||||
parameters:
|
parameters:
|
||||||
serviceName: Sense # Check: (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Sense").Start
|
code: sc stop "Sense" >nul 2>&1 & reg add "HKLM\SYSTEM\CurrentControlSet\Services\Sense" /v "Start" /t REG_DWORD /d "4" /f
|
||||||
defaultStartupMode: Manual # Alowed values: Boot | System | Automatic | Manual
|
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\Sense" /v "Start" /t REG_DWORD /d "3" /f & sc start "Sense" >nul 2>&1 # Alowed values: Boot | System | Automatic | Manual
|
||||||
-
|
-
|
||||||
function: RenameSystemFile
|
function: RenameSystemFile
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4782,8 +4941,8 @@ actions:
|
|||||||
# ✅ Can disable using registry as TrustedInstaller
|
# ✅ Can disable using registry as TrustedInstaller
|
||||||
function: RunInlineCodeAsTrustedInstaller
|
function: RunInlineCodeAsTrustedInstaller
|
||||||
parameters:
|
parameters:
|
||||||
code: reg add "HKLM\SYSTEM\CurrentControlSet\Services\SecurityHealthService" /v Start /t REG_DWORD /d 4 /f
|
code: sc stop "SecurityHealthService" >nul 2>&1 & reg add "HKLM\SYSTEM\CurrentControlSet\Services\SecurityHealthService" /v Start /t REG_DWORD /d 4 /f
|
||||||
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\SecurityHealthService" /v Start /t REG_DWORD /d 3 /f
|
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\SecurityHealthService" /v Start /t REG_DWORD /d 3 /f & sc start "SecurityHealthService" >nul 2>&1
|
||||||
-
|
-
|
||||||
function: RenameSystemFile
|
function: RenameSystemFile
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4884,29 +5043,233 @@ actions:
|
|||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /t REG_DWORD /d "1" /f
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /t REG_DWORD /d "1" /f
|
||||||
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /f 2>nul
|
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Disable automatic updates
|
category: Disable automatic updates
|
||||||
docs:
|
docs: |-
|
||||||
- https://docs.microsoft.com/fr-fr/security-updates/windowsupdateservices/18127152
|
Disabling automatic updates is often considered counterintuitive when it comes to securing your system. However, there are substantial arguments
|
||||||
- http://batcmd.com/windows/10/services/usosvc/
|
to consider this option if you're privacy-centric:
|
||||||
call:
|
|
||||||
|
1. **Patching and Pre-Approval**: Manual control over update deployment allows for pre-emptive approval of patches. This strategy is useful
|
||||||
|
in environments requiring the highest level of security. For instance, military agencies frequently employ air-gapped systems that mandate
|
||||||
|
careful review of each update to mitigate risks such as potential backdoors or data leaks. Similarly, financial institutions often
|
||||||
|
resort to staged rollouts of updates, subjecting them to an in-depth analysis of their implications on security and privacy before broad
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
2. **Telemetry and Data Transmission**: Automatic updates often come embedded with telemetry data collection mechanisms. Disabling these
|
||||||
|
updates facilitates granular control over the data transmitted back to Microsoft servers. Thus, the decision to disable automatic updates
|
||||||
|
allows you to control the timing and nature of information relayed to these servers.
|
||||||
|
|
||||||
|
3. **Peer-to-Peer Data Exposure**: Windows employs a Peer-to-Peer (P2P) approach to facilitate update distribution, which can
|
||||||
|
reveal your IP address and some system details to peer systems [1].
|
||||||
|
|
||||||
|
4. **Configurational integrity**: Updates have the capacity to change pre-configured settings without explicit user consent. This could
|
||||||
|
result in unintended alteration of your privacy settings, leaving you exposed until you realize the change.
|
||||||
|
|
||||||
|
**Security implications**: While controlling updates enhances your privacy, it can leave your system vulnerable to unpatched exploits.
|
||||||
|
Ensure that you manually review and apply updates on a regular basis. You're essentially trading off some security for a heightened level of
|
||||||
|
privacy.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230905120220/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-faq "Delivery Optimization Frequently Asked Questions - Windows Deployment | Microsoft Learn"
|
||||||
|
children:
|
||||||
-
|
-
|
||||||
function: RunInlineCode
|
name: Disable Automatic Updates (AU) feature
|
||||||
parameters:
|
docs: |-
|
||||||
code: |-
|
This script deactivates the Automatic Updates feature in Windows. By disabling Automatic Updates,
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "0" /f
|
you gain control over when your system is updated, which may be preferable in specific
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "2" /f
|
privacy-sensitive environments.
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /t "REG_DWORD" /d "0" /f
|
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /t "REG_DWORD" /d "3" /f
|
The script changes a specific setting in your computer's registry, with a key called `NoAutoUpdate`, which has
|
||||||
revertCode: |-
|
two possible states [1] [2]:
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "1" /f
|
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "3" /f
|
- `0`: Automatic Updates are enabled.
|
||||||
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /f 2>nul
|
- `1`: Automatic Updates are disabled.
|
||||||
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime /f 2>nul
|
|
||||||
|
By default, Windows comes with Automatic Updates enabled, meaning the `NoAutoUpdate` is set to `0` [3].
|
||||||
|
|
||||||
|
Running this script will set `NoAutoUpdate` to `1`, turning off Automatic Updates [1] [2] [3].
|
||||||
|
In doing so, you prevent your computer from automatically receiving updates, which is a feature
|
||||||
|
that could be considered intrusive or unwanted in some privacy-conscious settings.
|
||||||
|
|
||||||
|
It configure your computer to not automatically download and install updates without your explicit permission.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230807165936/https://learn.microsoft.com/de-de/security-updates/windowsupdateservices/18127499 "Configure Automatic Updates in a Non–Active Directory Environment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20221001051250/https://support.microsoft.com/en-us/topic/incorrect-automatic-updates-notification-is-received-even-though-au-options-are-disabled-in-windows-8-1-and-windows-server-2012-r2-18b4b73a-3910-9408-809c-7eaad0e1fbc7 "Incorrect Automatic Updates notification is received even though AU options are disabled in Windows 8.1 and Windows Server 2012 R2 - Microsoft Support"
|
||||||
|
[3]: https://web.archive.org/web/20230711172555/https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry "Manage additional Windows Update settings - Windows Deployment | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: RunInlineCode
|
||||||
|
parameters:
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "1" /f
|
||||||
|
# Default value is `0` since Windows 10 21H2 and Windows 11 21H2
|
||||||
|
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "0" /f
|
||||||
-
|
-
|
||||||
function: DisableService
|
name: Disable installing Windows updates without user approval
|
||||||
parameters:
|
docs: |-
|
||||||
serviceName: UsoSvc # Check: (Get-Service -Name 'UsoSvc').StartType
|
This script changes how your Windows computer handles automatic updates by modifying the `AUOptions` registry key.
|
||||||
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
|
After running this script, your computer will notify you before downloading any updates [1] [2] [3].
|
||||||
|
|
||||||
|
In the default setup, your Windows system is configured to download and install updates automatically without notifying you [4].
|
||||||
|
This means that new updates could be installed on your system without your explicit approval.
|
||||||
|
|
||||||
|
By forcing Windows to notify you before downloading updates, this script hands back control over your system to you.
|
||||||
|
This feature enhances your privacy and minimizes risks because you get to manually review and approve each update before it's installed.
|
||||||
|
|
||||||
|
To explain the technical aspect, the `AUOptions` registry key is a setting stored under
|
||||||
|
`HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU` in your computer's registry [1] [3].
|
||||||
|
A value of `2` for `AUOptions` means that you will be notified before any updates are downloaded and installed [1] [2].
|
||||||
|
On older versions of Windows, setting this key to `1` would prevent the system from even checking for updates [5].
|
||||||
|
However, starting from Windows 10, the key `1` has a different meaning [2][3].
|
||||||
|
|
||||||
|
Running this script doesn't disable updates; it just ensures that you are informed and have the final say on
|
||||||
|
whether to download them or not.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230807165936/https://learn.microsoft.com/de-de/security-updates/windowsupdateservices/18127499 "Configure Automatic Updates in a Non–Active Directory Environment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230711172555/https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry "Manage additional Windows Update settings - Windows Deployment | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230815051303/https://learn.microsoft.com/en-us/windows/deployment/update/waas-restart#registry-keys-used-to-manage-restart "Manage device restarts after updates - Windows Deployment | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230826081345/https://learn.microsoft.com/en-US/troubleshoot/windows-client/deployment/update-windows-update-agent "Update Windows Update Agent to latest version - Windows Client | Microsoft Learn"
|
||||||
|
[5]: https://web.archive.org/web/20221001051250/https://support.microsoft.com/en-us/topic/incorrect-automatic-updates-notification-is-received-even-though-au-options-are-disabled-in-windows-8-1-and-windows-server-2012-r2-18b4b73a-3910-9408-809c-7eaad0e1fbc7 "Incorrect Automatic Updates notification is received even though AU options are disabled in Windows 8.1 and Windows Server 2012 R2 - Microsoft Support"
|
||||||
|
call:
|
||||||
|
function: RunInlineCode
|
||||||
|
parameters:
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "2" /f
|
||||||
|
# Default value is `4` since Windows 10 21H2 and Windows 11 21H2
|
||||||
|
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "4" /f
|
||||||
|
-
|
||||||
|
name: Disable automatic daily installation of Windows updates
|
||||||
|
docs: |-
|
||||||
|
This script stops Windows from automatically installing updates every day. By doing so, you gain control over when update
|
||||||
|
happen on your computer [1] [2].
|
||||||
|
|
||||||
|
By default, Windows is set to automatically update every day [2]. Having control over the update timing allows you to review
|
||||||
|
what is being changed, thereby protecting your privacy and enhancing your system's security.
|
||||||
|
|
||||||
|
Technically, what the script does is remove a specific setting in the computer's system registry, the `ScheduledInstallDay` key
|
||||||
|
from `HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU` [1] [2].
|
||||||
|
|
||||||
|
Disabling the scheduled install day ensures that updates won't be forcibly applied on a specific day of the week.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230711172555/https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry "Manage additional Windows Update settings - Windows Deployment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230708165017/https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-update#scheduledinstallday "Update Policy CSP - Windows Client Management | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: RunInlineCode
|
||||||
|
parameters:
|
||||||
|
code: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /f 2>nul
|
||||||
|
revertCode: >-
|
||||||
|
:: This key does not exist by default since Windows 10 21H2 and Windows 11 21H2
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /f 2>nul
|
||||||
|
-
|
||||||
|
name: Disable scheduled automatic updates
|
||||||
|
docs: |-
|
||||||
|
This script turns off the automatic installation of Windows updates that are set to occur at a specific time.
|
||||||
|
By doing this, you take back control over when your computer updates itself [1] [2] [3].
|
||||||
|
The default behavior is to install updates at 3 AM [3].
|
||||||
|
|
||||||
|
Windows updates can be important for system security, but automatic installation could occur at inconvenient times and may even
|
||||||
|
restart your computer without prior warning. This could interrupt your tasks and may send data about your system to external servers.
|
||||||
|
By disabling the automatic scheduled installation time, you can manually control when updates are installed [3], ensuring that you're
|
||||||
|
aware of any changes to your system.
|
||||||
|
|
||||||
|
The script works by removing a specific registry key called `ScheduledInstallTime` under
|
||||||
|
`HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU` [2] [3]. This is the system setting that controls the scheduled update time.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230813094618/https://learn.microsoft.com/fr-fr/security-updates/windowsupdateservices/18127152 "Configure Automatic Updates in a Non–Active Directory Environment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230711172555/https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry "Manage additional Windows Update settings - Windows Deployment | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230708165017/https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-update#scheduledinstalltime "Update Policy CSP - Windows Client Management | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: RunInlineCode
|
||||||
|
parameters:
|
||||||
|
code: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /f 2>nul
|
||||||
|
revertCode: >-
|
||||||
|
:: This key does not exist by default since Windows 10 21H2 and Windows 11 21H2
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /f 2>nul
|
||||||
|
-
|
||||||
|
category: Disable Windows update services
|
||||||
|
docs: |-
|
||||||
|
The scripts in this category offer users the ability to control Windows services related to system updates.
|
||||||
|
These services manage how and when your system receives updates from Microsoft. By limiting or disabling these services,
|
||||||
|
users can decide when to update their system, reducing unexpected changes. Moreover, a system with fewer running
|
||||||
|
services uses fewer resources, which can improve overall performance.
|
||||||
|
|
||||||
|
Disabling these update services is also a privacy measure. Some updates can change privacy settings or add features that
|
||||||
|
collect user data. By controlling update services, users can review and approve any changes before they take effect.
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable "Windows Update" (`wuauserv`) service
|
||||||
|
docs: |-
|
||||||
|
This script turns off the Windows Update service, which is technically known as Windows Update Agent [1] [2].
|
||||||
|
By disabling this service, the automatic detection, download, and installation of updates for both Windows and other
|
||||||
|
installed programs are halted [3] [4].
|
||||||
|
|
||||||
|
Update can often come bundled with changes that could affect your privacy settings or introduce features that collect
|
||||||
|
more of your data. Taking control of when and how updates are applied provides you with the opportunity to review any changes
|
||||||
|
before they take effect.
|
||||||
|
|
||||||
|
By default, the service is enabled and set to start up manually [5].
|
||||||
|
|
||||||
|
If you disable this service, you won't be able to use the Windows Update feature for automatic updates [5]. Additionally,
|
||||||
|
other software on your c omputer won't be able to access the functionalities provided by the Windows Update Agent,
|
||||||
|
commonly known as WUA API [5].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230902020255/https://learn.microsoft.com/en-us/troubleshoot/windows-client/deployment/additional-resources-for-windows-update "Additional resources for Windows Update - Windows Client | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230711221240/https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/update-management/troubleshoot-software-update-scan-failures "Troubleshoot software update scan failures - Configuration Manager | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230905120348/https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/windows-devices-fail-boot-after-installing-kb4041676-kb4041691 "Windows devices may fail to boot after installing October 10 version of KB 4041676 or 4041691 that contained a publishing issue - Windows Client | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230905120345/https://learn.microsoft.com/en-us/windows-server/administration/server-core/server-core-servicing "Patching Server Core | Microsoft Learn"
|
||||||
|
[5]: https://web.archive.org/web/20230905120445/https://learn.microsoft.com/en-us/windows/deployment/update/prepare-deploy-windows "Security guidelines for system services in Windows Server 2016 | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: DisableService
|
||||||
|
parameters:
|
||||||
|
serviceName: wuauserv # Check: (Get-Service -Name 'wuauserv').StartType
|
||||||
|
defaultStartupMode: Manual # Allowed values: Automatic | Manual
|
||||||
|
-
|
||||||
|
name: Disable "Update Orchestrator Service" (`UsoSvc`)
|
||||||
|
docs: |-
|
||||||
|
This script disables the Update Orchestrator Service, also known as "Update Orchestrator Service for Windows Update" [1].
|
||||||
|
This service is in charge of managing the download and installation of Windows updates [1] [2].
|
||||||
|
|
||||||
|
By default, the service is enabled and set to start up manually [1].
|
||||||
|
|
||||||
|
While updates can be crucial for the security of your system, this service can sometimes install them without your approval.
|
||||||
|
This lack of control can pose risks to your privacy, as data might be sent from your system without your knowledge.
|
||||||
|
|
||||||
|
Windows updates relies on this service [1] [3].
|
||||||
|
If stopped, your devices will not be able to download and install latest updates [1].
|
||||||
|
|
||||||
|
Turning off this service can affect the update process and might cause issues like freezing during update scanning [3].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230905120757/https://learn.microsoft.com/en-us/windows-server/security/windows-services/security-guidelines-for-disabling-system-services-in-windows-server "Security guidelines for system services in Windows Server 2016 | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230905120348/https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/windows-devices-fail-boot-after-installing-kb4041676-kb4041691 "Windows devices may fail to boot after installing October 10 version of KB 4041676 or 4041691 that contained a publishing issue - Windows Client | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230905120445/https://learn.microsoft.com/en-us/windows/deployment/update/prepare-deploy-windows "Security guidelines for system services in Windows Server 2016 | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: DisableService
|
||||||
|
parameters:
|
||||||
|
serviceName: UsoSvc # Check: (Get-Service -Name 'UsoSvc').StartType
|
||||||
|
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
|
||||||
|
-
|
||||||
|
name: Disable "Windows Update Medic Service" (`WaaSMedicSvc`)
|
||||||
|
docs: |-
|
||||||
|
This script disables the Windows Update Medic Service. This service runs quietly in the background [1],
|
||||||
|
making sure that parts related to Windows updates are working as they should [1] [2].
|
||||||
|
|
||||||
|
By default, the service is enabled and its startup setting is set to manual [3].
|
||||||
|
|
||||||
|
This service can undo any adjustments you've made to your Windows Update settings without your consent.
|
||||||
|
For example, it can re-enable automatic Windows updates [4].
|
||||||
|
That can interfere if you've tailored these settings for better privacy or security.
|
||||||
|
|
||||||
|
When you disable this service using our script, you're taking back control. You get to choose how your system
|
||||||
|
handles updates and data transfers, ensuring that your privacy settings stay as you intended. This is a reliable
|
||||||
|
way to strengthen both your privacy and your control over your computer.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230905120805/https://support.microsoft.com/en-us/topic/kb5005322-some-devices-cannot-install-new-updates-after-installing-kb5003214-may-25-2021-and-kb5003690-june-21-2021-66edf7cf-5d3c-401f-bd32-49865343144f "KB5005322—Some devices cannot install new updates after installing KB5003214 (May 25, 2021) and KB5003690 (June 21, 2021) - Microsoft Support"
|
||||||
|
[2]: https://web.archive.org/web/20230905120445/https://learn.microsoft.com/en-us/windows/deployment/update/prepare-deploy-windows "Security guidelines for system services in Windows Server 2016 | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230905120815/https://learn.microsoft.com/en-us/windows/iot/iot-enterprise/optimize/services "Guidance on disabling system services on Windows IoT Enterprise | Microsoft Learn"
|
||||||
|
[4]: https://github.com/undergroundwires/privacy.sexy/issues/252
|
||||||
|
call:
|
||||||
|
function: DisableServiceInRegistry
|
||||||
|
# Since Windows 10 21H2 and Windows 11 21H2:
|
||||||
|
# - Using `sc config` resulsts in "Access in denied", so registry should be used to disable the service.
|
||||||
|
# - Default startup mode is Manual
|
||||||
|
parameters:
|
||||||
|
serviceName: WaaSMedicSvc # Check: (Get-Service -Name 'WaaSMedicSvc').StartType
|
||||||
|
defaultStartupMode: Manual # Allowed values: Automatic | Manual
|
||||||
-
|
-
|
||||||
category: Configure handling of downloaded files
|
category: Configure handling of downloaded files
|
||||||
docs: |-
|
docs: |-
|
||||||
@@ -5162,25 +5525,6 @@ actions:
|
|||||||
-
|
-
|
||||||
category: Disable OS services
|
category: Disable OS services
|
||||||
children:
|
children:
|
||||||
-
|
|
||||||
name: Delivery Optimization (P2P Windows Updates)
|
|
||||||
recommend: standard
|
|
||||||
docs:
|
|
||||||
# Delivery Optimization is a cloud-managed solution to offer Windows updates through
|
|
||||||
# other users' network (peer-to-peer).
|
|
||||||
- https://docs.microsoft.com/en-us/windows/deployment/update/waas-delivery-optimization
|
|
||||||
# Delivery Optimization service performs content delivery optimization tasks.
|
|
||||||
- http://batcmd.com/windows/10/services/dosvc/
|
|
||||||
# Connects to various Microsoft service endpoints to get metadata, policies, content, device information
|
|
||||||
# and information of other peers (Windows users).
|
|
||||||
- https://docs.microsoft.com/en-us/windows/deployment/update/delivery-optimization-workflow
|
|
||||||
call:
|
|
||||||
function: DisableServiceInRegistry
|
|
||||||
# Using registry way because because other options such as "sc config" or
|
|
||||||
# "Set-Service" returns "Access is denied" since Windows 10 1809.
|
|
||||||
parameters:
|
|
||||||
serviceName: DoSvc # Check: (Get-Service -Name 'DoSvc').StartType
|
|
||||||
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
|
|
||||||
-
|
-
|
||||||
name: Microsoft Account Sign-in Assistant (breaks Microsoft Store and Microsoft Account sign-in)
|
name: Microsoft Account Sign-in Assistant (breaks Microsoft Store and Microsoft Account sign-in)
|
||||||
recommend: strict
|
recommend: strict
|
||||||
@@ -6606,16 +6950,75 @@ actions:
|
|||||||
code: reg delete "HKCU\Environment" /v "OneDrive" /f 2>nul
|
code: reg delete "HKCU\Environment" /v "OneDrive" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Uninstall Edge (chromium-based)
|
name: Uninstall Edge (chromium-based)
|
||||||
|
docs: |-
|
||||||
|
This script automates the uninstallation of Microsoft Edge (also known as "Chromium Edge" or "New Edge" [1]), the web browser that comes
|
||||||
|
pre-installed with many versions of Windows.
|
||||||
|
|
||||||
|
Microsoft Edge collects various types of data, some of which pertain to your browsing habits, such as the websites you visit, your search
|
||||||
|
queries, and the data you enter into forms [2]. Additionally, it tracks usage metrics and diagnostic data about your device data and
|
||||||
|
how the browser is functioning [2]. These pieces of information could be used for targeted advertising or profiling. Removing Microsoft
|
||||||
|
Edge ensures that it is not silently accumulating this data in the background, thereby improving your overall privacy.
|
||||||
|
|
||||||
|
By default, Microsoft Edge doesn't allow easy uninstallation and has officially declared Microsoft Edge as uninstallable on Windows [3].
|
||||||
|
|
||||||
|
This scripts uses two steps to achieve this:
|
||||||
|
|
||||||
|
1. **Enable Uninstallation**: The script modifies a specific registry key to allow the uninstallation of Microsoft Edge. This step is crucial
|
||||||
|
because, starting from version 116 of Edge, you cannot uninstall it unless this registry key is set.
|
||||||
|
2. **Run Uninstaller**: The script then finds the Microsoft Edge installer (`setup.exe`) for every Microsoft Edge installation (it is possible
|
||||||
|
to have multiple versions) and executes it to perform a system-level uninstall.
|
||||||
|
|
||||||
|
There's no official documentation for the Edge installer or registry keys codes, which this script relies on. However, these have been verified
|
||||||
|
through testing and community support to work as expected.
|
||||||
|
|
||||||
|
[1]: https://en.wikipedia.org/w/index.php?title=Microsoft_Edge&oldid=1174053020#New_Edge_(2019%E2%80%93present) "Microsoft Edge - Wikipedia"
|
||||||
|
[2]: https://web.archive.org/web/20230907002709/https://support.microsoft.com/en-us/microsoft-edge/learn-more-about-diagnostic-data-collection-in-microsoft-edge-7fcee15b-39f7-ba02-bc59-9eef622c1a9f "Learn more about diagnostic data collection in Microsoft Edge - Microsoft Support"
|
||||||
|
[3]: https://web.archive.org/web/20230907002011/https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d "Why can't I uninstall Microsoft Edge? - Microsoft Support"
|
||||||
call:
|
call:
|
||||||
function: RunPowerShell
|
-
|
||||||
parameters:
|
function: RunInlineCode
|
||||||
code: |-
|
parameters:
|
||||||
$installer = (Get-ChildItem "$env:ProgramFiles*\Microsoft\Edge\Application\*\Installer\setup.exe")
|
code: reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdateDev" /v "AllowUninstall" /t REG_DWORD /d "1" /f
|
||||||
if (!$installer) {
|
revertCode: reg delete "HKLM\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdateDev" /v "AllowUninstall" /f 2>nul # It does not exists since Windows 10 21H2 and Windows 11 21H2
|
||||||
Write-Host 'Could not find the installer'
|
-
|
||||||
} else {
|
function: RunPowerShell
|
||||||
& $installer.FullName -Uninstall -System-Level -Verbose-Logging -Force-Uninstall
|
parameters:
|
||||||
}
|
code: |-
|
||||||
|
$installer = (Get-ChildItem "$($env:ProgramFiles)*\Microsoft\Edge\Application\*\Installer\setup.exe")
|
||||||
|
if (!$installer) {
|
||||||
|
Write-Host 'Installer not found. Microsoft Edge may already be uninstalled.'
|
||||||
|
} else {
|
||||||
|
$installer | ForEach-Object {
|
||||||
|
$uninstallerPath = $_.FullName
|
||||||
|
$installerArguments = @("--uninstall", "--system-level", "--verbose-logging", "--force-uninstall")
|
||||||
|
Write-Output "Uninstalling through uninstaller: $uninstallerPath"
|
||||||
|
$process = Start-Process -FilePath "$uninstallerPath" -ArgumentList $installerArguments -Wait -PassThru
|
||||||
|
if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 19) {
|
||||||
|
Write-Host "Successfully uninstalled Edge."
|
||||||
|
} else {
|
||||||
|
Write-Error "Failed to uninstall, uninstaller failed with exit code $($process.ExitCode)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
revertCode: |-
|
||||||
|
$edgeExePath = Get-ChildItem -Path "$($env:ProgramFiles)*\Microsoft\Edge\Application" -Filter 'msedge.exe' -Recurse
|
||||||
|
if ($edgeExePath) {
|
||||||
|
Write-Host 'Microsoft Edge is already installed. Skipping reinstallation.'
|
||||||
|
Exit 0
|
||||||
|
}
|
||||||
|
Write-Host 'Downloading Microsoft Edge...'
|
||||||
|
$edgeInstallerUrl = 'https://c2rsetup.officeapps.live.com/c2r/downloadEdge.aspx?platform=Default&Channel=Stable&language=en'
|
||||||
|
$downloadPath = "$($env:TEMP)\MicrosoftEdgeSetup.exe"
|
||||||
|
Invoke-WebRequest -Uri "$edgeInstallerUrl" -OutFile "$downloadPath"
|
||||||
|
$installerArguments = @('/install', '/silent')
|
||||||
|
Write-Host 'Installing Microsoft Edge...'
|
||||||
|
$process = Start-Process -FilePath "$downloadPath" -ArgumentList "$installerArguments" -Wait -PassThru
|
||||||
|
Remove-Item -Path $downloadPath -Force
|
||||||
|
if ($process.ExitCode -eq 0) {
|
||||||
|
Write-Host 'Successfully reinstalled Microsoft Edge.'
|
||||||
|
} else {
|
||||||
|
Write-Error "Failed to reinstall Microsoft Edge. Installer failed with exit code $($process.ExitCode)."
|
||||||
|
}
|
||||||
-
|
-
|
||||||
category: Disable built-in Windows features
|
category: Disable built-in Windows features
|
||||||
children:
|
children:
|
||||||
@@ -7359,44 +7762,10 @@ functions:
|
|||||||
call:
|
call:
|
||||||
function: RunPowerShell
|
function: RunPowerShell
|
||||||
parameters:
|
parameters:
|
||||||
code: |-
|
code: Get-WindowsCapability -Online -Name '{{ $capabilityName }}*' | Remove-WindowsCapability -Online
|
||||||
$capabilityName = '{{ $capabilityName }}'
|
|
||||||
try {
|
|
||||||
# Using wildcard for version number handling
|
|
||||||
$capability = Get-WindowsCapability -Online -Name "$capabilityName*"
|
|
||||||
if (!$capability) {
|
|
||||||
Write-Host "Skipping. Capability `"$capabilityName`" is missing."
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
if ($capability.State -eq 'NotPresent') {
|
|
||||||
Write-Host "Skipping. Capability `"$capabilityName`" is missing."
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
Write-Host "Removing capability `"$capabilityName`""
|
|
||||||
Remove-WindowsCapability -Online -Name "$($capability.Name)" -ErrorAction Stop
|
|
||||||
Write-Host "Successfully removed `"$CapabilityName`""
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Error "Failed to remove `"$capabilityName`": $_"
|
|
||||||
}
|
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
$capabilityName = '{{ $capabilityName }}'
|
$capability = Get-WindowsCapability -Online -Name '{{ $capabilityName }}*'
|
||||||
try {
|
Add-WindowsCapability -Name "$capability.Name" -Online
|
||||||
# Using wildcard for version number handling
|
|
||||||
$capability = Get-WindowsCapability -Online -Name "$capabilityName*"
|
|
||||||
if (!$capability) {
|
|
||||||
Write-Error "Failed to find `"$capabilityName`"."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
$result = Add-WindowsCapability -Name $capability.Name -Online -ErrorAction Stop
|
|
||||||
Write-Host "Successfully added `"$capabilityName`"."
|
|
||||||
if ($result.RestartNeeded -eq 'Yes') {
|
|
||||||
Write-Warning "A restart is needed to finish installing `"$capabilityName`"."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Error "Failed to add `"$capabilityName`": $_"
|
|
||||||
}
|
|
||||||
-
|
-
|
||||||
name: RenameSystemFile
|
name: RenameSystemFile
|
||||||
parameters:
|
parameters:
|
||||||
@@ -7500,6 +7869,7 @@ functions:
|
|||||||
parameters:
|
parameters:
|
||||||
- name: code
|
- name: code
|
||||||
- name: revertCode
|
- name: revertCode
|
||||||
|
optional: true
|
||||||
call:
|
call:
|
||||||
function: RunPowerShell
|
function: RunPowerShell
|
||||||
parameters:
|
parameters:
|
||||||
@@ -7567,7 +7937,8 @@ functions:
|
|||||||
Remove-Item $streamOutFile, $batchFile
|
Remove-Item $streamOutFile, $batchFile
|
||||||
}
|
}
|
||||||
revertCode: |- # Duplicated until custom pipes are implemented
|
revertCode: |- # Duplicated until custom pipes are implemented
|
||||||
$command = '{{ $revertCode }}'
|
{{ with $revertCode }}
|
||||||
|
$command = '{{ . }}'
|
||||||
$trustedInstallerSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464')
|
$trustedInstallerSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464')
|
||||||
$trustedInstallerName = $trustedInstallerSid.Translate([System.Security.Principal.NTAccount])
|
$trustedInstallerName = $trustedInstallerSid.Translate([System.Security.Principal.NTAccount])
|
||||||
$streamOutFile = New-TemporaryFile
|
$streamOutFile = New-TemporaryFile
|
||||||
@@ -7610,6 +7981,7 @@ functions:
|
|||||||
} finally {
|
} finally {
|
||||||
Remove-Item $streamOutFile, $batchFile
|
Remove-Item $streamOutFile, $batchFile
|
||||||
}
|
}
|
||||||
|
{{ end }}
|
||||||
-
|
-
|
||||||
name: DisableServiceInRegistry
|
name: DisableServiceInRegistry
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
@@ -1,31 +1,37 @@
|
|||||||
import os from 'os';
|
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
import child_process from 'child_process';
|
|
||||||
import { Environment } from '@/application/Environment/Environment';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { getWindowInjectedSystemOperations } from './SystemOperations/WindowInjectedSystemOperations';
|
||||||
|
|
||||||
export class CodeRunner {
|
export class CodeRunner {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly node = getNodeJs(),
|
private readonly system = getWindowInjectedSystemOperations(),
|
||||||
private readonly environment = Environment.CurrentEnvironment,
|
private readonly environment = RuntimeEnvironment.CurrentEnvironment,
|
||||||
) {
|
) {
|
||||||
|
if (!system) {
|
||||||
|
throw new Error('missing system operations');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async runCode(code: string, folderName: string, fileExtension: string): Promise<void> {
|
public async runCode(code: string, folderName: string, fileExtension: string): Promise<void> {
|
||||||
const dir = this.node.path.join(this.node.os.tmpdir(), folderName);
|
const { os } = this.environment;
|
||||||
await this.node.fs.promises.mkdir(dir, { recursive: true });
|
const dir = this.system.location.combinePaths(
|
||||||
const filePath = this.node.path.join(dir, `run.${fileExtension}`);
|
this.system.operatingSystem.getTempDirectory(),
|
||||||
await this.node.fs.promises.writeFile(filePath, code);
|
folderName,
|
||||||
await this.node.fs.promises.chmod(filePath, '755');
|
);
|
||||||
const command = getExecuteCommand(filePath, this.environment);
|
await this.system.fileSystem.createDirectory(dir, true);
|
||||||
this.node.child_process.exec(command);
|
const filePath = this.system.location.combinePaths(dir, `run.${fileExtension}`);
|
||||||
|
await this.system.fileSystem.writeToFile(filePath, code);
|
||||||
|
await this.system.fileSystem.setFilePermissions(filePath, '755');
|
||||||
|
const command = getExecuteCommand(filePath, os);
|
||||||
|
this.system.command.execute(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExecuteCommand(scriptPath: string, environment: Environment): string {
|
function getExecuteCommand(
|
||||||
switch (environment.os) {
|
scriptPath: string,
|
||||||
|
currentOperatingSystem: OperatingSystem,
|
||||||
|
): string {
|
||||||
|
switch (currentOperatingSystem) {
|
||||||
case OperatingSystem.Linux:
|
case OperatingSystem.Linux:
|
||||||
return `x-terminal-emulator -e '${scriptPath}'`;
|
return `x-terminal-emulator -e '${scriptPath}'`;
|
||||||
case OperatingSystem.macOS:
|
case OperatingSystem.macOS:
|
||||||
@@ -36,46 +42,6 @@ function getExecuteCommand(scriptPath: string, environment: Environment): string
|
|||||||
case OperatingSystem.Windows:
|
case OperatingSystem.Windows:
|
||||||
return scriptPath;
|
return scriptPath;
|
||||||
default:
|
default:
|
||||||
throw Error(`unsupported os: ${OperatingSystem[environment.os]}`);
|
throw Error(`unsupported os: ${OperatingSystem[currentOperatingSystem]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeJs(): INodeJs {
|
|
||||||
return {
|
|
||||||
os, path, fs, child_process,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface INodeJs {
|
|
||||||
os: INodeOs;
|
|
||||||
path: INodePath;
|
|
||||||
fs: INodeFs;
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
child_process: INodeChildProcess;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface INodeOs {
|
|
||||||
tmpdir(): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface INodePath {
|
|
||||||
join(...paths: string[]): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface INodeChildProcess {
|
|
||||||
exec(command: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface INodeFs {
|
|
||||||
readonly promises: INodeFsPromises;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INodeFsPromisesMakeDirectoryOptions {
|
|
||||||
recursive?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INodeFsPromises { // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v13/fs.d.ts
|
|
||||||
chmod(path: string, mode: string | number): Promise<void>;
|
|
||||||
mkdir(path: string, options: INodeFsPromisesMakeDirectoryOptions): Promise<string>;
|
|
||||||
writeFile(path: string, data: string): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { IEnvironmentVariablesFactory } from './IEnvironmentVariablesFactory';
|
||||||
|
import { validateEnvironmentVariables } from './EnvironmentVariablesValidator';
|
||||||
|
import { ViteEnvironmentVariables } from './Vite/ViteEnvironmentVariables';
|
||||||
|
import { IEnvironmentVariables } from './IEnvironmentVariables';
|
||||||
|
|
||||||
|
export class EnvironmentVariablesFactory implements IEnvironmentVariablesFactory {
|
||||||
|
public static readonly Current = new EnvironmentVariablesFactory();
|
||||||
|
|
||||||
|
public readonly instance: IEnvironmentVariables;
|
||||||
|
|
||||||
|
protected constructor(validator: EnvironmentVariablesValidator = validateEnvironmentVariables) {
|
||||||
|
const environment = new ViteEnvironmentVariables();
|
||||||
|
validator(environment);
|
||||||
|
this.instance = environment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnvironmentVariablesValidator = typeof validateEnvironmentVariables;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { IEnvironmentVariables } from './IEnvironmentVariables';
|
||||||
|
|
||||||
|
/* Validation is externalized to keep the environment objects simple */
|
||||||
|
export function validateEnvironmentVariables(environment: IEnvironmentVariables): void {
|
||||||
|
if (!environment) {
|
||||||
|
throw new Error('missing environment');
|
||||||
|
}
|
||||||
|
const keyValues = capturePropertyValues(environment);
|
||||||
|
if (!Object.keys(keyValues).length) {
|
||||||
|
throw new Error('Unable to capture key/value pairs');
|
||||||
|
}
|
||||||
|
const keysMissingValue = getKeysMissingValues(keyValues);
|
||||||
|
if (keysMissingValue.length > 0) {
|
||||||
|
throw new Error(`Environment keys missing: ${keysMissingValue.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeysMissingValues(keyValuePairs: Record<string, unknown>): string[] {
|
||||||
|
return Object.entries(keyValuePairs)
|
||||||
|
.reduce((acc, [key, value]) => {
|
||||||
|
if (!value && typeof value !== 'boolean') {
|
||||||
|
acc.push(key);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, new Array<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures values of properties and getters from the provided instance.
|
||||||
|
* Necessary because code transformations can make class getters non-enumerable during bundling.
|
||||||
|
* This ensures that even if getters are non-enumerable, their values are still captured and used.
|
||||||
|
*/
|
||||||
|
function capturePropertyValues(instance: unknown): Record<string, unknown> {
|
||||||
|
const obj: Record<string, unknown> = {};
|
||||||
|
const descriptors = Object.getOwnPropertyDescriptors(instance.constructor.prototype);
|
||||||
|
|
||||||
|
// Capture regular properties from the instance
|
||||||
|
for (const [key, value] of Object.entries(instance)) {
|
||||||
|
obj[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture getter properties from the instance's prototype
|
||||||
|
for (const [key, descriptor] of Object.entries(descriptors)) {
|
||||||
|
if (typeof descriptor.get === 'function') {
|
||||||
|
obj[key] = descriptor.get.call(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
10
src/infrastructure/EnvironmentVariables/IAppMetadata.ts
Normal file
10
src/infrastructure/EnvironmentVariables/IAppMetadata.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Represents essential metadata about the application.
|
||||||
|
*/
|
||||||
|
export interface IAppMetadata {
|
||||||
|
readonly version: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly slogan: string;
|
||||||
|
readonly repositoryUrl: string;
|
||||||
|
readonly homepageUrl: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { IAppMetadata } from './IAppMetadata';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Designed to decouple the process of retrieving environment variables
|
||||||
|
* (e.g., from the build environment) from the rest of the application.
|
||||||
|
*/
|
||||||
|
export interface IEnvironmentVariables extends IAppMetadata {
|
||||||
|
readonly isNonProduction: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { IEnvironmentVariables } from './IEnvironmentVariables';
|
||||||
|
|
||||||
|
export interface IEnvironmentVariablesFactory {
|
||||||
|
readonly instance: IEnvironmentVariables;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Only variables prefixed with VITE_ are exposed to Vite-processed code
|
||||||
|
export const VITE_USER_DEFINED_ENVIRONMENT_KEYS = {
|
||||||
|
VERSION: 'VITE_APP_VERSION',
|
||||||
|
NAME: 'VITE_APP_NAME',
|
||||||
|
SLOGAN: 'VITE_APP_SLOGAN',
|
||||||
|
REPOSITORY_URL: 'VITE_APP_REPOSITORY_URL',
|
||||||
|
HOMEPAGE_URL: 'VITE_APP_HOMEPAGE_URL',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const VITE_ENVIRONMENT_KEYS = {
|
||||||
|
...VITE_USER_DEFINED_ENVIRONMENT_KEYS,
|
||||||
|
DEV: 'DEV',
|
||||||
|
} as const;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { IEnvironmentVariables } from '../IEnvironmentVariables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the application's environment variables.
|
||||||
|
*/
|
||||||
|
export class ViteEnvironmentVariables implements IEnvironmentVariables {
|
||||||
|
// Ensure the use of import.meta.env prefix for the following properties.
|
||||||
|
// Vue will replace these statically during production builds.
|
||||||
|
|
||||||
|
public get version(): string {
|
||||||
|
return import.meta.env.VITE_APP_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name(): string {
|
||||||
|
return import.meta.env.VITE_APP_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get slogan(): string {
|
||||||
|
return import.meta.env.VITE_APP_SLOGAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get repositoryUrl(): string {
|
||||||
|
return import.meta.env.VITE_APP_REPOSITORY_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get homepageUrl(): string {
|
||||||
|
return import.meta.env.VITE_APP_HOMEPAGE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isNonProduction(): boolean {
|
||||||
|
return import.meta.env.DEV;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/infrastructure/EnvironmentVariables/Vite/vite-env.d.ts
vendored
Normal file
11
src/infrastructure/EnvironmentVariables/Vite/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
import { VITE_ENVIRONMENT_KEYS } from './ViteEnvironmentKeys';
|
||||||
|
|
||||||
|
export type ViteEnvironmentVariables = {
|
||||||
|
readonly [K in keyof typeof VITE_ENVIRONMENT_KEYS]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ViteEnvironmentVariables
|
||||||
|
}
|
||||||
@@ -1,9 +1,20 @@
|
|||||||
|
import { IEventSubscriptionCollection } from './IEventSubscriptionCollection';
|
||||||
import { IEventSubscription } from './IEventSource';
|
import { IEventSubscription } from './IEventSource';
|
||||||
|
|
||||||
export class EventSubscriptionCollection {
|
export class EventSubscriptionCollection implements IEventSubscriptionCollection {
|
||||||
private readonly subscriptions = new Array<IEventSubscription>();
|
private readonly subscriptions = new Array<IEventSubscription>();
|
||||||
|
|
||||||
public register(...subscriptions: IEventSubscription[]) {
|
public get subscriptionCount() {
|
||||||
|
return this.subscriptions.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public register(subscriptions: IEventSubscription[]) {
|
||||||
|
if (!subscriptions || subscriptions.length === 0) {
|
||||||
|
throw new Error('missing subscriptions');
|
||||||
|
}
|
||||||
|
if (subscriptions.some((subscription) => !subscription)) {
|
||||||
|
throw new Error('missing subscription in list');
|
||||||
|
}
|
||||||
this.subscriptions.push(...subscriptions);
|
this.subscriptions.push(...subscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,4 +22,9 @@ export class EventSubscriptionCollection {
|
|||||||
this.subscriptions.forEach((listener) => listener.unsubscribe());
|
this.subscriptions.forEach((listener) => listener.unsubscribe());
|
||||||
this.subscriptions.splice(0, this.subscriptions.length);
|
this.subscriptions.splice(0, this.subscriptions.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsubscribeAllAndRegister(subscriptions: IEventSubscription[]) {
|
||||||
|
this.unsubscribeAll();
|
||||||
|
this.register(subscriptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
|
||||||
|
|
||||||
|
export interface IEventSubscriptionCollection {
|
||||||
|
readonly subscriptionCount: number;
|
||||||
|
|
||||||
|
register(subscriptions: IEventSubscription[]): void;
|
||||||
|
unsubscribeAll(): void;
|
||||||
|
unsubscribeAllAndRegister(subscriptions: IEventSubscription[]);
|
||||||
|
}
|
||||||
13
src/infrastructure/Log/ConsoleLogger.ts
Normal file
13
src/infrastructure/Log/ConsoleLogger.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
export class ConsoleLogger implements ILogger {
|
||||||
|
constructor(private readonly globalConsole: Partial<Console> = console) {
|
||||||
|
if (!globalConsole) {
|
||||||
|
throw new Error('missing console');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(...params: unknown[]): void {
|
||||||
|
this.globalConsole.info(...params);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/infrastructure/Log/ElectronLogger.ts
Normal file
12
src/infrastructure/Log/ElectronLogger.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ElectronLog } from 'electron-log';
|
||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
// Using plain-function rather than class so it can be used in Electron's context-bridging.
|
||||||
|
export function createElectronLogger(logger: Partial<ElectronLog>): ILogger {
|
||||||
|
if (!logger) {
|
||||||
|
throw new Error('missing logger');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
info: (...params) => logger.info(...params),
|
||||||
|
};
|
||||||
|
}
|
||||||
3
src/infrastructure/Log/ILogger.ts
Normal file
3
src/infrastructure/Log/ILogger.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface ILogger {
|
||||||
|
info (...params: unknown[]): void;
|
||||||
|
}
|
||||||
5
src/infrastructure/Log/ILoggerFactory.ts
Normal file
5
src/infrastructure/Log/ILoggerFactory.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
export interface ILoggerFactory {
|
||||||
|
readonly logger: ILogger;
|
||||||
|
}
|
||||||
5
src/infrastructure/Log/NoopLogger.ts
Normal file
5
src/infrastructure/Log/NoopLogger.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
export class NoopLogger implements ILogger {
|
||||||
|
public info(): void { /* NOOP */ }
|
||||||
|
}
|
||||||
20
src/infrastructure/Log/WindowInjectedLogger.ts
Normal file
20
src/infrastructure/Log/WindowInjectedLogger.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { WindowVariables } from '../WindowVariables/WindowVariables';
|
||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
export class WindowInjectedLogger implements ILogger {
|
||||||
|
private readonly logger: ILogger;
|
||||||
|
|
||||||
|
constructor(windowVariables: WindowVariables = window) {
|
||||||
|
if (!windowVariables) {
|
||||||
|
throw new Error('missing window');
|
||||||
|
}
|
||||||
|
if (!windowVariables.log) {
|
||||||
|
throw new Error('missing log');
|
||||||
|
}
|
||||||
|
this.logger = windowVariables.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(...params: unknown[]): void {
|
||||||
|
this.logger.info(...params);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
|
||||||
|
export interface IRuntimeEnvironment {
|
||||||
|
readonly isDesktop: boolean;
|
||||||
|
readonly os: OperatingSystem | undefined;
|
||||||
|
readonly isNonProduction: boolean;
|
||||||
|
}
|
||||||
46
src/infrastructure/RuntimeEnvironment/RuntimeEnvironment.ts
Normal file
46
src/infrastructure/RuntimeEnvironment/RuntimeEnvironment.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||||
|
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||||
|
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||||
|
import { BrowserOsDetector } from './BrowserOs/BrowserOsDetector';
|
||||||
|
import { IBrowserOsDetector } from './BrowserOs/IBrowserOsDetector';
|
||||||
|
import { IRuntimeEnvironment } from './IRuntimeEnvironment';
|
||||||
|
|
||||||
|
export class RuntimeEnvironment implements IRuntimeEnvironment {
|
||||||
|
public static readonly CurrentEnvironment: IRuntimeEnvironment = new RuntimeEnvironment(window);
|
||||||
|
|
||||||
|
public readonly isDesktop: boolean;
|
||||||
|
|
||||||
|
public readonly os: OperatingSystem | undefined;
|
||||||
|
|
||||||
|
public readonly isNonProduction: boolean;
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
window: Partial<Window>,
|
||||||
|
environmentVariables: IEnvironmentVariables = EnvironmentVariablesFactory.Current.instance,
|
||||||
|
browserOsDetector: IBrowserOsDetector = new BrowserOsDetector(),
|
||||||
|
) {
|
||||||
|
if (!window) {
|
||||||
|
throw new Error('missing window');
|
||||||
|
}
|
||||||
|
this.isNonProduction = environmentVariables.isNonProduction;
|
||||||
|
this.isDesktop = isDesktop(window);
|
||||||
|
if (this.isDesktop) {
|
||||||
|
this.os = window?.os;
|
||||||
|
} else {
|
||||||
|
this.os = undefined;
|
||||||
|
const userAgent = getUserAgent(window);
|
||||||
|
if (userAgent) {
|
||||||
|
this.os = browserOsDetector.detect(userAgent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserAgent(window: Partial<Window>): string {
|
||||||
|
return window?.navigator?.userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDesktop(window: Partial<WindowVariables>): boolean {
|
||||||
|
return window?.isDesktop === true;
|
||||||
|
}
|
||||||
31
src/infrastructure/RuntimeSanity/Common/FactoryValidator.ts
Normal file
31
src/infrastructure/RuntimeSanity/Common/FactoryValidator.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { ISanityCheckOptions } from './ISanityCheckOptions';
|
||||||
|
import { ISanityValidator } from './ISanityValidator';
|
||||||
|
|
||||||
|
export type FactoryFunction<T> = () => T;
|
||||||
|
|
||||||
|
export abstract class FactoryValidator<T> implements ISanityValidator {
|
||||||
|
private readonly factory: FactoryFunction<T>;
|
||||||
|
|
||||||
|
protected constructor(factory: FactoryFunction<T>) {
|
||||||
|
if (!factory) {
|
||||||
|
throw new Error('missing factory');
|
||||||
|
}
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract shouldValidate(options: ISanityCheckOptions): boolean;
|
||||||
|
|
||||||
|
public abstract name: string;
|
||||||
|
|
||||||
|
public* collectErrors(): Iterable<string> {
|
||||||
|
try {
|
||||||
|
const value = this.factory();
|
||||||
|
if (!value) {
|
||||||
|
// Do not remove this check, it ensures that the factory call is not optimized away.
|
||||||
|
yield 'Factory resulted in a falsy value';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
yield `Error in factory creation: ${error.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user