Compare commits
1 Commits
0.12.7
...
capability
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7054521a7 |
@@ -5,7 +5,3 @@ end_of_line = lf
|
|||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
max_line_length = 100
|
max_line_length = 100
|
||||||
|
|
||||||
[{Dockerfile}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
|
|||||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
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,14 +17,42 @@ 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() {
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
inputs:
|
|
||||||
working-directory:
|
|
||||||
required: false
|
|
||||||
default: '.'
|
|
||||||
runs:
|
|
||||||
using: composite
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Run `npm ci` with retries
|
|
||||||
shell: bash
|
|
||||||
run: npm run install-deps -- --ci
|
|
||||||
working-directory: ${{ inputs.working-directory }}
|
|
||||||
2
.github/actions/setup-node/action.yml
vendored
2
.github/actions/setup-node/action.yml
vendored
@@ -3,6 +3,6 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Setup node
|
name: Setup node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 16.x
|
node-version: 16.x
|
||||||
|
|||||||
75
.github/workflows/checks.build.yaml
vendored
75
.github/workflows/checks.build.yaml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: checks.build
|
name: build-checks
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -9,92 +9,67 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos, ubuntu, windows ]
|
os: [ macos, ubuntu, windows ]
|
||||||
mode: [
|
mode: [ development, test, production ]
|
||||||
# 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:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Setup node
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
uses: ./.github/actions/npm-install-dependencies
|
run: npm ci
|
||||||
-
|
-
|
||||||
name: Build web
|
name: Build
|
||||||
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: [
|
mode: [ development, production ] # "test" is not supported https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1627
|
||||||
# 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:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Setup node
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
uses: ./.github/actions/npm-install-dependencies
|
run: npm ci
|
||||||
-
|
-
|
||||||
name: Prebuild desktop
|
name: Install cross-env
|
||||||
run: npm run electron:prebuild -- --mode ${{ matrix.mode }}
|
# Used to set NODE_ENV due to https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1626
|
||||||
|
run: npm install --global cross-env
|
||||||
-
|
-
|
||||||
name: Verify unbundled desktop build artifacts
|
name: Build
|
||||||
run: npm run check:verify-build-artifacts -- --electron-unbundled
|
run: |-
|
||||||
-
|
cross-env-shell NODE_ENV=${{ matrix.mode }}
|
||||||
name: Build (bundle and package) desktop application
|
npm run electron:build -- --publish never --mode ${{ matrix.mode }}
|
||||||
run: npm run electron:build -- --publish never
|
|
||||||
-
|
|
||||||
name: Verify bundled desktop build artifacts
|
|
||||||
run: npm run check:verify-build-artifacts -- --electron-bundled
|
|
||||||
|
|
||||||
build-docker:
|
create-icons:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos, ubuntu ] # Windows runners do not support Linux containers
|
os: [ macos, ubuntu, windows ]
|
||||||
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:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Install Docker on macOS
|
name: Setup node
|
||||||
if: matrix.os == 'macos' # macOS runner is missing Docker
|
uses: ./.github/actions/setup-node
|
||||||
run: |-
|
|
||||||
# Install Docker
|
|
||||||
brew install docker
|
|
||||||
# Docker on macOS misses daemon due to licensing, so install colima as runtime
|
|
||||||
brew install colima
|
|
||||||
# Start the daemon
|
|
||||||
colima start
|
|
||||||
-
|
-
|
||||||
name: Build Docker image
|
name: Install dependencies
|
||||||
run: docker build -t undergroundwires/privacy.sexy:latest .
|
run: npm ci
|
||||||
-
|
-
|
||||||
name: Run Docker image on port 8080
|
name: Create icons
|
||||||
run: docker run -d -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest
|
run: npm run create-icons
|
||||||
-
|
|
||||||
name: Check server is up and returns HTTP 200
|
|
||||||
run: node ./scripts/verify-web-server-status.js --url http://localhost:8080
|
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
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@v4
|
|
||||||
-
|
|
||||||
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
22
.github/workflows/checks.external-urls.yaml
vendored
@@ -1,22 +0,0 @@
|
|||||||
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@v4
|
|
||||||
-
|
|
||||||
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
|
|
||||||
16
.github/workflows/checks.quality.yaml
vendored
16
.github/workflows/checks.quality.yaml
vendored
@@ -16,15 +16,11 @@ 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@v4
|
- name: Setup node
|
||||||
-
|
|
||||||
name: Setup node
|
|
||||||
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: Lint
|
||||||
-
|
|
||||||
name: Lint
|
|
||||||
run: ${{ matrix.lint-command }}
|
run: ${{ matrix.lint-command }}
|
||||||
|
|||||||
55
.github/workflows/checks.scripts.yaml
vendored
55
.github/workflows/checks.scripts.yaml
vendored
@@ -1,55 +0,0 @@
|
|||||||
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@v4
|
|
||||||
-
|
|
||||||
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@v4
|
|
||||||
-
|
|
||||||
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 }}
|
|
||||||
42
.github/workflows/checks.security.sast.yaml
vendored
42
.github/workflows/checks.security.sast.yaml
vendored
@@ -1,42 +0,0 @@
|
|||||||
name: checks.security.sast
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * 0' # at 00:00 on every Sunday
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [
|
|
||||||
javascript # analyzes code written in JavaScript, TypeScript and both.
|
|
||||||
]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
-
|
|
||||||
name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v2
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
queries: +security-and-quality
|
|
||||||
-
|
|
||||||
name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v2
|
|
||||||
-
|
|
||||||
name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
||||||
with:
|
|
||||||
category: "/language:${{ matrix.language }}"
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: checks.security.dependencies
|
name: security-checks
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -13,10 +13,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Setup node
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: NPM audit
|
name: NPM audit
|
||||||
run: npm audit --omit=dev
|
run: exit "$(npm audit)" # Since node 15.x, it does not fail with error if we don't explicitly exit
|
||||||
25
.github/workflows/release.desktop.yaml
vendored
25
.github/workflows/release.desktop.yaml
vendored
@@ -13,29 +13,20 @@ 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@v4
|
|
||||||
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
|
||||||
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
|
||||||
-
|
- name: Publish desktop app
|
||||||
name: Prebuild
|
run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github
|
||||||
run: npm run electron: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
|
||||||
|
|||||||
21
.github/workflows/release.site.yaml
vendored
21
.github/workflows/release.site.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: "Infrastructure: Checkout"
|
name: "Infrastructure: Checkout"
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
path: aws
|
path: aws
|
||||||
repository: undergroundwires/aws-static-site-with-cd
|
repository: undergroundwires/aws-static-site-with-cd
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
working-directory: aws
|
working-directory: aws
|
||||||
-
|
-
|
||||||
name: "App: Checkout"
|
name: "App: Checkout"
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
path: app
|
path: app
|
||||||
ref: master # otherwise we don't get version bump commit
|
ref: master # otherwise we don't get version bump commit
|
||||||
@@ -84,8 +84,7 @@ jobs:
|
|||||||
uses: ./app/.github/actions/setup-node
|
uses: ./app/.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: "App: Install dependencies"
|
name: "App: Install dependencies"
|
||||||
uses: ./app/.github/actions/npm-install-dependencies
|
run: npm ci
|
||||||
with:
|
|
||||||
working-directory: app
|
working-directory: app
|
||||||
-
|
-
|
||||||
name: "App: Run unit tests"
|
name: "App: Run unit tests"
|
||||||
@@ -95,21 +94,11 @@ 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 "${web_output_dir}" \
|
--folder app/dist \
|
||||||
--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}} \
|
||||||
|
|||||||
6
.github/workflows/tests.e2e.yaml
vendored
6
.github/workflows/tests.e2e.yaml
vendored
@@ -14,13 +14,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Setup node
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
uses: ./.github/actions/npm-install-dependencies
|
run: npm ci
|
||||||
-
|
-
|
||||||
name: Run e2e tests
|
name: Run e2e tests
|
||||||
run: npm run test:cy:run
|
run: npm run test:e2e -- --headless
|
||||||
|
|||||||
4
.github/workflows/tests.integration.yaml
vendored
4
.github/workflows/tests.integration.yaml
vendored
@@ -16,13 +16,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Setup node
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
uses: ./.github/actions/npm-install-dependencies
|
run: npm ci
|
||||||
-
|
-
|
||||||
name: Run integration tests
|
name: Run integration tests
|
||||||
run: npm run test:integration
|
run: npm run test:integration
|
||||||
|
|||||||
4
.github/workflows/tests.unit.yaml
vendored
4
.github/workflows/tests.unit.yaml
vendored
@@ -14,13 +14,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
-
|
-
|
||||||
name: Set-up node
|
name: Set-up node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
uses: ./.github/actions/npm-install-dependencies
|
run: npm ci
|
||||||
-
|
-
|
||||||
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,5 +1,10 @@
|
|||||||
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
|
||||||
"Vue.volar", // Official Vue extensions
|
"jcbuisson.vue", // Highlights syntax.
|
||||||
"Vue.vscode-typescript-vue-plugin", // Official TypeScript Vue Plugin
|
"octref.vetur", // Adds Vetur, Vue tooling support.
|
||||||
// Scripting
|
// Scripting
|
||||||
"timonwong.shellcheck", // Lints bash files.
|
"timonwong.shellcheck", // Lints bash files.
|
||||||
"ms-vscode.powershell", // Lints PowerShell files.
|
"ms-vscode.powershell", // Lints PowerShell files.
|
||||||
|
|||||||
112
CHANGELOG.md
112
CHANGELOG.md
@@ -1,117 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.12.6 (2023-11-03)
|
|
||||||
|
|
||||||
* Bump dependencies to latest | [25d7f7b](https://github.com/undergroundwires/privacy.sexy/commit/25d7f7b2a479e51e092881cc2751e67a7d3f179f)
|
|
||||||
* win: improve system app uninstall cleanup #73 | [dbe3c5c](https://github.com/undergroundwires/privacy.sexy/commit/dbe3c5cfb91ba8a1657838b69117858843c8fbc8)
|
|
||||||
* win: improve system app uninstall /w fallback #260 | [98a26f9](https://github.com/undergroundwires/privacy.sexy/commit/98a26f9ae47af2668aa53f39d1768983036048ce)
|
|
||||||
* Improve performance of rendering during search | [79b46bf](https://github.com/undergroundwires/privacy.sexy/commit/79b46bf21004d96d31551439e5db5d698a3f71f3)
|
|
||||||
* Fix YAML error for site release in CI/CD | [237d994](https://github.com/undergroundwires/privacy.sexy/commit/237d9944f900f5172366868d75219224ff0542b0)
|
|
||||||
* win: fix Microsoft Advertising app removal #200 | [e40b9a3](https://github.com/undergroundwires/privacy.sexy/commit/e40b9a3cf53c341f2e84023a9f0e9680ac08f3fa)
|
|
||||||
* win: improve directory cleanup security | [060e789](https://github.com/undergroundwires/privacy.sexy/commit/060e7896624309aebd25e8b190c127282de177e8)
|
|
||||||
* Centralize Electron entry file path configuration | [d6da406](https://github.com/undergroundwires/privacy.sexy/commit/d6da406c61e5b9f5408851d1302d6d7398157a2e)
|
|
||||||
* win: prevent updates from reinstalling apps #260 | [8570b02](https://github.com/undergroundwires/privacy.sexy/commit/8570b02dde14ffad64863f614682c3fc1f87b6c2)
|
|
||||||
* win: improve script environment robustness #221 | [dfd4451](https://github.com/undergroundwires/privacy.sexy/commit/dfd44515613f38abe5a806bda36f44e7b715b50b)
|
|
||||||
* Fix compiler failing with nested `with` expression | [80821fc](https://github.com/undergroundwires/privacy.sexy/commit/80821fca0769e5fd2c6338918fbdcea12fbe83d2)
|
|
||||||
* win: improve soft file/app delete security #260 | [f4a74f0](https://github.com/undergroundwires/privacy.sexy/commit/f4a74f058db9b5bcbcbe438785db5ec88ecc1657)
|
|
||||||
* Fix incorrect tooltip position after window resize | [f8e5f1a](https://github.com/undergroundwires/privacy.sexy/commit/f8e5f1a5a2afa1f18567e6d965359b6a1f082367)
|
|
||||||
* linux: fix string formatting of Firefox configs | [e775d68](https://github.com/undergroundwires/privacy.sexy/commit/e775d68a9b4a5f9e893ff0e3500dade036185193)
|
|
||||||
* win: improve file delete | [e72c1c1](https://github.com/undergroundwires/privacy.sexy/commit/e72c1c13ea2d73ebfc7a8da5a21254fdfc0e5b59)
|
|
||||||
* win: change system app removal to hard delete #260 | [77123d8](https://github.com/undergroundwires/privacy.sexy/commit/77123d8c929d23676a9cb21d7b697703fd1b6e82)
|
|
||||||
* Improve UI performance by optimizing reactivity | [4995e49](https://github.com/undergroundwires/privacy.sexy/commit/4995e49c469211404dac9fcb79b75eb121f80bce)
|
|
||||||
* Migrate to Vue 3.0 #230 | [ca81f68](https://github.com/undergroundwires/privacy.sexy/commit/ca81f68ff1c3bbe5b22981096ae9220b0b5851c7)
|
|
||||||
* win, linux: unify & improve Firefox clean-up #273 | [0466b86](https://github.com/undergroundwires/privacy.sexy/commit/0466b86f1013341c966a9bbf6513990337b16598)
|
|
||||||
* win: fix store revert for multiple installs #260 | [5bb13e3](https://github.com/undergroundwires/privacy.sexy/commit/5bb13e34f8de2e2a7ba943ff72b12c0569435e62)
|
|
||||||
|
|
||||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.5...0.12.6)
|
|
||||||
|
|
||||||
## 0.12.5 (2023-10-13)
|
|
||||||
|
|
||||||
* Fix Docker build and improve checks #220 | [7669985](https://github.com/undergroundwires/privacy.sexy/commit/7669985f8e1446e726a95626ecf35b3ce6b60a16)
|
|
||||||
* Add SAST security checks with SECURITY.md #178 | [3e5239f](https://github.com/undergroundwires/privacy.sexy/commit/3e5239f7d35e57749c01adf3dbbcd365aebb39c8)
|
|
||||||
* Add Scoop download instructions #174 | [cf55ca9](https://github.com/undergroundwires/privacy.sexy/commit/cf55ca9e28b064fa7a516077a9da23e3a8e3f534)
|
|
||||||
* win: fix and improve temp dir cleanup #176, #89 | [d457504](https://github.com/undergroundwires/privacy.sexy/commit/d45750428cca010daf2721b33a8ae3a01b28813b)
|
|
||||||
* win, linux: improve VSCode setting robustness #196 | [e8a52f7](https://github.com/undergroundwires/privacy.sexy/commit/e8a52f717dc799b34ceeb1c27c2b8219391dff6a)
|
|
||||||
* linux: fix obsolete Firefox DPI script #239 | [e5f6edf](https://github.com/undergroundwires/privacy.sexy/commit/e5f6edf405bcec7c29ea4d7932d1910620fa15f8)
|
|
||||||
* win: add removal of Edge assocations #64 | [888c916](https://github.com/undergroundwires/privacy.sexy/commit/888c9166fc66a2094137fa8be739cc21bafef5f6)
|
|
||||||
* win: improve Edge & OneDrive shortcut removal #73 | [8501495](https://github.com/undergroundwires/privacy.sexy/commit/8501495c170af61913288a63dbd369db5bbc5003)
|
|
||||||
* win: relocate and document SecHealthUI #190 | [2862951](https://github.com/undergroundwires/privacy.sexy/commit/286295128d0179358e0c6b7b6415d752175a1aed)
|
|
||||||
* Add developer toolkit UI component | [2147eae](https://github.com/undergroundwires/privacy.sexy/commit/2147eae687b82d05bc43bb4605d9068f148bb92a)
|
|
||||||
* win: fix and improve network data usage reset #265 | [5e359c2](https://github.com/undergroundwires/privacy.sexy/commit/5e359c2fb82a08e6acf7159b70ca86a8234b359b)
|
|
||||||
* win: improve app reversion and docs #260 | [a3f11df](https://github.com/undergroundwires/privacy.sexy/commit/a3f11dff187c821a00910c20dac05e285cda9073)
|
|
||||||
* Fix working directory in CI/CD web release | [698b570](https://github.com/undergroundwires/privacy.sexy/commit/698b570ee6e300d6703015464f4345b5e706f1cb)
|
|
||||||
* Implement new UI component for icons #230 | [48730bc](https://github.com/undergroundwires/privacy.sexy/commit/48730bca0506120bca4bf3a23545d59f2b1a9009)
|
|
||||||
* win: fix and improve AppCompat disabling #255 | [bab6316](https://github.com/undergroundwires/privacy.sexy/commit/bab6316e7625230cf4a4cf67c3aca417347db75c)
|
|
||||||
* win, linux, mac: fix typos and improve naming | [67c3677](https://github.com/undergroundwires/privacy.sexy/commit/67c3677621b201525a813e8a26f07d607176e89b)
|
|
||||||
|
|
||||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.4...0.12.5)
|
|
||||||
|
|
||||||
## 0.12.4 (2023-09-25)
|
|
||||||
|
|
||||||
* win: fix Windows spotlight revert, docs, recommend | [659fea7](https://github.com/undergroundwires/privacy.sexy/commit/659fea7afcabcd0ea273cfdcc8c4bae190c126f3)
|
|
||||||
* win: fix Edge telemetry disabling for v116+ #242 | [6d301f9](https://github.com/undergroundwires/privacy.sexy/commit/6d301f99616ed49975876803d0098eafe4d3cb2e)
|
|
||||||
* win: fix, improve disabling automatic updates #252 | [6e9b65d](https://github.com/undergroundwires/privacy.sexy/commit/6e9b65d8b1b481c1471dde90876c37838b4ac4e5)
|
|
||||||
* win: refactor `update.mode` key for VSCode #215 | [c27172c](https://github.com/undergroundwires/privacy.sexy/commit/c27172c32e7c316b7cb0f44cab611eed89ca034e)
|
|
||||||
* Fix wrong action path in website CI deployment | [a1f2497](https://github.com/undergroundwires/privacy.sexy/commit/a1f24973813ccbdd7e1f06c64e1912a991a6bb64)
|
|
||||||
* Fix compiler bug with nested optional arguments | [53222fd](https://github.com/undergroundwires/privacy.sexy/commit/53222fd83c2846089746a217482195806f960d18)
|
|
||||||
* Fix no spacing after lists in documentation text | [f810ed0](https://github.com/undergroundwires/privacy.sexy/commit/f810ed0c147c2a46cae3b70b635ed81128646fff)
|
|
||||||
* Rewrite tooltip UI for efficiency and Vue 3.0 #230 | [8b930fc](https://github.com/undergroundwires/privacy.sexy/commit/8b930fc57c8ee6691ed6165bcb27d97e64a1a0c0)
|
|
||||||
* win: fix uninstallation of newer Edge #236 | [60dde11](https://github.com/undergroundwires/privacy.sexy/commit/60dde11311a2409537f5965f370b0daaaec53339)
|
|
||||||
* win: fix delivery optimization side-effects #173 | [203daeb](https://github.com/undergroundwires/privacy.sexy/commit/203daeb4a2fca0a0295cbc2a736394f9f87725e6)
|
|
||||||
* win: fix Defender scan artifacts removal #246 | [cb21a97](https://github.com/undergroundwires/privacy.sexy/commit/cb21a970b6b867e1476a5eb8a72b9a7fdd53a744)
|
|
||||||
* Fix outdated and broken links in README #161 | [0303ef2](https://github.com/undergroundwires/privacy.sexy/commit/0303ef2fd98b36306523e2a0c5f5ae812a4c6c99)
|
|
||||||
* Fix loss of tree node state when switching views | [8f188ac](https://github.com/undergroundwires/privacy.sexy/commit/8f188acd3c2d93e40c89569c74bc5cff992f0052)
|
|
||||||
* Fix slow appearance of nodes on tree view | [bd2082e](https://github.com/undergroundwires/privacy.sexy/commit/bd2082e8c574db065bb4462f30ea3ace2cb028cb)
|
|
||||||
|
|
||||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.3...0.12.4)
|
|
||||||
|
|
||||||
## 0.12.3 (2023-09-09)
|
|
||||||
|
|
||||||
* linux: use user.js over prefs.js for Firefox #232 | [dae6d11](https://github.com/undergroundwires/privacy.sexy/commit/dae6d114daab6857d773071211eb57619b136281)
|
|
||||||
* win: fix typo in Defender retention script #213 | [35be05d](https://github.com/undergroundwires/privacy.sexy/commit/35be05df2094ea8bba4ee4725e6fa4956a79493d)
|
|
||||||
* Improve desktop runtime execution tests | [ad0576a](https://github.com/undergroundwires/privacy.sexy/commit/ad0576a752f8fd6ea2f917a59173fe61f9951246)
|
|
||||||
* Fix Windows artifact naming in desktop packaging | [f4d86fc](https://github.com/undergroundwires/privacy.sexy/commit/f4d86fccfd0e73e94c8c6e400a33514900bc5abe)
|
|
||||||
* Refactor and improve external URL checks | [19e42c9](https://github.com/undergroundwires/privacy.sexy/commit/19e42c9c52a18c813ded4265e687e01032cdd4c8)
|
|
||||||
* Fix memory leaks via auto-unsubscribing and DI | [eb096d0](https://github.com/undergroundwires/privacy.sexy/commit/eb096d07e276e1b4c8040220c47f186d02841e14)
|
|
||||||
* Refactor build configs and improve CI/CD checks | [0a2a1a0](https://github.com/undergroundwires/privacy.sexy/commit/0a2a1a026b0efb29624be82b06536c518c1ea439)
|
|
||||||
* Introduce retry mechanism for npm install in CI/CD | [4beb1bb](https://github.com/undergroundwires/privacy.sexy/commit/4beb1bb5748a60886210187ca3cdc7f4b41067c0)
|
|
||||||
* win: fix disable recent apps revert #211, #248 | [4ce327e](https://github.com/undergroundwires/privacy.sexy/commit/4ce327eb6af542ed2916d649553e5e1ba5833882)
|
|
||||||
* Change license to AGPLv3 | [821cc62](https://github.com/undergroundwires/privacy.sexy/commit/821cc62c4c8347cb76d041f82f574754e4d948c5)
|
|
||||||
* Introduce new TreeView UI component | [65f121c](https://github.com/undergroundwires/privacy.sexy/commit/65f121c451af87315e1c91df4198562e0445b2c2)
|
|
||||||
|
|
||||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.2...0.12.3)
|
|
||||||
|
|
||||||
## 0.12.2 (2023-08-25)
|
|
||||||
|
|
||||||
* Add automated checks for desktop app runtime #233 | [04b3133](https://github.com/undergroundwires/privacy.sexy/commit/04b3133500485d0d278a81a177a1677134131405)
|
|
||||||
* 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)
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ You have two alternatives:
|
|||||||
|
|
||||||
1. [Create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) and ask for someone else to add the script for you.
|
1. [Create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) and ask for someone else to add the script for you.
|
||||||
2. Or send a PR yourself. This would make it faster to get your code into the project. You need to add scripts to related OS in [collections](src/application/collections/) folder. Then you'd sent a pull request, see [pull request process](#pull-request-process).
|
2. Or send a PR yourself. This would make it faster to get your code into the project. You need to add scripts to related OS in [collections](src/application/collections/) folder. Then you'd sent a pull request, see [pull request process](#pull-request-process).
|
||||||
- 💡 You should use existing shared functions for most of the operations, like `DisableService` for disabling services, to maintain code consistency and efficiency.
|
|
||||||
- 📖 If you're unsure about the syntax, check [collection-files.md](docs/collection-files.md).
|
- 📖 If you're unsure about the syntax, check [collection-files.md](docs/collection-files.md).
|
||||||
- 📖 If you wish to use templates, use [templating.md](./docs/templating.md).
|
- 📖 If you wish to use templates, use [templating.md](./docs/templating.md).
|
||||||
|
|
||||||
|
|||||||
15
Dockerfile
15
Dockerfile
@@ -1,16 +1,13 @@
|
|||||||
# Build
|
# Build
|
||||||
FROM node:lts-alpine AS build-stage
|
FROM node:lts-alpine as build-stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run install-deps
|
RUN npm run build
|
||||||
RUN npm run build \
|
|
||||||
&& npm run check:verify-build-artifacts -- --web
|
|
||||||
RUN mkdir /dist \
|
|
||||||
&& dist_directory=$(node 'scripts/print-dist-dir.js' --web) \
|
|
||||||
&& cp -a "${dist_directory}/." '/dist'
|
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM nginx:stable-alpine AS production-stage
|
FROM nginx:stable-alpine as production-stage
|
||||||
COPY --from=build-stage /dist /usr/share/nginx/html
|
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
141
LICENSE
141
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 19 November 2007
|
Version 3, 29 June 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,15 +7,17 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU Affero General Public License is a free, copyleft license for
|
The GNU General Public License is a free, copyleft license for
|
||||||
software and other kinds of works, specifically designed to ensure
|
software and other kinds of works.
|
||||||
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,
|
||||||
our General Public Licenses are intended to guarantee your freedom to
|
the GNU General Public License is 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.
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
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
|
||||||
@@ -24,34 +26,44 @@ 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.
|
||||||
|
|
||||||
Developers that use our General Public Licenses protect your rights
|
To protect your rights, we need to prevent others from denying you
|
||||||
with two steps: (1) assert copyright on the software, and (2) offer
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
you this License which gives you legal permission to copy, distribute
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
and/or modify the software.
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
A secondary benefit of defending all users' freedom is that
|
For example, if you distribute copies of such a program, whether
|
||||||
improvements made in alternate versions of the program, if they
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
receive widespread use, become available for other developers to
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
incorporate. Many developers of free software are heartened and
|
or can get the source code. And you must show them these terms so they
|
||||||
encouraged by the resulting cooperation. However, in the case of
|
know their rights.
|
||||||
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.
|
|
||||||
|
|
||||||
The GNU Affero General Public License is designed specifically to
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
ensure that, in such cases, the modified source code becomes available
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
to the community. It requires the operator of a network server to
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
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.
|
|
||||||
|
|
||||||
An older license, called the Affero General Public License and
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
published by Affero, was designed to accomplish similar goals. This is
|
that there is no warranty for this free software. For both users' and
|
||||||
a different license, not a version of the Affero GPL, but Affero has
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
released a new version of the Affero GPL which permits relicensing under
|
changed, so that their problems will not be attributed erroneously to
|
||||||
this license.
|
authors of previous versions.
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -60,7 +72,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
"This License" refers to version 3 of the GNU 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.
|
||||||
@@ -537,45 +549,35 @@ 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. Remote Network Interaction; Use with the GNU General Public License.
|
13. Use with the GNU Affero 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 General Public License into a single
|
under version 3 of the GNU Affero 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 work with which it is combined will remain governed by version
|
but the special requirements of the GNU Affero General Public License,
|
||||||
3 of the GNU General Public License.
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
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 Affero General Public License from time to time. Such new versions
|
the GNU General Public License from time to time. Such new versions will
|
||||||
will be similar in spirit to the present version, but may differ in detail to
|
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 Affero General
|
Program specifies that a certain numbered version of the GNU 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 Affero General Public License, you may choose any version ever published
|
GNU 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 Affero General Public License can be used, that proxy's
|
versions of the GNU 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.
|
||||||
|
|
||||||
@@ -633,29 +635,40 @@ 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 Affero General Public License as published by
|
it under the terms of the GNU 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 Affero General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU 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 your software can interact with users remotely through a computer
|
If the program does terminal interaction, make it output a short
|
||||||
network, you should also make sure that it provides a way for users to
|
notice like this when it starts in an interactive mode:
|
||||||
get its source. For example, if your program is a web application, its
|
|
||||||
interface could display a "Source" link that leads users to an archive
|
<program> Copyright (C) <year> <name of author>
|
||||||
of the code. There are many ways you could offer source, and different
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
solutions will be better for different programs; see section 13 for the
|
This is free software, and you are welcome to redistribute it
|
||||||
specific requirements.
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
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 AGPL, see
|
For more information on this, and how to apply and follow the GNU GPL, 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>.
|
||||||
|
|||||||
93
README.md
93
README.md
@@ -4,19 +4,27 @@
|
|||||||
|
|
||||||
<!-- markdownlint-disable MD033 -->
|
<!-- markdownlint-disable MD033 -->
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://undergroundwires.dev/donate?project=privacy.sexy" target="_blank" rel="noopener noreferrer">
|
<a href="https://undergroundwires.dev/donate?project=privacy.sexy">
|
||||||
<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" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md">
|
||||||
<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"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability" target="_blank" rel="noopener noreferrer">
|
<!-- Code quality -->
|
||||||
|
<br />
|
||||||
|
<a href="https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript">
|
||||||
|
<img
|
||||||
|
alt="Language grade: JavaScript/TypeScript"
|
||||||
|
src="https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability">
|
||||||
<img
|
<img
|
||||||
alt="Maintainability"
|
alt="Maintainability"
|
||||||
src="https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability"
|
src="https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability"
|
||||||
@@ -24,85 +32,59 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Tests -->
|
<!-- Tests -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.unit.yaml" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.unit.yaml">
|
||||||
<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" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.integration.yaml">
|
||||||
<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" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.e2e.yaml">
|
||||||
<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"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<!-- Security checks -->
|
|
||||||
<br />
|
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.sast.yaml" target="_blank" rel="noopener noreferrer">
|
|
||||||
<img
|
|
||||||
alt="Status of dependency security checks"
|
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.sast/badge.svg"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.dependencies.yaml" target="_blank" rel="noopener noreferrer">
|
|
||||||
<img
|
|
||||||
alt="Status of Static Analysis Security Testing (SAST)"
|
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.dependencies/badge.svg"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<!-- Checks -->
|
<!-- Checks -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml">
|
||||||
<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.build.yaml" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.yaml">
|
||||||
<img
|
<img
|
||||||
alt="Status of build checks"
|
alt="Security checks status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.build/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.desktop-runtime-errors.yaml" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml">
|
||||||
<img
|
<img
|
||||||
alt="Status of runtime error checks for the desktop application"
|
alt="Build checks status"
|
||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.desktop-runtime-errors/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/build-checks/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>
|
</a>
|
||||||
<!-- Release -->
|
<!-- Release -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml">
|
||||||
<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" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.site.yaml">
|
||||||
<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" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.desktop.yaml">
|
||||||
<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"
|
||||||
@@ -110,7 +92,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Others -->
|
<!-- Others -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/bump-everywhere" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/bump-everywhere">
|
||||||
<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"
|
||||||
@@ -122,7 +104,7 @@
|
|||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
||||||
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.6/privacy.sexy-Setup-0.12.6.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.6/privacy.sexy-0.12.6.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.6/privacy.sexy-0.12.6.AppImage). For more options, see [here](#additional-install-options).
|
- 🖥️ **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).
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -150,25 +132,6 @@ Online version does not require to run any software on your computer. Offline ve
|
|||||||
|
|
||||||
**Contribute 👷**. Contributions of any type are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) as the starting point. It includes useful information like [how to add new scripts](./CONTRIBUTING.md#extend-scripts).
|
**Contribute 👷**. Contributions of any type are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) as the starting point. It includes useful information like [how to add new scripts](./CONTRIBUTING.md#extend-scripts).
|
||||||
|
|
||||||
## Additional Install Options
|
|
||||||
|
|
||||||
- Check the [releases page](https://github.com/undergroundwires/privacy.sexy/releases) for all available versions.
|
|
||||||
- Other unofficial channels (not maintained by privacy.sexy) for Windows include:
|
|
||||||
- [Scoop 🥄](https://scoop.sh/#/apps?q=privacy.sexy&s=2&d=1&o=true) (latest version):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
scoop bucket add extras
|
|
||||||
scoop install privacy.sexy
|
|
||||||
```
|
|
||||||
|
|
||||||
- [winget 🪟](https://winget.run/pkg/undergroundwires/privacy.sexy) (may be outdated):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
winget install -e --id undergroundwires.privacy.sexy
|
|
||||||
```
|
|
||||||
|
|
||||||
With winget, updates require manual submission; the auto-update feature within privacy.sexy will notify you of new releases post-installation.
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment.
|
Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment.
|
||||||
@@ -176,7 +139,3 @@ Refer to [development.md](./docs/development.md) for Docker usage and reading mo
|
|||||||
Check [architecture.md](./docs/architecture.md) for an overview of design and how different parts and layers work together. You can refer to [application.md](./docs/application.md) for a closer look at application layer codebase and [presentation.md](./docs/presentation.md) for code related to GUI layer. [collection-files.md](./docs/collection-files.md) explains the YAML files that are the core of the application and [templating.md](./docs/templating.md) documents how to use templating language in those files. In [ci-cd.md](./docs/ci-cd.md), you can read more about the pipelines that automates maintenance tasks and ensures you get what see.
|
Check [architecture.md](./docs/architecture.md) for an overview of design and how different parts and layers work together. You can refer to [application.md](./docs/application.md) for a closer look at application layer codebase and [presentation.md](./docs/presentation.md) for code related to GUI layer. [collection-files.md](./docs/collection-files.md) explains the YAML files that are the core of the application and [templating.md](./docs/templating.md) documents how to use templating language in those files. In [ci-cd.md](./docs/ci-cd.md), you can read more about the pipelines that automates maintenance tasks and ensures you get what see.
|
||||||
|
|
||||||
[docs/](./docs/) folder includes all other documentation.
|
[docs/](./docs/) folder includes all other documentation.
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
Security is a top priority at privacy.sexy. An extensive commitment to security verification ensures this priority. For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).
|
|
||||||
|
|||||||
31
SECURITY.md
31
SECURITY.md
@@ -1,31 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
privacy.sexy takes security seriously. Commitment is made to address all security issues with urgency. Responsible reporting of any discovered vulnerabilities in the project is highly encouraged.
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Efforts to responsibly disclose findings are greatly appreciated. To report a security vulnerability, follow these steps:
|
|
||||||
|
|
||||||
- For general vulnerabilities, [open an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) using the bug report template.
|
|
||||||
- For sensitive matters, [contact the developer directly](https://undergroundwires.dev).
|
|
||||||
|
|
||||||
## Security Report Handling
|
|
||||||
|
|
||||||
Upon receipt of a security report, the following actions will be taken:
|
|
||||||
|
|
||||||
- The report will be confirmed, identifying the affected components.
|
|
||||||
- The impact and severity of the issue will be assessed.
|
|
||||||
- Work on a fix and plan a release to address the vulnerability will be initiated.
|
|
||||||
- The reporter will be kept updated about the progress.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Regular and extensive testing is conducted to ensure robust security in the project. Information about testing practices can be found in the [Testing Documentation](./docs/tests.md).
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
For additional assistance or any unanswered questions, [submit a GitHub issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose). Security concerns are a priority, and necessary support to address them is assured.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Active contribution to the safety and security of privacy.sexy is thanked. This collaborative effort keeps the project resilient and trustworthy for all.
|
|
||||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset',
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
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: `${CYPRESS_BASE_DIR}/fixtures`,
|
fixturesFolder: 'tests/e2e/fixtures',
|
||||||
screenshotsFolder: `${CYPRESS_BASE_DIR}/screenshots`,
|
screenshotsFolder: 'tests/e2e/screenshots',
|
||||||
|
videosFolder: 'tests/e2e/videos',
|
||||||
video: true,
|
|
||||||
videosFolder: `${CYPRESS_BASE_DIR}/videos`,
|
|
||||||
|
|
||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: `http://localhost:${ViteConfig.server.port}/`,
|
setupNodeEvents(on, config) {
|
||||||
specPattern: `${CYPRESS_BASE_DIR}/**/*.cy.{js,jsx,ts,tsx}`, // Default: cypress/e2e/**/*.cy.{js,jsx,ts,tsx}
|
return require('./tests/e2e/plugins/index.js')(on, config)
|
||||||
supportFile: `${CYPRESS_BASE_DIR}/support/e2e.ts`,
|
},
|
||||||
|
specPattern: 'tests/e2e/specs/**/*.cy.{js,jsx,ts,tsx}',
|
||||||
|
supportFile: 'tests/e2e/support/index.js',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"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).
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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,23 +15,11 @@ Application is
|
|||||||
|
|
||||||
Application uses highly decoupled models & services in different DDD layers:
|
Application uses highly decoupled models & services in different DDD layers:
|
||||||
|
|
||||||
**Application layer** (see [application.md](./application.md)):
|
- presentation layer (see [presentation.md](./presentation.md)),
|
||||||
|
- application layer (see [application.md](./application.md)),
|
||||||
|
- and domain layer.
|
||||||
|
|
||||||
- Coordinates application activities and consumes the 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.
|
||||||
|
|
||||||
**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.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -39,8 +27,6 @@ Application uses highly decoupled models & services in different DDD layers:
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
@@ -59,7 +45,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`, `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).
|
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).
|
||||||
|
|
||||||
## AWS infrastructure
|
## AWS infrastructure
|
||||||
|
|
||||||
|
|||||||
@@ -174,19 +174,3 @@
|
|||||||
- `endCode:` *`string`* (**required**)
|
- `endCode:` *`string`* (**required**)
|
||||||
- Code that'll be inserted at the end of user created script.
|
- Code that'll be inserted at the end of user created script.
|
||||||
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
|
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
|
||||||
|
|
||||||
## Naming guidelines
|
|
||||||
|
|
||||||
- Prioritize consistency throughout all names.
|
|
||||||
- Use an instruction format like "do this, do that" for clear, direct guidance. This approach reduces potential confusion and offers easy-to-follow steps. It provides specific, unambiguous instructions.
|
|
||||||
- Ensure brand names adhere to their official casing.
|
|
||||||
- Choose clear and uncomplicated language.
|
|
||||||
- Favor the terms:
|
|
||||||
- "Disable" over "Turn off"
|
|
||||||
- "Configure" over "Set up"
|
|
||||||
- "Clear" over "Erase" or "Clean"
|
|
||||||
- "Minimize" over "Limit" or "Reduce" (when it enhances clarity)
|
|
||||||
- "Remove" over "Uninstall"
|
|
||||||
- Structure your phrases for clarity.
|
|
||||||
- For instance, "Disable XX telemetry" or "Clear XX data" are preferred over "Clear data from XX", "Disable telemetry in XX", or "Clear data of XX".
|
|
||||||
- Use sentence case rather than Title Case.
|
|
||||||
|
|||||||
@@ -5,28 +5,22 @@ 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.
|
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.
|
||||||
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 >16.x.
|
- Install node >15.x.
|
||||||
- Install dependencies using `npm install` (or [`npm run install-deps`](#utility-scripts) for more options).
|
- Install dependencies using `npm install`.
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
- Run unit tests: `npm run test:unit`
|
- Run unit tests: `npm run test:unit`
|
||||||
- Run integration tests: `npm run test:integration`
|
- Run integration tests: `npm run test:integration`
|
||||||
- Run end-to-end (e2e) tests:
|
- Run e2e (end-to-end) tests
|
||||||
- `npm run test:cy:open`: Run tests interactively using the development server with hot-reloading.
|
- Interactive mode with GUI: `npm run test:e2e`
|
||||||
- `npm run test:cy:run`: Run tests on the production build in a headless mode.
|
- Headless mode without GUI: `npm run test:e2e -- --headless`
|
||||||
- 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).
|
||||||
|
|
||||||
@@ -41,26 +35,11 @@ See [ci-cd.md](./ci-cd.md) for more information.
|
|||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
**Web:**
|
- Run in local server: `npm run serve`
|
||||||
|
|
||||||
- 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.
|
||||||
- Preview production build: `npm run preview`
|
- Run using Docker:
|
||||||
- Start a local web server that serves the built solution from `./dist`.
|
|
||||||
- 💡 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 .`
|
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`
|
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest`
|
||||||
3. Application should be available at [`http://localhost:8080`](http://localhost:8080)
|
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
@@ -68,27 +47,13 @@ See [ci-cd.md](./ci-cd.md) for more information.
|
|||||||
- 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`
|
||||||
|
|
||||||
### Scripts
|
### Utility Scripts
|
||||||
|
|
||||||
📖 For detailed options and behavior for any of the following scripts, please refer to the script file itself.
|
- Run fresh NPM install: [`./scripts/fresh-npm-install.sh`](../scripts/fresh-npm-install.sh)
|
||||||
|
- 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.
|
||||||
#### Utility scripts
|
- Configure VSCode: [`./scripts/configure-vscode.sh`](../scripts/configure-vscode.sh)
|
||||||
|
|
||||||
- [**`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.
|
|
||||||
- [**`npm run check:verify-build-artifacts [-- <options>]`**](../scripts/verify-build-artifacts.js):
|
|
||||||
- Verifies the existence and content of build artifacts. Useful for ensuring that the build process is generating the expected output.
|
|
||||||
- [**`node scripts/verify-web-server-status.js --url [URL]`**](../scripts/verify-web-server-status.js):
|
|
||||||
- Checks if a specified server is up with retries and returns an HTTP 200 status code.
|
|
||||||
|
|
||||||
## Recommended extensions
|
## Recommended extensions
|
||||||
|
|
||||||
You should use EditorConfig to follow project style.
|
You should use EditorConfig to follow project style.
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
# Presentation layer
|
# Presentation layer
|
||||||
|
|
||||||
The presentation layer handles UI concerns using Vue as JavaScript framework and Electron to provide desktop functionality.
|
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.
|
||||||
|
|
||||||
It reflects the [application state](./application.md#application-state) and allows user interactions to modify it. Components manage their own local UI state.
|
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.
|
||||||
|
|
||||||
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 Vue and Electron code.
|
- [`/src/` **`presentation/`**](./../src/presentation/): Contains all presentation related code including Vue and Electron configurations
|
||||||
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue components and plugins.
|
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue global objects including components and plugins.
|
||||||
- [**`components/`**](./../src/presentation/components/): Contains Vue components and helpers.
|
- [**`components/`**](./../src/presentation/components/): Contains all Vue components and their helper classes.
|
||||||
- [**`Shared/`**](./../src/presentation/components/Shared): Contains shared Vue components and helpers.
|
- [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that other components share.
|
||||||
- [**`Hooks`**](../src/presentation/components/Shared/Hooks): Hooks used by components through [dependency injection](#dependency-injections).
|
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets that webpack will process.
|
||||||
- [**`/public/`**](../src/presentation/public/): Contains static assets.
|
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts
|
||||||
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets processed by Vite.
|
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles used throughout different components.
|
||||||
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
|
- [**`components/`**](./../src/presentation/assets/styles/components): Contains reusable styles coupled to a Vue/HTML component.
|
||||||
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
|
- [**`vendors-extensions/`**](./../src/presentation/assets/styles/third-party-extensions): Contains styles that override third-party components used.
|
||||||
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles coupled to Vue components.
|
- [**`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.
|
||||||
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint.
|
- [**`main.ts`**](./../src/presentation/main.ts): Application entry point that mounts and starts Vue application.
|
||||||
- [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
|
- [**`electron/`**](./../src/presentation/electron/): Electron configuration for the desktop application.
|
||||||
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
|
- [**`main.ts`**](./../src/presentation/main.ts): Main process of Electron, started as first thing when app starts.
|
||||||
- [`/main/` **`index.ts`**](./../src/presentation/main.ts): Main entry for Electron, managing application windows and lifecycle events.
|
- [**`/public/`**](./../public/): Contains static assets that are directly copied and do not go through webpack.
|
||||||
- [`/preload/` **`index.ts`**](./../src/presentation/main.ts): Script executed before the renderer, securing Node.js features for renderer use.
|
- [**`/vue.config.js`**](./../vue.config.js): Global Vue CLI configurations loaded by `@vue/cli-service`.
|
||||||
- [**`/vite.config.ts`**](./../vite.config.ts): Contains Vite configurations for building web application.
|
- [**`/postcss.config.js`**](./../postcss.config.js): PostCSS configurations used by Vue CLI internally.
|
||||||
- [**`/electron.vite.config.ts`**](./../electron.vite.config.ts): Contains Vite configurations for building desktop applications.
|
- [**`/babel.config.js`**](./../babel.config.js): Babel configurations for polyfills used by `@vue/cli-plugin-babel`.
|
||||||
- [**`/postcss.config.cjs`**](./../postcss.config.cjs): Contains PostCSS configurations for Vite.
|
|
||||||
|
|
||||||
## Visual design best-practices
|
## Visual design best-practices
|
||||||
|
|
||||||
@@ -35,7 +32,7 @@ Add visual clues for clickable items. It should be as clear as possible that the
|
|||||||
|
|
||||||
## Application data
|
## Application data
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
[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:
|
||||||
|
|
||||||
@@ -46,58 +43,34 @@ You can read more about how application layer provides application data to he pr
|
|||||||
|
|
||||||
## Application state
|
## Application state
|
||||||
|
|
||||||
This project uses a singleton instance of the application state, making it available to all Vue components.
|
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.
|
||||||
|
|
||||||
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.
|
[`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts) functions include:
|
||||||
|
|
||||||
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.
|
- Creating a singleton of the state and makes it available to presentation layer as single source of truth.
|
||||||
|
- 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.
|
||||||
|
|
||||||
[`UseCollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) provides several functionalities including:
|
📖 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.
|
||||||
|
|
||||||
- **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.
|
## Modals
|
||||||
- **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).
|
|
||||||
|
|
||||||
📖 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.
|
[Dialog.vue](./../src/presentation/components/Shared/Dialog.vue) is a shared component that other components used to show modal windows.
|
||||||
|
|
||||||
## Dependency injections
|
You can use it by wrapping the content inside of its `slot` and call `.show()` function on its reference. For example:
|
||||||
|
|
||||||
The presentation layer uses Vue's native dependency injection system to increase testability and decouple components.
|
```html
|
||||||
|
<Dialog ref="testDialog">
|
||||||
|
<div>Hello world</div>
|
||||||
|
</Dialog>
|
||||||
|
<div @click="$refs.testDialog.show()">Show dialog</div>
|
||||||
|
```
|
||||||
|
|
||||||
To add a new dependency:
|
## Sass naming convention
|
||||||
|
|
||||||
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;`
|
||||||
|
|||||||
@@ -2,69 +2,21 @@
|
|||||||
|
|
||||||
## Benefits of templating
|
## Benefits of templating
|
||||||
|
|
||||||
- **Code sharing:** Share code across scripts for consistent practices and easier maintenance.
|
- Generating scripts by sharing code to increase best-practice usage and maintainability.
|
||||||
- **Script independence:** Generate self-contained scripts, eliminating the need for external code.
|
- Creating self-contained scripts without cross-dependencies.
|
||||||
- **Cleaner code:** Use pipes for complex operations, resulting in more readable and streamlined code.
|
- Use of pipes for writing cleaner code and letting pipes do dirty work.
|
||||||
|
|
||||||
## Expressions
|
## Expressions
|
||||||
|
|
||||||
**Syntax:**
|
- Expressions start and end with mustaches (double brackets, `{{` and `}}`).
|
||||||
|
- E.g. `Hello {{ $name }} !`
|
||||||
Expressions are enclosed within `{{` and `}}`.
|
- Syntax is close to [Go Templates ❤️](https://pkg.go.dev/text/template) but not the same.
|
||||||
Example: `Hello {{ $name }}!`.
|
- Functions enables usage of expressions.
|
||||||
They are a core component of templating, enhancing scripts with dynamic capabilities and functionality.
|
- In script definition parts of a function, see [`Function`](./collection-files.md#Function).
|
||||||
|
- When doing a call as argument values, see [`FunctionCall`](./collection-files.md#Function).
|
||||||
**Syntax similarity:**
|
- Expressions inside expressions (nested templates) are supported.
|
||||||
|
- An expression can output another expression that will also be compiled.
|
||||||
The syntax shares similarities with [Go Templates ❤️](https://pkg.go.dev/text/template), but with some differences:
|
- E.g. following would compile first [with expression](#with), and then [parameter substitution](#parameter-substitution) in its output.
|
||||||
|
|
||||||
**Function definitions:**
|
|
||||||
|
|
||||||
You can use expressions in function definition.
|
|
||||||
Refer to [Function](./collection-files.md#function) for more details.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: GreetFunction
|
|
||||||
parameters:
|
|
||||||
- name: name
|
|
||||||
code: Hello {{ $name }}!
|
|
||||||
```
|
|
||||||
|
|
||||||
If you assign `name` the value `world`, invoking `GreetFunction` would result in `Hello world!`.
|
|
||||||
|
|
||||||
**Function arguments:**
|
|
||||||
|
|
||||||
You can also use expressions in arguments in nested function calls.
|
|
||||||
Refer to [`Function | collection-files.md`](./collection-files.md#functioncall) for more details.
|
|
||||||
|
|
||||||
Example with nested function calls:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
-
|
|
||||||
name: PrintMessageFunction
|
|
||||||
parameters:
|
|
||||||
- name: message
|
|
||||||
code: echo "{{ $message }}"
|
|
||||||
-
|
|
||||||
name: GreetUserFunction
|
|
||||||
parameters:
|
|
||||||
- name: userName
|
|
||||||
call:
|
|
||||||
name: PrintMessageFunction
|
|
||||||
parameters:
|
|
||||||
argument: 'Hello, {{ $userName }}!'
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, if `userName` is `Alice`, invoking `GreetUserFunction` would execute `echo "Hello, Alice!"`.
|
|
||||||
|
|
||||||
**Nested templates:**
|
|
||||||
|
|
||||||
You can nest expressions inside expressions (also called "nested templates").
|
|
||||||
This means that an expression can output another expression where compiler will compile both.
|
|
||||||
|
|
||||||
For example, following would compile first [with expression](#with), and then [parameter substitution](#parameter-substitution) in its output:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{ with $condition }}
|
{{ with $condition }}
|
||||||
@@ -74,70 +26,55 @@ For example, following would compile first [with expression](#with), and then [p
|
|||||||
|
|
||||||
### Parameter substitution
|
### Parameter substitution
|
||||||
|
|
||||||
Parameter substitution dynamically replaces variable references with their corresponding values in the script.
|
A simple function example:
|
||||||
|
|
||||||
**Example function:**
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: DisplayTextFunction
|
function: EchoArgument
|
||||||
parameters:
|
parameters:
|
||||||
- name: 'text'
|
- name: 'argument'
|
||||||
code: echo {{ $text }}
|
code: Hello {{ $argument }} !
|
||||||
```
|
```
|
||||||
|
|
||||||
Invoking `DisplayTextFunction` with `text` set to `"Hello, world!"` would result in `echo "Hello, World!"`.
|
It would print "Hello world" if it's called in a [script](./collection-files.md#script) as following:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
script: Echo script
|
||||||
|
call:
|
||||||
|
function: EchoArgument
|
||||||
|
parameters:
|
||||||
|
argument: World
|
||||||
|
```
|
||||||
|
|
||||||
|
A function can call other functions such as:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
-
|
||||||
|
function: CallerFunction
|
||||||
|
parameters:
|
||||||
|
- name: 'value'
|
||||||
|
call:
|
||||||
|
function: EchoArgument
|
||||||
|
parameters:
|
||||||
|
argument: {{ $value }}
|
||||||
|
-
|
||||||
|
function: EchoArgument
|
||||||
|
parameters:
|
||||||
|
- name: 'argument'
|
||||||
|
code: Hello {{ $argument }} !
|
||||||
|
```
|
||||||
|
|
||||||
### with
|
### with
|
||||||
|
|
||||||
The `with` expression enables conditional rendering and provides a context variable for simpler code.
|
Skips its "block" if the variable is absent or empty. Its "block" is between `with` start (`{{ with .. }}`) and end (`{{ end }`}) expressions.
|
||||||
|
E.g. `{{ with $parameterName }} Hi, I'm a block! {{ end }}` would only output `Hi, I'm a block!` if `parameterName` has any value..
|
||||||
|
|
||||||
**Optional block rendering:**
|
It binds its context (value of the provided parameter value) as arbitrary `.` value. It allows you to use the argument value of the given parameter when it is provided and not empty such as:
|
||||||
|
|
||||||
If the provided variable is falsy (`false`, `null`, or empty), the compiler skips the enclosed block of code.
|
|
||||||
A "block" lies between the with start (`{{ with .. }}`) and end (`{{ end }}`) expressions, defining its boundaries.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
{{ with $optionalVariable }}
|
|
||||||
Hello
|
|
||||||
{{ end }}
|
|
||||||
```
|
|
||||||
|
|
||||||
This would display `Hello` if `$optionalVariable` is truthy.
|
|
||||||
|
|
||||||
**Parameter declaration:**
|
|
||||||
|
|
||||||
You should set `optional: true` for the argument if you use it like `{{ with $argument }} .. {{ end }}`.
|
|
||||||
|
|
||||||
Declare parameters used for `with` condition as optional such as:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: ConditionalOutputFunction
|
|
||||||
parameters:
|
|
||||||
- name: 'data'
|
|
||||||
optional: true
|
|
||||||
code: |-
|
|
||||||
{{ with $data }}
|
|
||||||
Data is: {{ . }}
|
|
||||||
{{ end }}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Context variable:**
|
|
||||||
|
|
||||||
`with` statement binds its context (value of the provided parameter value) as arbitrary `.` value.
|
|
||||||
`{{ . }}` syntax gives you access to the context variable.
|
|
||||||
This is optional to use, and not required to use `with` expressions.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{ with $parameterName }}Parameter value is {{ . }} here {{ end }}
|
{{ with $parameterName }}Parameter value is {{ . }} here {{ end }}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Multiline text:**
|
It supports multiline text inside the block. You can have something like:
|
||||||
|
|
||||||
It supports multiline text inside the block. You can write something like:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{ with $argument }}
|
{{ with $argument }}
|
||||||
@@ -146,9 +83,7 @@ It supports multiline text inside the block. You can write something like:
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Inner expressions:**
|
You can also use other expressions inside its block, such as [parameter substitution](#parameter-substitution):
|
||||||
|
|
||||||
You can also embed other expressions inside its block, such as [parameter substitution](#parameter-substitution):
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{ with $condition }}
|
{{ with $condition }}
|
||||||
@@ -156,44 +91,32 @@ You can also embed other expressions inside its block, such as [parameter substi
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
```
|
```
|
||||||
|
|
||||||
This also includes nesting `with` statements:
|
💡 Declare parameters used for `with` condition as optional. Set `optional: true` for the argument if you use it like `{{ with $argument }} .. {{ end }}`.
|
||||||
|
|
||||||
```go
|
Example:
|
||||||
{{ with $condition1 }}
|
|
||||||
Value of $condition1: {{ . }}
|
```yaml
|
||||||
{{ with $condition2 }}
|
function: FunctionThatOutputsConditionally
|
||||||
Value of $condition2: {{ . }}
|
parameters:
|
||||||
{{ end }}
|
- name: 'argument'
|
||||||
|
optional: true
|
||||||
|
code: |-
|
||||||
|
{{ with $argument }}
|
||||||
|
Value is: {{ . }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pipes
|
### Pipes
|
||||||
|
|
||||||
Pipes are functions designed for text manipulation.
|
- Pipes are functions available for handling text.
|
||||||
They allow for a sequential application of operations resembling [Unix pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)), also known as "chaining".
|
- Allows stacking actions one after another also known as "chaining".
|
||||||
Each pipeline's output becomes the input of the following pipe.
|
- Like [Unix pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)), the concept is simple: each pipeline's output becomes the input of the following pipe.
|
||||||
|
- You cannot create pipes. [A dedicated compiler](./application.md#parsing-and-compiling) provides pre-defined pipes to consume in collection files.
|
||||||
**Pre-defined**:
|
- You can combine pipes with other expressions such as [parameter substitution](#parameter-substitution) and [with](#with) syntax.
|
||||||
|
- ❗ Pipe names must be camelCase without any space or special characters.
|
||||||
Pipes are pre-defined by the system.
|
- **Existing pipes**
|
||||||
You cannot create pipes in [collection files](./collection-files.md).
|
|
||||||
[A dedicated compiler](./application.md#parsing-and-compiling) provides pre-defined pipes to consume in collection files.
|
|
||||||
|
|
||||||
**Compatibility:**
|
|
||||||
|
|
||||||
You can combine pipes with other expressions such as [parameter substitution](#parameter-substitution) and [with](#with) syntax.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
{{ with $script }} echo "{{ . | inlinePowerShell | escapeDoubleQuotes }}" {{ end }}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Naming:**
|
|
||||||
|
|
||||||
❗ Pipe names must be camelCase without any space or special characters.
|
|
||||||
|
|
||||||
**Available pipes:**
|
|
||||||
|
|
||||||
- `inlinePowerShell`: Converts a multi-lined PowerShell script to a single line.
|
- `inlinePowerShell`: Converts a multi-lined PowerShell script to a single line.
|
||||||
- `escapeDoubleQuotes`: Escapes `"` characters for batch command execution, allows you to use them inside double quotes (`"`).
|
- `escapeDoubleQuotes`: Escapes `"` characters, allows you to use them inside double quotes (`"`).
|
||||||
|
- **Example usages**
|
||||||
|
- `{{ with $code }} echo "{{ . | inlinePowerShell }}" {{ end }}`
|
||||||
|
- `{{ with $code }} echo "{{ . | inlinePowerShell | escapeDoubleQuotes }}" {{ end }}`
|
||||||
|
|||||||
127
docs/tests.md
127
docs/tests.md
@@ -5,84 +5,77 @@ 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)
|
|
||||||
|
|
||||||
## Unit and integration tests
|
Common aspects for all tests:
|
||||||
|
|
||||||
- They utilize [Vitest](https://vitest.dev/).
|
- They use [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/).
|
||||||
- Test files are suffixed with `.spec.ts`.
|
- Their files end with `.spec.{ts|js}` suffix.
|
||||||
|
|
||||||
|
💡 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 implement the act, arrange, and assert (AAA) pattern.
|
- Tests use act, arrange and assert (AAA) pattern when applicable.
|
||||||
- **Arrange**
|
- **Arrange**
|
||||||
- Sets up the test scenario and environment.
|
- Sets up the test case.
|
||||||
- Begins with comment line `// arrange`.
|
- Starts with comment line `// arrange`.
|
||||||
- **Act**
|
- **Act**
|
||||||
- Executes the actual test.
|
- Executes the actual test.
|
||||||
- Begins with comment line `// act`.
|
- Starts with comment line `// act`.
|
||||||
- **Assert**
|
- **Assert**
|
||||||
- Sets an expectation for the test's outcome.
|
- Elicit some sort of expectation.
|
||||||
- Begins with comment line `// assert`.
|
- Starts with comment line `// assert`.
|
||||||
|
|
||||||
### Unit tests
|
## Integration tests
|
||||||
|
|
||||||
- Evaluate individual components in isolation.
|
- Tests functionality of a component in combination with others (not isolated).
|
||||||
- Located in [`./tests/unit`](./../tests/unit).
|
- Ensure dependencies to third parties work as expected.
|
||||||
- Achieve isolation using [stubs](./../tests/unit/shared/Stubs).
|
- Defined in [./tests/integration](./../tests/integration).
|
||||||
- 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
|
||||||
|
|
||||||
- Examine the live web application's functionality and performance.
|
- Test the functionality and performance of a running application.
|
||||||
- Uses Cypress to run the tests.
|
- Vue CLI plugin [`e2e-cypress`](https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-plugin-e2e-cypress#readme) configures E2E tests.
|
||||||
|
- Test names and folders have logical structure based on tests executed.
|
||||||
## Automated checks
|
- The structure is following:
|
||||||
|
- [`cypress.config.ts`](./../cypress.config.ts): Cypress configuration file.
|
||||||
These checks validate various qualities like runtime execution, building process, security testing, etc.
|
- [`./tests/e2e/`](./../tests/e2e/): Base Cypress folder.
|
||||||
|
- [`/specs/`](./../tests/e2e/specs/): Test files named with `.spec.js` extension.
|
||||||
- Use [various tools](./../package.json) and [scripts](./../scripts).
|
- [`/plugins/index.js`](./../tests/e2e/plugins/index.js): Plugin file executed before loading project.
|
||||||
- Are automatically executed as [GitHub workflows](./../.github/workflows).
|
- [`/support/index.js`](./../tests/e2e/support/index.js): Support file, runs before every single spec file.
|
||||||
|
- *(Ignored)* `/videos`: Asset folder for videos taken during tests.
|
||||||
### Security checks
|
- *(Ignored)* `/screenshots`: Asset folder for Screenshots taken during tests.
|
||||||
|
|
||||||
- [`checks.security.sast`](./../.github/workflows/checks.security.sast.yaml): Utilizes CodeQL to conduct Static Analysis Security Testing (SAST) to ensure the secure integrity of the codebase.
|
|
||||||
- [`checks.security.dependencies`](./../.github/workflows/checks.security.dependencies.yaml): Performs audits on third-party dependencies to identify and mitigate potential vulnerabilities, safeguarding the project from exploitable weaknesses.
|
|
||||||
|
|
||||||
## Tests structure
|
|
||||||
|
|
||||||
- [`package.json`](./../package.json): Defines test commands and includes tools used in tests.
|
|
||||||
- [`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.
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
/* 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}',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
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 ELECTRON_DIST_SUBDIRECTORIES = {
|
|
||||||
main: resolveElectronDistSubdirectory('main'),
|
|
||||||
preload: resolveElectronDistSubdirectory('preload'),
|
|
||||||
renderer: resolveElectronDistSubdirectory('renderer'),
|
|
||||||
};
|
|
||||||
|
|
||||||
process.env.ELECTRON_ENTRY = resolve(ELECTRON_DIST_SUBDIRECTORIES.main, 'index.cjs');
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
main: getSharedElectronConfig({
|
|
||||||
distDirSubfolder: ELECTRON_DIST_SUBDIRECTORIES.main,
|
|
||||||
entryFilePath: MAIN_ENTRY_FILE,
|
|
||||||
}),
|
|
||||||
preload: getSharedElectronConfig({
|
|
||||||
distDirSubfolder: ELECTRON_DIST_SUBDIRECTORIES.preload,
|
|
||||||
entryFilePath: PRELOAD_ENTRY_FILE,
|
|
||||||
}),
|
|
||||||
renderer: mergeConfig(
|
|
||||||
createVueConfig({
|
|
||||||
supportLegacyBrowsers: false,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
build: {
|
|
||||||
outDir: ELECTRON_DIST_SUBDIRECTORIES.renderer,
|
|
||||||
rollupOptions: {
|
|
||||||
input: {
|
|
||||||
index: WEB_INDEX_HTML_PATH,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
function getSharedElectronConfig(options: {
|
|
||||||
readonly distDirSubfolder: string;
|
|
||||||
readonly entryFilePath: string;
|
|
||||||
}): UserConfig {
|
|
||||||
return {
|
|
||||||
build: {
|
|
||||||
outDir: 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): string {
|
|
||||||
return resolve(__dirname, pathSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveElectronDistSubdirectory(subDirectory: string): string {
|
|
||||||
const electronDistDir = resolvePathFromProjectRoot(distDirs.electronUnbundled);
|
|
||||||
return resolve(electronDistDir, subDirectory);
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,9 @@ This folder contains image files and other resources related to images.
|
|||||||
|
|
||||||
## logo.svg
|
## logo.svg
|
||||||
|
|
||||||
[`logo.svg`](./logo.svg) serves as the primary logo from which all other icons and images are derived.
|
[logo.svg](./logo.svg) is the master logo from which all other icons or images are created from.
|
||||||
Only modify this file manually.
|
It should be the only file that will be changed 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.
|
||||||
|
|||||||
@@ -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, 'src/presentation/public');
|
this.publicDirectory = join(projectRoot, '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',
|
||||||
34950
package-lock.json
generated
34950
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
139
package.json
139
package.json
@@ -1,107 +1,112 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.12.6",
|
"version": "0.12.0",
|
||||||
"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",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-cli-service build",
|
||||||
"preview": "vite preview",
|
"test:unit": "vue-cli-service test:unit",
|
||||||
"test:unit": "vitest run --dir tests/unit",
|
"test:e2e": "vue-cli-service test:e2e",
|
||||||
"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",
|
||||||
"install-deps": "node scripts/npm-install.js",
|
"create-icons": "node img/logo-update.mjs",
|
||||||
"icons:build": "node scripts/logo-update.js",
|
"electron:build": "vue-cli-service electron:build",
|
||||||
"check:desktop": "vitest run --dir tests/checks/desktop-runtime-errors --environment node",
|
"electron:serve": "vue-cli-service electron:serve",
|
||||||
"check:external-urls": "vitest run --dir tests/checks/external-urls --environment node",
|
"lint:eslint": "vue-cli-service lint --no-fix --mode production",
|
||||||
"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/free-brands-svg-icons": "^6.4.0",
|
||||||
|
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||||
|
"@fortawesome/vue-fontawesome": "^2.0.9",
|
||||||
"@juggle/resize-observer": "^3.4.0",
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
"ace-builds": "^1.30.0",
|
"ace-builds": "^1.23.4",
|
||||||
|
"core-js": "^3.32.0",
|
||||||
"cross-fetch": "^4.0.0",
|
"cross-fetch": "^4.0.0",
|
||||||
"electron-log": "^4.4.8",
|
|
||||||
"electron-progressbar": "^2.1.0",
|
"electron-progressbar": "^2.1.0",
|
||||||
"electron-updater": "^6.1.4",
|
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"markdown-it": "^13.0.2",
|
"install": "^0.13.0",
|
||||||
"vue": "^3.3.7"
|
"liquor-tree": "^0.2.70",
|
||||||
|
"markdown-it": "^13.0.1",
|
||||||
|
"npm": "^9.8.1",
|
||||||
|
"v-tooltip": "2.1.3",
|
||||||
|
"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.5.1",
|
"@types/ace": "^0.0.48",
|
||||||
"@types/ace": "^0.0.49",
|
"@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",
|
||||||
"@vitejs/plugin-legacy": "^4.1.1",
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
"@vitejs/plugin-vue": "^4.4.0",
|
"@vue/cli-plugin-e2e-cypress": "~5.0.8",
|
||||||
|
"@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",
|
||||||
"@vue/test-utils": "^2.4.1",
|
"chai": "^4.3.7",
|
||||||
"autoprefixer": "^10.4.16",
|
"cypress": "^12.17.2",
|
||||||
"cypress": "^13.3.1",
|
"electron": "^25.3.2",
|
||||||
"electron": "^27.0.0",
|
"electron-builder": "^24.6.3",
|
||||||
"electron-builder": "^24.6.4",
|
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-icon-builder": "^2.0.1",
|
"electron-icon-builder": "^2.0.1",
|
||||||
"electron-vite": "^1.0.28",
|
"electron-log": "^4.4.8",
|
||||||
"eslint": "^8.51.0",
|
"electron-updater": "^6.1.4",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint": "^8.46.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-vuejs-accessibility": "^2.2.0",
|
"eslint-plugin-vue": "^9.6.0",
|
||||||
"icon-gen": "^4.0.0",
|
"eslint-plugin-vuejs-accessibility": "^1.2.0",
|
||||||
"jsdom": "^22.1.0",
|
"icon-gen": "^3.0.1",
|
||||||
"markdownlint-cli": "^0.37.0",
|
"js-yaml-loader": "^1.2.2",
|
||||||
"postcss": "^8.4.31",
|
"markdownlint-cli": "^0.35.0",
|
||||||
"remark-cli": "^12.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": "^13.0.0",
|
"remark-validate-links": "^12.1.1",
|
||||||
"sass": "^1.69.3",
|
"sass": "^1.64.1",
|
||||||
"start-server-and-test": "^2.0.1",
|
"sass-loader": "^13.3.2",
|
||||||
"svgexport": "^0.4.2",
|
"svgexport": "^0.4.2",
|
||||||
"terser": "^5.21.0",
|
"ts-loader": "^9.4.4",
|
||||||
"tslib": "^2.6.2",
|
"typescript": "~4.6.2",
|
||||||
"typescript": "^5.2.2",
|
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4",
|
||||||
"vite": "^4.4.11",
|
"yaml-lint": "^1.7.0",
|
||||||
"vitest": "^0.34.6",
|
"tslib": "~2.4.0"
|
||||||
"vue-tsc": "^1.8.19",
|
},
|
||||||
"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": [
|
||||||
"@rushstack/eslint-patch": "Needed by @vue/eslint-config-typescript",
|
"Cannot upgrade to 5.X.X due to unmaintained @vue/cli-plugin-typescript, https://github.com/vuejs/vue-cli/issues/7401",
|
||||||
"@vue/eslint-config-typescript": "Cannot upgrade to 12.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
|
"Cannot upgrade to > 4.6.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252"
|
||||||
"@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
|
],
|
||||||
"@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58"
|
"tslib": "Cannot upgrade to > 2.4.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252",
|
||||||
|
"@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-typescript, https://github.com/vuejs/eslint-config-typescript/pull/60",
|
||||||
|
"@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-typescript, https://github.com/vuejs/eslint-config-typescript/pull/60"
|
||||||
},
|
},
|
||||||
"homepage": "https://privacy.sexy",
|
"homepage": "https://privacy.sexy",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/undergroundwires/privacy.sexy.git"
|
"url": "https://github.com/undergroundwires/privacy.sexy.git"
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"dmg-license": "^1.0.11"
|
|
||||||
},
|
|
||||||
"//optionalDependencies": {
|
|
||||||
"dmg-license": "Required by `electron-builder` for DMG builds on macOS, https://github.com/electron-userland/electron-builder/issues/6489, https://github.com/electron-userland/electron-builder/issues/6520"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
const autoprefixer = require('autoprefixer');
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
return {
|
|
||||||
plugins: [
|
|
||||||
autoprefixer(),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
5
postcss.config.js
Normal file
5
postcss.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -1,4 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<title>Privacy is sexy 🍑🍆 - Enforce privacy & security on Windows, macOS and Linux</title>
|
||||||
|
<meta name="robots" content="index,follow" />
|
||||||
|
<meta name="description" content="Web tool to generate scripts for enforcing privacy & security best-practices such as stopping data collection of Windows and different softwares on it."/>
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<style>
|
<style>
|
||||||
@@ -19,5 +28,7 @@
|
|||||||
<p>The page does not work without JavaScript enabled. Please enable it to use privacy.sexy. There's no shady stuff as 100% of the website is open source.</p>
|
<p>The page does not work without JavaScript enabled. Please enable it to use privacy.sexy. There's no shady stuff as 100% of the website is open source.</p>
|
||||||
</div>
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
||||||
95
scripts/fresh-npm-install.sh
Executable file
95
scripts/fresh-npm-install.sh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/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"
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
/*
|
|
||||||
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();
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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();
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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();
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/**
|
|
||||||
* Description:
|
|
||||||
* This script checks if a server, provided as a CLI argument, is up
|
|
||||||
* and returns an HTTP 200 status code.
|
|
||||||
* It is designed to provide easy verification of server availability
|
|
||||||
* and will retry a specified number of times.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node ./scripts/verify-web-server-status.js --url [URL]
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
* --url URL of the server to check
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { get } from 'http';
|
|
||||||
|
|
||||||
const MAX_RETRIES = 30;
|
|
||||||
const RETRY_DELAY_IN_SECONDS = 3;
|
|
||||||
const URL_PARAMETER_NAME = '--url';
|
|
||||||
|
|
||||||
function checkServer(currentRetryCount = 1) {
|
|
||||||
const serverUrl = getServerUrl();
|
|
||||||
console.log(`Requesting ${serverUrl}...`);
|
|
||||||
get(serverUrl, (res) => {
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
console.log('🎊 Success: The server is up and returned HTTP 200.');
|
|
||||||
process.exit(0);
|
|
||||||
} else {
|
|
||||||
console.log(`Server returned HTTP status code ${res.statusCode}.`);
|
|
||||||
retry(currentRetryCount);
|
|
||||||
}
|
|
||||||
}).on('error', (err) => {
|
|
||||||
console.error('Error making the request:', err);
|
|
||||||
retry(currentRetryCount);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function retry(currentRetryCount) {
|
|
||||||
console.log(`Attempt ${currentRetryCount}/${MAX_RETRIES}:`);
|
|
||||||
console.log(`Retrying in ${RETRY_DELAY_IN_SECONDS} seconds.`);
|
|
||||||
|
|
||||||
const remainingTime = (MAX_RETRIES - currentRetryCount) * RETRY_DELAY_IN_SECONDS;
|
|
||||||
console.log(`Time remaining before timeout: ${remainingTime}s`);
|
|
||||||
|
|
||||||
if (currentRetryCount < MAX_RETRIES) {
|
|
||||||
setTimeout(() => checkServer(currentRetryCount + 1), RETRY_DELAY_IN_SECONDS * 1000);
|
|
||||||
} else {
|
|
||||||
console.log('Failure: The server at did not return HTTP 200 within the allocated time. Exiting.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getServerUrl() {
|
|
||||||
const urlIndex = process.argv.indexOf(URL_PARAMETER_NAME);
|
|
||||||
if (urlIndex === -1 || urlIndex === process.argv.length - 1) {
|
|
||||||
console.error(`Parameter "${URL_PARAMETER_NAME}" is not provided.`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
return process.argv[urlIndex + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
checkServer();
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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];
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
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,23 +1,25 @@
|
|||||||
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 { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
import { Environment } from '../Environment/Environment';
|
||||||
|
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 = RuntimeEnvironment.CurrentEnvironment,
|
environment = Environment.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.os);
|
const os = getInitialOs(app, environment);
|
||||||
return new ApplicationContext(app, os);
|
return new ApplicationContext(app, os);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitialOs(app: IApplication, currentOs: OperatingSystem): OperatingSystem {
|
function getInitialOs(app: IApplication, environment: IEnvironment): 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;
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum FilterActionType {
|
|
||||||
Apply,
|
|
||||||
Clear,
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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 filterChanged: IEventSource<IFilterChangeDetails>;
|
readonly filtered: IEventSource<IFilterResult>;
|
||||||
|
readonly filterRemoved: IEventSource<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserFilter extends IReadOnlyUserFilter {
|
export interface IUserFilter extends IReadOnlyUserFilter {
|
||||||
applyFilter(filter: string): void;
|
setFilter(filter: string): void;
|
||||||
clearFilter(): void;
|
removeFilter(): 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 filterChanged = new EventSource<IFilterChangeDetails>();
|
public readonly filtered = new EventSource<IFilterResult>();
|
||||||
|
|
||||||
|
public readonly filterRemoved = new EventSource<void>();
|
||||||
|
|
||||||
public currentFilter: IFilterResult | undefined;
|
public currentFilter: IFilterResult | undefined;
|
||||||
|
|
||||||
@@ -16,9 +16,9 @@ export class UserFilter implements IUserFilter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public applyFilter(filter: string): void {
|
public setFilter(filter: string): void {
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
throw new Error('Filter must be defined and not empty. Use clearFilter() to remove the filter');
|
throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter');
|
||||||
}
|
}
|
||||||
const filterLowercase = filter.toLocaleLowerCase();
|
const filterLowercase = filter.toLocaleLowerCase();
|
||||||
const filteredScripts = this.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.filterChanged.notify(FilterChange.forApply(this.currentFilter));
|
this.filtered.notify(matches);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearFilter(): void {
|
public removeFilter(): void {
|
||||||
this.currentFilter = undefined;
|
this.currentFilter = undefined;
|
||||||
this.filterChanged.notify(FilterChange.forClear());
|
this.filterRemoved.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
89
src/application/Environment/Environment.ts
Normal file
89
src/application/Environment/Environment.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
6
src/application/Environment/IEnvironment.ts
Normal file
6
src/application/Environment/IEnvironment.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
|
||||||
|
export interface IEnvironment {
|
||||||
|
readonly isDesktop: boolean;
|
||||||
|
readonly os: OperatingSystem;
|
||||||
|
}
|
||||||
@@ -7,19 +7,16 @@ 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(
|
||||||
categoryParser = parseCategoryCollection,
|
parser = CategoryCollectionParser,
|
||||||
informationParser = parseProjectInformation,
|
processEnv: NodeJS.ProcessEnv = process.env,
|
||||||
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
|
|
||||||
collectionsData = PreParsedCollections,
|
collectionsData = PreParsedCollections,
|
||||||
): IApplication {
|
): IApplication {
|
||||||
validateCollectionsData(collectionsData);
|
validateCollectionsData(collectionsData);
|
||||||
const information = informationParser(metadata);
|
const information = parseProjectInformation(processEnv);
|
||||||
const collections = collectionsData.map((collection) => categoryParser(collection, information));
|
const collections = collectionsData.map((collection) => parser(collection, information));
|
||||||
const app = new Application(information, collections);
|
const app = new Application(information, collections);
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
@@ -27,12 +24,16 @@ 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?.length) {
|
if (!collections || !collections.length) {
|
||||||
throw new Error('missing collections');
|
throw new Error('missing collections');
|
||||||
}
|
}
|
||||||
if (collections.some((collection) => !collection)) {
|
if (collections.some((collection) => !collection)) {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
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 CustomError {
|
export class NodeDataError extends Error {
|
||||||
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,29 +1,28 @@
|
|||||||
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
|
export function parseProjectInformation(
|
||||||
parseProjectInformation(
|
environment: NodeJS.ProcessEnv | VueAppEnvironment,
|
||||||
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
|
|
||||||
createProjectInformation: ProjectInformationFactory = (
|
|
||||||
...args
|
|
||||||
) => new ProjectInformation(...args),
|
|
||||||
): IProjectInformation {
|
): IProjectInformation {
|
||||||
const version = new Version(
|
const version = new Version(environment[VueAppEnvironmentKeys.VUE_APP_VERSION]);
|
||||||
metadata.version,
|
return new ProjectInformation(
|
||||||
);
|
environment[VueAppEnvironmentKeys.VUE_APP_NAME],
|
||||||
return createProjectInformation(
|
|
||||||
metadata.name,
|
|
||||||
version,
|
version,
|
||||||
metadata.slogan,
|
environment[VueAppEnvironmentKeys.VUE_APP_SLOGAN],
|
||||||
metadata.repositoryUrl,
|
environment[VueAppEnvironmentKeys.VUE_APP_REPOSITORY_URL],
|
||||||
metadata.homepageUrl,
|
environment[VueAppEnvironmentKeys.VUE_APP_HOMEPAGE_URL],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProjectInformationFactory = (
|
export const VueAppEnvironmentKeys = {
|
||||||
...args: ConstructorArguments<typeof ProjectInformation>
|
VUE_APP_VERSION: 'VUE_APP_VERSION',
|
||||||
) => IProjectInformation;
|
VUE_APP_NAME: 'VUE_APP_NAME',
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,44 +14,45 @@ export class ExpressionRegexBuilder {
|
|||||||
.addRawRegex('\\s+');
|
.addRawRegex('\\s+');
|
||||||
}
|
}
|
||||||
|
|
||||||
public captureOptionalPipeline() {
|
public matchPipeline() {
|
||||||
return this
|
return this
|
||||||
.addRawRegex('((?:\\|\\s*\\b[a-zA-Z]+\\b\\s*)*)');
|
.expectZeroOrMoreWhitespaces()
|
||||||
|
.addRawRegex('(\\|\\s*.+?)?');
|
||||||
}
|
}
|
||||||
|
|
||||||
public captureUntilWhitespaceOrPipe() {
|
public matchUntilFirstWhitespace() {
|
||||||
return this
|
return this
|
||||||
.addRawRegex('([^|\\s]+)');
|
.addRawRegex('([^|\\s]+)');
|
||||||
}
|
}
|
||||||
|
|
||||||
public captureMultilineAnythingExceptSurroundingWhitespaces() {
|
public matchMultilineAnythingExceptSurroundingWhitespaces() {
|
||||||
return this
|
return this
|
||||||
.expectOptionalWhitespaces()
|
.expectZeroOrMoreWhitespaces()
|
||||||
.addRawRegex('([\\s\\S]*\\S)')
|
.addRawRegex('([\\S\\s]+?)')
|
||||||
.expectOptionalWhitespaces();
|
.expectZeroOrMoreWhitespaces();
|
||||||
}
|
}
|
||||||
|
|
||||||
public expectExpressionStart() {
|
public expectExpressionStart() {
|
||||||
return this
|
return this
|
||||||
.expectCharacters('{{')
|
.expectCharacters('{{')
|
||||||
.expectOptionalWhitespaces();
|
.expectZeroOrMoreWhitespaces();
|
||||||
}
|
}
|
||||||
|
|
||||||
public expectExpressionEnd() {
|
public expectExpressionEnd() {
|
||||||
return this
|
return this
|
||||||
.expectOptionalWhitespaces()
|
.expectZeroOrMoreWhitespaces()
|
||||||
.expectCharacters('}}');
|
.expectCharacters('}}');
|
||||||
}
|
}
|
||||||
|
|
||||||
public expectOptionalWhitespaces() {
|
|
||||||
return this
|
|
||||||
.addRawRegex('\\s*');
|
|
||||||
}
|
|
||||||
|
|
||||||
public buildRegExp(): RegExp {
|
public buildRegExp(): RegExp {
|
||||||
return new RegExp(this.parts.join(''), 'g');
|
return new RegExp(this.parts.join(''), 'g');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private expectZeroOrMoreWhitespaces() {
|
||||||
|
return this
|
||||||
|
.addRawRegex('\\s*');
|
||||||
|
}
|
||||||
|
|
||||||
private addRawRegex(regex: string) {
|
private addRawRegex(regex: string) {
|
||||||
this.parts.push(regex);
|
this.parts.push(regex);
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ export class ParameterSubstitutionParser extends RegexParser {
|
|||||||
protected readonly regex = new ExpressionRegexBuilder()
|
protected readonly regex = new ExpressionRegexBuilder()
|
||||||
.expectExpressionStart()
|
.expectExpressionStart()
|
||||||
.expectCharacters('$')
|
.expectCharacters('$')
|
||||||
.captureUntilWhitespaceOrPipe() // First capture: Parameter name
|
.matchUntilFirstWhitespace() // First match: Parameter name
|
||||||
.expectOptionalWhitespaces()
|
.matchPipeline() // Second match: Pipeline
|
||||||
.captureOptionalPipeline() // Second capture: Pipeline
|
|
||||||
.expectExpressionEnd()
|
.expectExpressionEnd()
|
||||||
.buildRegExp();
|
.buildRegExp();
|
||||||
|
|
||||||
|
|||||||
@@ -1,222 +1,59 @@
|
|||||||
// eslint-disable-next-line max-classes-per-file
|
|
||||||
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
|
||||||
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
|
||||||
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||||
import { IExpression } from '../Expression/IExpression';
|
import { RegexParser, IPrimitiveExpression } from '../Parser/Regex/RegexParser';
|
||||||
import { ExpressionPosition } from '../Expression/ExpressionPosition';
|
|
||||||
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
|
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
|
||||||
|
|
||||||
export class WithParser implements IExpressionParser {
|
export class WithParser extends RegexParser {
|
||||||
public findExpressions(code: string): IExpression[] {
|
protected readonly regex = new ExpressionRegexBuilder()
|
||||||
if (!code) {
|
|
||||||
throw new Error('missing code');
|
|
||||||
}
|
|
||||||
return parseWithExpressions(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WithStatementType {
|
|
||||||
Start,
|
|
||||||
End,
|
|
||||||
ContextVariable,
|
|
||||||
}
|
|
||||||
|
|
||||||
type WithStatement = {
|
|
||||||
readonly type: WithStatementType.Start;
|
|
||||||
readonly parameterName: string;
|
|
||||||
readonly position: ExpressionPosition;
|
|
||||||
} | {
|
|
||||||
readonly type: WithStatementType.End;
|
|
||||||
readonly position: ExpressionPosition;
|
|
||||||
} | {
|
|
||||||
readonly type: WithStatementType.ContextVariable;
|
|
||||||
readonly position: ExpressionPosition;
|
|
||||||
readonly pipeline: string | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
function parseAllWithExpressions(
|
|
||||||
input: string,
|
|
||||||
): WithStatement[] {
|
|
||||||
const expressions = new Array<WithStatement>();
|
|
||||||
for (const match of input.matchAll(WithStatementStartRegEx)) {
|
|
||||||
expressions.push({
|
|
||||||
type: WithStatementType.Start,
|
|
||||||
parameterName: match[1],
|
|
||||||
position: createPosition(match),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (const match of input.matchAll(WithStatementEndRegEx)) {
|
|
||||||
expressions.push({
|
|
||||||
type: WithStatementType.End,
|
|
||||||
position: createPosition(match),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (const match of input.matchAll(ContextVariableWithPipelineRegEx)) {
|
|
||||||
expressions.push({
|
|
||||||
type: WithStatementType.ContextVariable,
|
|
||||||
position: createPosition(match),
|
|
||||||
pipeline: match[1],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return expressions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPosition(match: RegExpMatchArray): ExpressionPosition {
|
|
||||||
const startPos = match.index;
|
|
||||||
const endPos = startPos + match[0].length;
|
|
||||||
return new ExpressionPosition(startPos, endPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
class WithStatementBuilder {
|
|
||||||
private readonly contextVariables = new Array<{
|
|
||||||
readonly positionInScope: ExpressionPosition;
|
|
||||||
readonly pipeline: string | undefined;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
public addContextVariable(
|
|
||||||
absolutePosition: ExpressionPosition,
|
|
||||||
pipeline: string | undefined,
|
|
||||||
): void {
|
|
||||||
const positionInScope = new ExpressionPosition(
|
|
||||||
absolutePosition.start - this.startExpressionPosition.end,
|
|
||||||
absolutePosition.end - this.startExpressionPosition.end,
|
|
||||||
);
|
|
||||||
this.contextVariables.push({
|
|
||||||
positionInScope,
|
|
||||||
pipeline,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public buildExpression(endExpressionPosition: ExpressionPosition, input: string): IExpression {
|
|
||||||
const parameters = new FunctionParameterCollection();
|
|
||||||
parameters.addParameter(new FunctionParameter(this.parameterName, true));
|
|
||||||
const position = new ExpressionPosition(
|
|
||||||
this.startExpressionPosition.start,
|
|
||||||
endExpressionPosition.end,
|
|
||||||
);
|
|
||||||
const scope = input.substring(this.startExpressionPosition.end, endExpressionPosition.start);
|
|
||||||
return {
|
|
||||||
parameters,
|
|
||||||
position,
|
|
||||||
evaluate: (context) => {
|
|
||||||
const argumentValue = context.args.hasArgument(this.parameterName)
|
|
||||||
? context.args.getArgument(this.parameterName).argumentValue
|
|
||||||
: undefined;
|
|
||||||
if (!argumentValue) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const substitutedScope = this.substituteContextVariables(scope, (pipeline) => {
|
|
||||||
if (!pipeline) {
|
|
||||||
return argumentValue;
|
|
||||||
}
|
|
||||||
return context.pipelineCompiler.compile(argumentValue, pipeline);
|
|
||||||
});
|
|
||||||
return substitutedScope;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly startExpressionPosition: ExpressionPosition,
|
|
||||||
private readonly parameterName: string,
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private substituteContextVariables(
|
|
||||||
scope: string,
|
|
||||||
substituter: (pipeline: string) => string,
|
|
||||||
): string {
|
|
||||||
if (!this.contextVariables.length) {
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
let substitutedScope = '';
|
|
||||||
let scopeSubstrIndex = 0;
|
|
||||||
for (const contextVariable of this.contextVariables) {
|
|
||||||
substitutedScope += scope.substring(scopeSubstrIndex, contextVariable.positionInScope.start);
|
|
||||||
substitutedScope += substituter(contextVariable.pipeline);
|
|
||||||
scopeSubstrIndex = contextVariable.positionInScope.end;
|
|
||||||
}
|
|
||||||
substitutedScope += scope.substring(scopeSubstrIndex, scope.length);
|
|
||||||
return substitutedScope;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildErrorContext(code: string, statements: readonly WithStatement[]): string {
|
|
||||||
const formattedStatements = statements.map((s) => `- [${s.position.start}, ${s.position.end}] ${WithStatementType[s.type]}`).join('\n');
|
|
||||||
return [
|
|
||||||
'Code:', '---', code, '---',
|
|
||||||
'nStatements:', '---', formattedStatements, '---',
|
|
||||||
].join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseWithExpressions(input: string): IExpression[] {
|
|
||||||
const allStatements = parseAllWithExpressions(input);
|
|
||||||
const sortedStatements = allStatements
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => b.position.start - a.position.start);
|
|
||||||
const expressions = new Array<IExpression>();
|
|
||||||
const builders = new Array<WithStatementBuilder>();
|
|
||||||
const throwWithContext = (message: string) => {
|
|
||||||
throw new Error(`${message}\n${buildErrorContext(input, allStatements)}}`);
|
|
||||||
};
|
|
||||||
while (sortedStatements.length > 0) {
|
|
||||||
const statement = sortedStatements.pop();
|
|
||||||
if (!statement) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
switch (statement.type) { // eslint-disable-line default-case
|
|
||||||
case WithStatementType.Start:
|
|
||||||
builders.push(new WithStatementBuilder(
|
|
||||||
statement.position,
|
|
||||||
statement.parameterName,
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
case WithStatementType.ContextVariable:
|
|
||||||
if (builders.length === 0) {
|
|
||||||
throwWithContext('Context variable before `with` statement.');
|
|
||||||
}
|
|
||||||
builders[builders.length - 1].addContextVariable(statement.position, statement.pipeline);
|
|
||||||
break;
|
|
||||||
case WithStatementType.End:
|
|
||||||
if (builders.length === 0) {
|
|
||||||
throwWithContext('Redundant `end` statement, missing `with`?');
|
|
||||||
}
|
|
||||||
expressions.push(builders.pop().buildExpression(statement.position, input));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (builders.length > 0) {
|
|
||||||
throwWithContext('Missing `end` statement, forgot `{{ end }}?');
|
|
||||||
}
|
|
||||||
return expressions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ContextVariableWithPipelineRegEx = new ExpressionRegexBuilder()
|
|
||||||
// {{ . | pipeName }}
|
|
||||||
.expectExpressionStart()
|
|
||||||
.expectCharacters('.')
|
|
||||||
.expectOptionalWhitespaces()
|
|
||||||
.captureOptionalPipeline() // First capture: pipeline
|
|
||||||
.expectExpressionEnd()
|
|
||||||
.buildRegExp();
|
|
||||||
|
|
||||||
const WithStatementStartRegEx = new ExpressionRegexBuilder()
|
|
||||||
// {{ with $parameterName }}
|
// {{ with $parameterName }}
|
||||||
.expectExpressionStart()
|
.expectExpressionStart()
|
||||||
.expectCharacters('with')
|
.expectCharacters('with')
|
||||||
.expectOneOrMoreWhitespaces()
|
.expectOneOrMoreWhitespaces()
|
||||||
.expectCharacters('$')
|
.expectCharacters('$')
|
||||||
.captureUntilWhitespaceOrPipe() // First capture: parameter name
|
.matchUntilFirstWhitespace() // First match: parameter name
|
||||||
.expectExpressionEnd()
|
.expectExpressionEnd()
|
||||||
.expectOptionalWhitespaces()
|
// ...
|
||||||
.buildRegExp();
|
.matchMultilineAnythingExceptSurroundingWhitespaces() // Second match: Scope text
|
||||||
|
|
||||||
const WithStatementEndRegEx = new ExpressionRegexBuilder()
|
|
||||||
// {{ end }}
|
// {{ end }}
|
||||||
.expectOptionalWhitespaces()
|
|
||||||
.expectExpressionStart()
|
.expectExpressionStart()
|
||||||
.expectCharacters('end')
|
.expectCharacters('end')
|
||||||
.expectOptionalWhitespaces()
|
|
||||||
.expectExpressionEnd()
|
.expectExpressionEnd()
|
||||||
.buildRegExp();
|
.buildRegExp();
|
||||||
|
|
||||||
|
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||||
|
const parameterName = match[1];
|
||||||
|
const scopeText = match[2];
|
||||||
|
return {
|
||||||
|
parameters: [new FunctionParameter(parameterName, true)],
|
||||||
|
evaluator: (context) => {
|
||||||
|
const argumentValue = context.args.hasArgument(parameterName)
|
||||||
|
? context.args.getArgument(parameterName).argumentValue
|
||||||
|
: undefined;
|
||||||
|
if (!argumentValue) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return replaceEachScopeSubstitution(scopeText, (pipeline) => {
|
||||||
|
if (!pipeline) {
|
||||||
|
return argumentValue;
|
||||||
|
}
|
||||||
|
return context.pipelineCompiler.compile(argumentValue, pipeline);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScopeSubstitutionRegEx = new ExpressionRegexBuilder()
|
||||||
|
// {{ . | pipeName }}
|
||||||
|
.expectExpressionStart()
|
||||||
|
.expectCharacters('.')
|
||||||
|
.matchPipeline() // First match: pipeline
|
||||||
|
.expectExpressionEnd()
|
||||||
|
.buildRegExp();
|
||||||
|
|
||||||
|
function replaceEachScopeSubstitution(scopeText: string, replacer: (pipeline: string) => string) {
|
||||||
|
// Not using /{{\s*.\s*(?:(\|\s*[^{}]*?)\s*)?}}/g for not matching brackets,
|
||||||
|
// but instead letting the pipeline compiler to fail on those.
|
||||||
|
return scopeText.replaceAll(ScopeSubstitutionRegEx, (_$, match1) => {
|
||||||
|
return replacer(match1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { CompiledCode } from '../CompiledCode';
|
|
||||||
|
|
||||||
export interface CodeSegmentMerger {
|
|
||||||
mergeCodeParts(codeSegments: readonly CompiledCode[]): CompiledCode;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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,9 +0,0 @@
|
|||||||
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,10 +1,149 @@
|
|||||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||||
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||||
|
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
||||||
|
import { IExpressionsCompiler } from '../../../Expressions/IExpressionsCompiler';
|
||||||
|
import { ExpressionsCompiler } from '../../../Expressions/ExpressionsCompiler';
|
||||||
|
import { ISharedFunction, IFunctionCode } from '../../ISharedFunction';
|
||||||
import { FunctionCall } from '../FunctionCall';
|
import { FunctionCall } from '../FunctionCall';
|
||||||
import { CompiledCode } from './CompiledCode';
|
import { FunctionCallArgumentCollection } from '../Argument/FunctionCallArgumentCollection';
|
||||||
|
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
||||||
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
|
|
||||||
export interface FunctionCallCompiler {
|
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
||||||
compileFunctionCalls(
|
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
||||||
calls: readonly FunctionCall[],
|
|
||||||
functions: ISharedFunctionCollection,
|
protected constructor(
|
||||||
): CompiledCode;
|
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public compileCall(
|
||||||
|
calls: IFunctionCall[],
|
||||||
|
functions: ISharedFunctionCollection,
|
||||||
|
): ICompiledCode {
|
||||||
|
if (!functions) { throw new Error('missing functions'); }
|
||||||
|
if (!calls) { throw new Error('missing calls'); }
|
||||||
|
if (calls.some((f) => !f)) { throw new Error('missing function call'); }
|
||||||
|
const context: ICompilationContext = {
|
||||||
|
allFunctions: functions,
|
||||||
|
callSequence: calls,
|
||||||
|
expressionsCompiler: this.expressionsCompiler,
|
||||||
|
};
|
||||||
|
const code = compileCallSequence(context);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICompilationContext {
|
||||||
|
allFunctions: ISharedFunctionCollection;
|
||||||
|
callSequence: readonly IFunctionCall[];
|
||||||
|
expressionsCompiler: IExpressionsCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICompiledFunctionCall {
|
||||||
|
readonly code: string;
|
||||||
|
readonly revertCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileCallSequence(context: ICompilationContext): ICompiledFunctionCall {
|
||||||
|
const compiledFunctions = context.callSequence
|
||||||
|
.flatMap((call) => compileSingleCall(call, context));
|
||||||
|
return {
|
||||||
|
code: merge(compiledFunctions.map((f) => f.code)),
|
||||||
|
revertCode: merge(compiledFunctions.map((f) => f.revertCode)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileSingleCall(
|
||||||
|
call: IFunctionCall,
|
||||||
|
context: ICompilationContext,
|
||||||
|
): ICompiledFunctionCall[] {
|
||||||
|
const func = context.allFunctions.getFunctionByName(call.functionName);
|
||||||
|
ensureThatCallArgumentsExistInParameterDefinition(func, call.args);
|
||||||
|
if (func.body.code) { // Function with inline code
|
||||||
|
const compiledCode = compileCode(func.body.code, call.args, context.expressionsCompiler);
|
||||||
|
return [compiledCode];
|
||||||
|
}
|
||||||
|
// Function with inner calls
|
||||||
|
return func.body.calls
|
||||||
|
.map((innerCall) => {
|
||||||
|
const compiledArgs = compileArgs(innerCall.args, call.args, context.expressionsCompiler);
|
||||||
|
const compiledCall = new FunctionCall(innerCall.functionName, compiledArgs);
|
||||||
|
return compileSingleCall(compiledCall, context);
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileCode(
|
||||||
|
code: IFunctionCode,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
compiler: IExpressionsCompiler,
|
||||||
|
): ICompiledFunctionCall {
|
||||||
|
return {
|
||||||
|
code: compiler.compileExpressions(code.execute, args),
|
||||||
|
revertCode: compiler.compileExpressions(code.revert, args),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileArgs(
|
||||||
|
argsToCompile: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
compiler: IExpressionsCompiler,
|
||||||
|
): IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
return argsToCompile
|
||||||
|
.getAllParameterNames()
|
||||||
|
.map((parameterName) => {
|
||||||
|
const { argumentValue } = argsToCompile.getArgument(parameterName);
|
||||||
|
const compiledValue = compiler.compileExpressions(argumentValue, args);
|
||||||
|
return new FunctionCallArgument(parameterName, compiledValue);
|
||||||
|
})
|
||||||
|
.reduce((compiledArgs, arg) => {
|
||||||
|
compiledArgs.addArgument(arg);
|
||||||
|
return compiledArgs;
|
||||||
|
}, new FunctionCallArgumentCollection());
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge(codeParts: readonly string[]): string {
|
||||||
|
return codeParts
|
||||||
|
.filter((part) => part?.length > 0)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureThatCallArgumentsExistInParameterDefinition(
|
||||||
|
func: ISharedFunction,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
): void {
|
||||||
|
const callArgumentNames = args.getAllParameterNames();
|
||||||
|
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
|
||||||
|
const unexpectedParameters = findUnexpectedParameters(callArgumentNames, functionParameterNames);
|
||||||
|
throwIfNotEmpty(func.name, unexpectedParameters, functionParameterNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findUnexpectedParameters(
|
||||||
|
callArgumentNames: string[],
|
||||||
|
functionParameterNames: string[],
|
||||||
|
): string[] {
|
||||||
|
if (!callArgumentNames.length && !functionParameterNames.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return callArgumentNames
|
||||||
|
.filter((callParam) => !functionParameterNames.includes(callParam));
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwIfNotEmpty(
|
||||||
|
functionName: string,
|
||||||
|
unexpectedParameters: string[],
|
||||||
|
expectedParameters: string[],
|
||||||
|
) {
|
||||||
|
if (!unexpectedParameters.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
// eslint-disable-next-line prefer-template
|
||||||
|
`Function "${functionName}" has unexpected parameter(s) provided: `
|
||||||
|
+ `"${unexpectedParameters.join('", "')}"`
|
||||||
|
+ '. Expected parameter(s): '
|
||||||
|
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
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,4 +1,4 @@
|
|||||||
export interface CompiledCode {
|
export interface ICompiledCode {
|
||||||
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 { IFunctionCall } from '../IFunctionCall';
|
||||||
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
|
|
||||||
|
export interface IFunctionCallCompiler {
|
||||||
|
compileCall(
|
||||||
|
calls: IFunctionCall[],
|
||||||
|
functions: ISharedFunctionCollection): ICompiledCode;
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
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'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { FunctionCall } from '../../FunctionCall';
|
|
||||||
import { CompiledCode } from '../CompiledCode';
|
|
||||||
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
|
||||||
|
|
||||||
export interface SingleCallCompiler {
|
|
||||||
compileSingleCall(
|
|
||||||
call: FunctionCall,
|
|
||||||
context: FunctionCallCompilationContext,
|
|
||||||
): CompiledCode[];
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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[],
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
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());
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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,6 +1,16 @@
|
|||||||
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IFunctionCall } from './IFunctionCall';
|
||||||
|
|
||||||
export interface FunctionCall {
|
export class FunctionCall implements IFunctionCall {
|
||||||
readonly functionName: string;
|
constructor(
|
||||||
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
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,10 +1,10 @@
|
|||||||
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
||||||
import { FunctionCall } from './FunctionCall';
|
import { IFunctionCall } from './IFunctionCall';
|
||||||
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
||||||
import { ParsedFunctionCall } from './ParsedFunctionCall';
|
import { FunctionCall } from './FunctionCall';
|
||||||
|
|
||||||
export function parseFunctionCalls(calls: FunctionCallsData): FunctionCall[] {
|
export function parseFunctionCalls(calls: FunctionCallsData): IFunctionCall[] {
|
||||||
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): FunctionCall {
|
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
||||||
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 ParsedFunctionCall(call.function, callArgs);
|
return new FunctionCall(call.function, callArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseArgs(
|
function parseArgs(
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
|
export interface IFunctionCall {
|
||||||
|
readonly functionName: string;
|
||||||
|
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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 { FunctionCall } from './Call/FunctionCall';
|
import { IFunctionCall } from './Call/IFunctionCall';
|
||||||
|
|
||||||
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 | undefined;
|
readonly code: IFunctionCode;
|
||||||
readonly calls: readonly FunctionCall[] | undefined;
|
readonly calls: readonly IFunctionCall[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FunctionBodyType {
|
export enum FunctionBodyType {
|
||||||
|
|||||||
@@ -2,5 +2,4 @@ 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 { FunctionCall } from './Call/FunctionCall';
|
import { IFunctionCall } from './Call/IFunctionCall';
|
||||||
|
|
||||||
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 FunctionCall[],
|
callSequence: readonly IFunctionCall[],
|
||||||
): 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 FunctionCall[],
|
content: IFunctionCode | readonly IFunctionCall[],
|
||||||
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 FunctionCall[] : undefined,
|
calls: bodyType === FunctionBodyType.Calls ? content as readonly IFunctionCall[] : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,6 @@ 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 { FunctionCallSequenceCompiler } from './Function/Call/Compiler/FunctionCallSequenceCompiler';
|
import { IFunctionCallCompiler } from './Function/Call/Compiler/IFunctionCallCompiler';
|
||||||
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 { CompiledCode } from './Function/Call/Compiler/CompiledCode';
|
import { ICompiledCode } from './Function/Call/Compiler/ICompiledCode';
|
||||||
|
|
||||||
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: FunctionCallCompiler = FunctionCallSequenceCompiler.instance,
|
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.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.compileFunctionCalls(calls, this.functions);
|
const compiledCode = this.callCompiler.compileCall(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: CompiledCode, validator: ICodeValidator): void {
|
function validateCompiledCode(compiledCode: ICompiledCode, 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()]),
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
# Structure is documented in "docs/collection-files.md"
|
# Structure documented in "docs/collection-files.md"
|
||||||
os: macos
|
os: macos
|
||||||
scripting:
|
scripting:
|
||||||
language: shellscript
|
language: shellscript
|
||||||
@@ -48,18 +48,18 @@ actions:
|
|||||||
# on main HDD
|
# on main HDD
|
||||||
sudo rm -rfv ~/.Trash/* &>/dev/null
|
sudo rm -rfv ~/.Trash/* &>/dev/null
|
||||||
-
|
-
|
||||||
name: Clear system cache
|
name: Clear system cache files
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
code: |-
|
||||||
sudo rm -rfv /Library/Caches/* &>/dev/null
|
sudo rm -rfv /Library/Caches/* &>/dev/null
|
||||||
sudo rm -rfv /System/Library/Caches/* &>/dev/null
|
sudo rm -rfv /System/Library/Caches/* &>/dev/null
|
||||||
sudo rm -rfv ~/Library/Caches/* &>/dev/null
|
sudo rm -rfv ~/Library/Caches/* &>/dev/null
|
||||||
-
|
-
|
||||||
category: Clear operating system logs
|
category: Clear OS logs
|
||||||
recommend: strict
|
recommend: strict
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
category: Clear unified diagnostic logs
|
category: Clear unified logs (diagnostics)
|
||||||
docs: https://developer.apple.com/documentation/os/logging
|
docs: https://developer.apple.com/documentation/os/logging
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
@@ -69,7 +69,7 @@ actions:
|
|||||||
sudo rm -rfv /private/var/db/diagnostics/*
|
sudo rm -rfv /private/var/db/diagnostics/*
|
||||||
sudo rm -rfv /var/db/diagnostics/*
|
sudo rm -rfv /var/db/diagnostics/*
|
||||||
-
|
-
|
||||||
name: Clear shared cache strings data
|
name: Clear shared-cache strings data
|
||||||
docs:
|
docs:
|
||||||
- https://eclecticlight.co/2017/09/23/sierras-unified-log-evolves-more-persistent-and-a-valuable-log-log/
|
- https://eclecticlight.co/2017/09/23/sierras-unified-log-evolves-more-persistent-and-a-valuable-log-log/
|
||||||
- https://github.com/privacysexy-forks/dtformats/blob/main/documentation/Apple%20Unified%20Logging%20and%20Activity%20Tracing%20formats.asciidoc
|
- https://github.com/privacysexy-forks/dtformats/blob/main/documentation/Apple%20Unified%20Logging%20and%20Activity%20Tracing%20formats.asciidoc
|
||||||
@@ -77,7 +77,7 @@ actions:
|
|||||||
sudo rm -rfv /private/var/db/uuidtext/
|
sudo rm -rfv /private/var/db/uuidtext/
|
||||||
sudo rm -rfv /var/db/uuidtext/
|
sudo rm -rfv /var/db/uuidtext/
|
||||||
-
|
-
|
||||||
category: Clear system logs
|
category: Clear system logs (/var/log/)
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Clear Apple System Logs (ASL)
|
name: Clear Apple System Logs (ASL)
|
||||||
@@ -94,7 +94,7 @@ actions:
|
|||||||
docs: https://discussions.apple.com/thread/1829842
|
docs: https://discussions.apple.com/thread/1829842
|
||||||
code: sudo rm -fv /var/log/install.log
|
code: sudo rm -fv /var/log/install.log
|
||||||
-
|
-
|
||||||
name: Clear all system logs in `/var/log/` directory
|
name: Clear all system logs
|
||||||
docs: https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
|
docs: https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
|
||||||
code: sudo rm -rfv /var/log/* # Clears including /var/log/system.log
|
code: sudo rm -rfv /var/log/* # Clears including /var/log/system.log
|
||||||
-
|
-
|
||||||
@@ -105,7 +105,7 @@ actions:
|
|||||||
name: Clear Mail logs
|
name: Clear Mail logs
|
||||||
code: rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/*
|
code: rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/*
|
||||||
-
|
-
|
||||||
name: Clear user activity audit logs (login, logout, authentication, etc.)
|
name: Clear audit logs (login, logout, authentication and other user activity)
|
||||||
docs:
|
docs:
|
||||||
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
||||||
- http://macadmins.psu.edu/wp-content/uploads/sites/24696/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
|
- http://macadmins.psu.edu/wp-content/uploads/sites/24696/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
|
||||||
@@ -113,7 +113,7 @@ actions:
|
|||||||
sudo rm -rfv /var/audit/*
|
sudo rm -rfv /var/audit/*
|
||||||
sudo rm -rfv /private/var/audit/*
|
sudo rm -rfv /private/var/audit/*
|
||||||
-
|
-
|
||||||
name: Clear user report logs
|
name: Clear user logs (user reports)
|
||||||
docs:
|
docs:
|
||||||
- https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
|
- https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
|
||||||
- https://apple.stackexchange.com/questions/272929/is-it-safe-to-delete-the-content-of-library-logs
|
- https://apple.stackexchange.com/questions/272929/is-it-safe-to-delete-the-content-of-library-logs
|
||||||
@@ -134,15 +134,15 @@ actions:
|
|||||||
category: Clear browser history
|
category: Clear browser history
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
category: Clear Chrome history
|
category: Clear Google Chrome history
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Clear Chrome browsing history
|
name: Clear Google Chrome browsing history
|
||||||
code: |-
|
code: |-
|
||||||
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History &>/dev/null
|
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History &>/dev/null
|
||||||
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History-journal &>/dev/null
|
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History-journal &>/dev/null
|
||||||
-
|
-
|
||||||
name: Clear Chrome cache
|
name: Google Chrome Cache Files
|
||||||
code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null
|
code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null
|
||||||
-
|
-
|
||||||
category: Clear Safari history
|
category: Clear Safari history
|
||||||
@@ -165,7 +165,7 @@ actions:
|
|||||||
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||||
code: rm -f ~/Library/Safari/Downloads.plist
|
code: rm -f ~/Library/Safari/Downloads.plist
|
||||||
-
|
-
|
||||||
name: Clear Safari frequently visited sites
|
name: Clear Safari top sites
|
||||||
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
code: rm -f ~/Library/Safari/TopSites.plist
|
code: rm -f ~/Library/Safari/TopSites.plist
|
||||||
-
|
-
|
||||||
@@ -182,7 +182,7 @@ actions:
|
|||||||
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
code: rm -f ~/Library/Caches/com.apple.Safari/Cache.db
|
code: rm -f ~/Library/Caches/com.apple.Safari/Cache.db
|
||||||
-
|
-
|
||||||
name: Clear Safari URL bar web page icons
|
name: Clear Safari web page icons displayed on URL bar
|
||||||
docs:
|
docs:
|
||||||
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
- https://lifehacker.com/safaris-private-browsing-mode-saves-urls-in-an-easily-a-1691944343
|
- https://lifehacker.com/safaris-private-browsing-mode-saves-urls-in-an-easily-a-1691944343
|
||||||
@@ -194,11 +194,11 @@ actions:
|
|||||||
- https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/
|
- https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/
|
||||||
code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews
|
code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews
|
||||||
-
|
-
|
||||||
name: Clear Safari history copy
|
name: Clear copy of the Safari history
|
||||||
docs: https://forensicsfromthesausagefactory.blogspot.com/2010/06/safari-history-spotlight-webhistory.html
|
docs: https://forensicsfromthesausagefactory.blogspot.com/2010/06/safari-history-spotlight-webhistory.html
|
||||||
code: rm -rfv ~/Library/Caches/Metadata/Safari/History
|
code: rm -rfv ~/Library/Caches/Metadata/Safari/History
|
||||||
-
|
-
|
||||||
name: Clear search term history embedded in Safari preferences
|
name: Clear search history embedded in Safari preferences
|
||||||
docs: https://krypted.com/tag/recentsearchstrings/
|
docs: https://krypted.com/tag/recentsearchstrings/
|
||||||
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
|
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
|
||||||
-
|
-
|
||||||
@@ -215,11 +215,11 @@ actions:
|
|||||||
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||||
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plist
|
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plist
|
||||||
-
|
-
|
||||||
name: Clear allowed URLs for Safari notifications
|
name: Clear URLs that are allowed to display notifications in Safari
|
||||||
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||||
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
|
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
|
||||||
-
|
-
|
||||||
name: Clear Safari preferences for downloads, geolocation, pop-ups, and autoplay per site
|
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays
|
||||||
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||||
code: rm -f ~/Library/Safari/PerSitePreferences.db
|
code: rm -f ~/Library/Safari/PerSitePreferences.db
|
||||||
-
|
-
|
||||||
@@ -231,15 +231,15 @@ actions:
|
|||||||
sudo rm -rf ~/Library/Caches/Mozilla/
|
sudo rm -rf ~/Library/Caches/Mozilla/
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite
|
||||||
-
|
-
|
||||||
name: Clear Firefox form history
|
name: Delete Firefox form history
|
||||||
code: |-
|
code: |-
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.sqlite
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.sqlite
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.dat
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.dat
|
||||||
-
|
-
|
||||||
name: Clear Firefox site preferences
|
name: Delete Firefox site preferences
|
||||||
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/content-prefs.sqlite
|
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/content-prefs.sqlite
|
||||||
-
|
-
|
||||||
name: Clear Firefox session restore data (loads after the browser closes or crashes)
|
name: Delete Firefox session restore data (loads after the browser closes or crashes)
|
||||||
code: |-
|
code: |-
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionCheckpoints.json
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionCheckpoints.json
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore*.js*
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore*.js*
|
||||||
@@ -250,7 +250,7 @@ actions:
|
|||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.bak*
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.bak*
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
|
||||||
-
|
-
|
||||||
name: Clear Firefox passwords
|
name: Delete Firefox passwords
|
||||||
docs: https://web.archive.org/web/20210425202923/http://kb.mozillazine.org/Password_Manager
|
docs: https://web.archive.org/web/20210425202923/http://kb.mozillazine.org/Password_Manager
|
||||||
code: |-
|
code: |-
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
|
||||||
@@ -259,20 +259,20 @@ actions:
|
|||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json
|
||||||
-
|
-
|
||||||
name: Clear Firefox HTML5 cookies
|
name: Delete Firefox HTML5 cookies
|
||||||
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite
|
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite
|
||||||
-
|
-
|
||||||
name: Clear Firefox crash reports
|
name: Delete Firefox crash reports
|
||||||
code: |-
|
code: |-
|
||||||
rm -rfv ~/Library/Application\ Support/Firefox/Crash\ Reports/
|
rm -rfv ~/Library/Application\ Support/Firefox/Crash\ Reports/
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/minidumps/*.dmp
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/minidumps/*.dmp
|
||||||
-
|
-
|
||||||
name: Clear Firefox backup files
|
name: Delete Firefox backup files
|
||||||
code: |-
|
code: |-
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.json
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.json
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.jsonlz4
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.jsonlz4
|
||||||
-
|
-
|
||||||
name: Clear Firefox cookies
|
name: Delete Firefox cookies
|
||||||
code: |-
|
code: |-
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite
|
||||||
@@ -280,7 +280,7 @@ actions:
|
|||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-wal
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-wal
|
||||||
rm -rfv ~/Library/Application\ Support/Firefox/Profiles/*/storage/default/http*
|
rm -rfv ~/Library/Application\ Support/Firefox/Profiles/*/storage/default/http*
|
||||||
-
|
-
|
||||||
category: Clear third-party application data
|
category: Clear third party application data
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Clear Adobe cache
|
name: Clear Adobe cache
|
||||||
@@ -290,18 +290,18 @@ actions:
|
|||||||
name: Clear Gradle cache
|
name: Clear Gradle cache
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
code: |-
|
||||||
if [ -d "~/.gradle/caches" ]; then
|
if [ -d "/Users/${HOST}/.gradle/caches" ]; then
|
||||||
rm -rfv ~/.gradle/caches/ &> /dev/null
|
rm -rfv ~/.gradle/caches/ &> /dev/null
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Clear Dropbox cache
|
name: Clear Dropbox cache
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: |-
|
code: |-
|
||||||
if [ -d "~/Dropbox/.dropbox.cache" ]; then
|
if [ -d "/Users/${HOST}/Dropbox" ]; then
|
||||||
sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null
|
sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Clear Google Drive File Stream cache
|
name: Clear Google Drive file stream cache
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: |-
|
code: |-
|
||||||
killall "Google Drive File Stream"
|
killall "Google Drive File Stream"
|
||||||
@@ -323,54 +323,21 @@ actions:
|
|||||||
brew tap --repair &>/dev/null
|
brew tap --repair &>/dev/null
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Clear old Ruby gem versions
|
name: Clear any old versions of Ruby gems
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
code: |-
|
||||||
if type "gem" &> /dev/null; then
|
if type "gem" &> /dev/null; then
|
||||||
gem cleanup &>/dev/null
|
gem cleanup &>/dev/null
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Clear unused Docker data
|
name: Clear Docker
|
||||||
recommend: strict
|
recommend: strict
|
||||||
docs: |-
|
|
||||||
This script frees up disk space, but also improves user privacy by:
|
|
||||||
|
|
||||||
1. **Removal of stopped containers**: Containers often run applications or services that might process sensitive
|
|
||||||
or personal data. Even if a container is stopped, its filesystem remains intact, and potentially sensitive data inside
|
|
||||||
it can be accessed. By removing stopped containers, we eliminate this potential privacy risk.
|
|
||||||
|
|
||||||
2. **Deletion of unused images**: Images can sometimes contain sensitive information, especially if they were built
|
|
||||||
from `Dockerfile`s that copied local files or were used in scenarios where sensitive data was processed. Deleting unused
|
|
||||||
images ensures that any inadvertent sensitive information embedded in those images is eradicated.
|
|
||||||
|
|
||||||
3. **Cleanup of network configurations**: Networks, especially custom ones, can contain configurations that reveal details
|
|
||||||
about system architecture, inter-container communication, or even hardcoded secrets. Removing unused networks mitigates
|
|
||||||
risks associated with lingering, outdated, or insecure configurations.
|
|
||||||
|
|
||||||
4. **Elimination of build cache**: The Docker build process uses a cache to speed up image creation. This cache can contain
|
|
||||||
remnants of previous builds, including potentially sensitive data or files. Pruning the build cache ensures that these remnants
|
|
||||||
are deleted, further safeguarding privacy.
|
|
||||||
|
|
||||||
5. **Footprint reduction**: By consistently pruning unused Docker objects, the overall footprint of Docker on the system is
|
|
||||||
reduced. This makes it harder for malicious actors to exploit any lingering or overlooked vulnerabilities in the system or Docker
|
|
||||||
itself.
|
|
||||||
|
|
||||||
This script runs `docker system prune -af` command to clean up unused Docker data [1].
|
|
||||||
|
|
||||||
Specifically, the command will [1]:
|
|
||||||
|
|
||||||
- Remove all stopped containers.
|
|
||||||
- Remove all networks not used by at least one container.
|
|
||||||
- Remove all images not used by any container.
|
|
||||||
- Remove all build cache.
|
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20230810171526/https://docs.docker.com/engine/reference/commandline/system_prune/ "docker system prune | Docker Documentation"
|
|
||||||
code: |-
|
code: |-
|
||||||
if type "docker" &> /dev/null; then
|
if type "docker" &> /dev/null; then
|
||||||
docker system prune -af
|
docker system prune -af
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Clear Pyenv-Virtualenv cache
|
name: Clear Pyenv-VirtualEnv cache
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
code: |-
|
||||||
if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then
|
if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then
|
||||||
@@ -392,22 +359,22 @@ actions:
|
|||||||
yarn cache clean --force
|
yarn cache clean --force
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
category: Clear iOS usage data
|
category: iOS Cleanup
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Clear iOS app copies from iTunes
|
name: Clear iOS applications
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null
|
code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null
|
||||||
-
|
-
|
||||||
name: Clear iOS photo cache
|
name: Clear iOS photo caches
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/*
|
code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/*
|
||||||
-
|
-
|
||||||
name: Clear iOS Device Backups
|
name: Remove iOS Device Backups
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null
|
code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null
|
||||||
-
|
-
|
||||||
name: Clear iOS simulators
|
name: Clear iOS Simulators
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
code: |-
|
||||||
if type "xcrun" &>/dev/null; then
|
if type "xcrun" &>/dev/null; then
|
||||||
@@ -418,7 +385,7 @@ actions:
|
|||||||
xcrun simctl erase all
|
xcrun simctl erase all
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Clear list of connected iOS devices
|
name: Clear the list of iOS devices connected
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
code: |-
|
||||||
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
|
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
|
||||||
@@ -427,7 +394,7 @@ actions:
|
|||||||
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
|
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
|
||||||
sudo rm -rfv /var/db/lockdown/*
|
sudo rm -rfv /var/db/lockdown/*
|
||||||
-
|
-
|
||||||
name: Clear Xcode's derived data and archives
|
name: Clear XCode Derived Data and Archives
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
code: |-
|
||||||
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null
|
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null
|
||||||
@@ -440,50 +407,50 @@ actions:
|
|||||||
sudo dscacheutil -flushcache
|
sudo dscacheutil -flushcache
|
||||||
sudo killall -HUP mDNSResponder
|
sudo killall -HUP mDNSResponder
|
||||||
-
|
-
|
||||||
name: Clear inactive memory
|
name: Purge inactive memory
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: sudo purge
|
code: sudo purge
|
||||||
-
|
-
|
||||||
category: Clear all privacy permissions for applications
|
category: Reset privacy permissions for all applications
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Clear "camera" permissions
|
name: Reset camera permissions
|
||||||
code: tccutil reset Camera
|
code: tccutil reset Camera
|
||||||
-
|
-
|
||||||
name: Clear "microphone" permissions
|
name: Reset microphone permissions
|
||||||
code: tccutil reset Microphone
|
code: tccutil reset Microphone
|
||||||
-
|
-
|
||||||
name: Clear "accessibility" permissions
|
name: Reset accessibility permissions
|
||||||
code: tccutil reset Accessibility
|
code: tccutil reset Accessibility
|
||||||
-
|
-
|
||||||
name: Clear "screen capture" permissions
|
name: Reset screen capture permissions
|
||||||
code: tccutil reset ScreenCapture
|
code: tccutil reset ScreenCapture
|
||||||
-
|
-
|
||||||
name: Clear "reminders" permissions
|
name: Reset reminders permissions
|
||||||
code: tccutil reset Reminders
|
code: tccutil reset Reminders
|
||||||
-
|
-
|
||||||
name: Clear "photos" permissions
|
name: Reset photos permissions
|
||||||
code: tccutil reset Photos
|
code: tccutil reset Photos
|
||||||
-
|
-
|
||||||
name: Clear "calendar" permissions
|
name: Reset calendar permissions
|
||||||
code: tccutil reset Calendar
|
code: tccutil reset Calendar
|
||||||
-
|
-
|
||||||
name: Clear "full disk access" permissions
|
name: Reset full disk access permissions
|
||||||
code: tccutil reset SystemPolicyAllFiles
|
code: tccutil reset SystemPolicyAllFiles
|
||||||
-
|
-
|
||||||
name: Clear "contacts" permissions
|
name: Reset contacts permissions
|
||||||
code: tccutil reset SystemPolicyAllFiles
|
code: tccutil reset SystemPolicyAllFiles
|
||||||
-
|
-
|
||||||
name: Clear "desktop folder" permissions
|
name: Reset desktop folder permissions
|
||||||
code: tccutil reset SystemPolicyDesktopFolder
|
code: tccutil reset SystemPolicyDesktopFolder
|
||||||
-
|
-
|
||||||
name: Clear "documents folder" permissions
|
name: Reset documents folder permissions
|
||||||
code: tccutil reset SystemPolicyDocumentsFolder
|
code: tccutil reset SystemPolicyDocumentsFolder
|
||||||
-
|
-
|
||||||
name: Clear "downloads" permissions
|
name: Reset downloads permissions
|
||||||
code: tccutil reset SystemPolicyDownloadsFolder
|
code: tccutil reset SystemPolicyDownloadsFolder
|
||||||
-
|
-
|
||||||
name: Clear all app permissions
|
name: Reset all app permissions
|
||||||
code: tccutil reset All
|
code: tccutil reset All
|
||||||
-
|
-
|
||||||
category: Configure programs
|
category: Configure programs
|
||||||
@@ -501,20 +468,20 @@ actions:
|
|||||||
sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled
|
sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled
|
||||||
sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry
|
sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry
|
||||||
-
|
-
|
||||||
name: Disable Microsoft Office telemetry
|
name: Disable Microsoft Office diagnostics data sending
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData
|
code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData
|
||||||
revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference
|
revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference
|
||||||
-
|
-
|
||||||
name: Remove Google Software Update service
|
name: Uninstall Google update
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
code: |-
|
||||||
googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall
|
googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall
|
||||||
if [ -f "$googleUpdateFile" ]; then
|
if [ -f "$googleUpdateFile" ]; then
|
||||||
$googleUpdateFile --nuke
|
$googleUpdateFile --nuke
|
||||||
echo 'Uninstalled Google update'
|
echo Uninstalled google update
|
||||||
else
|
else
|
||||||
echo 'Google update file does not exist'
|
echo Google update file does not exist
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Disable Homebrew user behavior analytics
|
name: Disable Homebrew user behavior analytics
|
||||||
@@ -547,12 +514,12 @@ actions:
|
|||||||
docs: |-
|
docs: |-
|
||||||
Parallels Desktop for Mac is software providing hardware virtualization for macOS [1].
|
Parallels Desktop for Mac is software providing hardware virtualization for macOS [1].
|
||||||
|
|
||||||
When you use it, it collects and shares your personal data to third parties [2]. Personal
|
When you use it, it collects and share your personal data to third parties [2]. Personal
|
||||||
data include IP address of your device, your broad geographical location (country, state
|
data include IP address of your device, your broad geographical location (country, state
|
||||||
(if applicable), and city) and used product [2].
|
(if applicable), and city) and used product [2].
|
||||||
|
|
||||||
It includes third-party advertisements [3] and automatic check for updates [4] by default.
|
It includes third-party ads [3] and automatic check for updates [4] by default. Both of these
|
||||||
Both of these behaviors communicate with online services that reveal personal data about you.
|
behaviors communicate with online services that reveal data about you.
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221012155943/https://en.wikipedia.org/wiki/Parallels_Desktop_for_Mac "Parallels Desktop for Mac - Wikipedia | en.wikipedia.org"
|
[1]: https://web.archive.org/web/20221012155943/https://en.wikipedia.org/wiki/Parallels_Desktop_for_Mac "Parallels Desktop for Mac - Wikipedia | en.wikipedia.org"
|
||||||
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
|
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
|
||||||
@@ -560,7 +527,7 @@ actions:
|
|||||||
[4]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
|
[4]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable Parallels Desktop advertisements
|
name: Turn off ads in Parallels Desktop
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs: |-
|
docs: |-
|
||||||
Parallels Desktop in-product notifications to show ads from Parallels or other third
|
Parallels Desktop in-product notifications to show ads from Parallels or other third
|
||||||
@@ -585,16 +552,16 @@ actions:
|
|||||||
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool no
|
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool no
|
||||||
defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes
|
defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes
|
||||||
-
|
-
|
||||||
category: Disable Parallels Desktop automatic updates
|
category: Disable Parallels Desktop auto-updates
|
||||||
docs: |-
|
docs: |-
|
||||||
Parallels Desktop by default checks for updates frequently and automatically downloads them [1].
|
Parallels Desktop by default checks for updates frequently and automatically downloads them [1].
|
||||||
This reveal personal data about you [2] without your control.
|
This reveal personal data about [2] you without your control.
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
|
[1]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
|
||||||
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
|
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable automatic downloads for Parallels Desktop updates
|
name: Disable automatically downloading Parallels Desktop updates
|
||||||
docs: |-
|
docs: |-
|
||||||
Automatic downloads are enabled by default, and this script disables automatic downloads.
|
Automatic downloads are enabled by default, and this script disables automatic downloads.
|
||||||
|
|
||||||
@@ -607,7 +574,7 @@ actions:
|
|||||||
code: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool no
|
code: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool no
|
||||||
revertCode: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool yes
|
revertCode: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool yes
|
||||||
-
|
-
|
||||||
name: Disable automatic checks for Parallels Desktop updates
|
name: Disable automatically checking for Parallels Desktop updates
|
||||||
docs: |-
|
docs: |-
|
||||||
Automatic checks are weekly by default, and this script disables the checks completely.
|
Automatic checks are weekly by default, and this script disables the checks completely.
|
||||||
|
|
||||||
@@ -626,7 +593,7 @@ actions:
|
|||||||
category: Configure Apple Remote Desktop
|
category: Configure Apple Remote Desktop
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable remote management service
|
name: Deactivate the Remote Management Service
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop
|
code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop
|
||||||
revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console
|
revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console
|
||||||
@@ -637,26 +604,26 @@ actions:
|
|||||||
sudo rm -rf /var/db/RemoteManagement
|
sudo rm -rf /var/db/RemoteManagement
|
||||||
sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist
|
sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist
|
||||||
defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist
|
defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist
|
||||||
sudo rm -rf /Library/Application\ Support/Apple/Remote\ Desktop/
|
sudo rm -r /Library/Application\ Support/Apple/Remote\ Desktop/
|
||||||
rm -r ~/Library/Application\ Support/Remote\ Desktop/
|
rm -r ~/Library/Application\ Support/Remote\ Desktop/
|
||||||
rm -r ~/Library/Containers/com.apple.RemoteDesktop
|
rm -r ~/Library/Containers/com.apple.RemoteDesktop
|
||||||
-
|
-
|
||||||
name: Disable online spell correction
|
name: Disable Internet based spell correction
|
||||||
code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false
|
code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false
|
||||||
revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled
|
revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled
|
||||||
-
|
-
|
||||||
name: Disable remote Apple events
|
name: Disable Remote Apple Events
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: sudo systemsetup -setremoteappleevents off
|
code: sudo systemsetup -setremoteappleevents off
|
||||||
revertCode: sudo systemsetup -setremoteappleevents on
|
revertCode: sudo systemsetup -setremoteappleevents on
|
||||||
-
|
-
|
||||||
name: Disable automatic storage of documents in iCloud Drive
|
name: Do not store documents to iCloud Drive by default
|
||||||
docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html
|
docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
|
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
|
||||||
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
|
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
|
||||||
-
|
-
|
||||||
name: Disable display of recent applications on Dock
|
name: Do not show recent items on dock
|
||||||
docs: https://developer.apple.com/documentation/devicemanagement/dock
|
docs: https://developer.apple.com/documentation/devicemanagement/dock
|
||||||
code: defaults write com.apple.dock show-recents -bool false
|
code: defaults write com.apple.dock show-recents -bool false
|
||||||
revertCode: defaults delete com.apple.dock show-recents
|
revertCode: defaults delete com.apple.dock show-recents
|
||||||
@@ -669,7 +636,7 @@ actions:
|
|||||||
category: Configure Siri
|
category: Configure Siri
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable participation in Siri data collection
|
name: Opt-out from Siri data collection
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: defaults write com.apple.assistant.support 'Siri Data Sharing Opt-In Status' -int 2
|
code: defaults write com.apple.assistant.support 'Siri Data Sharing Opt-In Status' -int 2
|
||||||
revertCode: defaults delete com.apple.assistant.support 'Siri Data Sharing Opt-In Status'
|
revertCode: defaults delete com.apple.assistant.support 'Siri Data Sharing Opt-In Status'
|
||||||
@@ -716,7 +683,7 @@ actions:
|
|||||||
launchctl enable "gui/$UID/com.apple.Siri.agent"
|
launchctl enable "gui/$UID/com.apple.Siri.agent"
|
||||||
sudo launchctl enable 'system/com.apple.Siri.agent'
|
sudo launchctl enable 'system/com.apple.Siri.agent'
|
||||||
if [ $(/usr/bin/csrutil status | awk '/status/ {print $5}' | sed 's/\.$//') = "enabled" ]; then
|
if [ $(/usr/bin/csrutil status | awk '/status/ {print $5}' | sed 's/\.$//') = "enabled" ]; then
|
||||||
>&2 echo 'This script requires SIP to be disabled. Read more: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection'
|
>&2 echo 'This script requires SIP to be disabled. Read more: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection''
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Disable "Do you want to enable Siri?" pop-up
|
name: Disable "Do you want to enable Siri?" pop-up
|
||||||
@@ -727,15 +694,15 @@ actions:
|
|||||||
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
|
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
|
||||||
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
|
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
|
||||||
-
|
-
|
||||||
category: Remove Siri from user interface
|
category: Hide Siri
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Remove Siri from menu bar
|
name: Hide Siri from menu bar
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 0
|
code: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 0
|
||||||
revertCode: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 1
|
revertCode: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 1
|
||||||
-
|
-
|
||||||
name: Remove Siri from status menu
|
name: Hide Siri from status menu
|
||||||
recommend: strict
|
recommend: strict
|
||||||
docs: https://derflounder.wordpress.com/2016/09/20/blocking-siri-on-macos-sierra/
|
docs: https://derflounder.wordpress.com/2016/09/20/blocking-siri-on-macos-sierra/
|
||||||
code: |-
|
code: |-
|
||||||
@@ -749,7 +716,7 @@ actions:
|
|||||||
code: sudo mdutil -i off -d /
|
code: sudo mdutil -i off -d /
|
||||||
revertCode: sudo mdutil -i on /
|
revertCode: sudo mdutil -i on /
|
||||||
-
|
-
|
||||||
name: Disable personalized advertisements and identifier tracking
|
name: Disable Personalized advertisements and identifier collection
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs: |-
|
docs: |-
|
||||||
This script enhances your privacy by deactivating Personalized Ads and disabling the collection
|
This script enhances your privacy by deactivating Personalized Ads and disabling the collection
|
||||||
@@ -779,7 +746,7 @@ actions:
|
|||||||
|
|
||||||
Please note: The `forceLimitAdTracking` key limits ad tracking [3] [4] and is found in CIS
|
Please note: The `forceLimitAdTracking` key limits ad tracking [3] [4] and is found in CIS
|
||||||
benchmarks for macOS [4]. However, the official macOS documentation specifies that it is
|
benchmarks for macOS [4]. However, the official macOS documentation specifies that it is
|
||||||
applicable only to iOS 7 and newer versions, not to macOS [3]. The key does not exist on the OS
|
applicable only to iOS 7 and later versions, not to macOS [3]. The key does not exist on the OS
|
||||||
by default.
|
by default.
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple"
|
[1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple"
|
||||||
@@ -822,7 +789,7 @@ actions:
|
|||||||
sudo defaults write /Library/Preferences/com.apple.alf globalstate -bool false
|
sudo defaults write /Library/Preferences/com.apple.alf globalstate -bool false
|
||||||
defaults write com.apple.security.firewall EnableFirewall -bool false
|
defaults write com.apple.security.firewall EnableFirewall -bool false
|
||||||
-
|
-
|
||||||
name: Enable firewall logging
|
name: Turn on firewall logging
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs:
|
docs:
|
||||||
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
|
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
|
||||||
@@ -834,7 +801,7 @@ actions:
|
|||||||
/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
|
/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
|
||||||
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -bool false
|
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -bool false
|
||||||
-
|
-
|
||||||
name: Enable stealth mode
|
name: Turn on stealth mode
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs:
|
docs:
|
||||||
- https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327
|
- https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327
|
||||||
@@ -849,16 +816,16 @@ actions:
|
|||||||
sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -bool false
|
sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -bool false
|
||||||
defaults write com.apple.security.firewall EnableStealthMode -bool false
|
defaults write com.apple.security.firewall EnableStealthMode -bool false
|
||||||
-
|
-
|
||||||
category: Disable automatic permission for incoming traffic in applications
|
category: Disable auto-permitting incoming traffic for apps
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable automatic incoming connections for signed apps
|
name: Prevent automatically allowing incoming connections to signed apps
|
||||||
docs: https://daiderd.com/nix-darwin/manual/index.html
|
docs: https://daiderd.com/nix-darwin/manual/index.html
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool false
|
code: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool false
|
||||||
revertCode: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool true
|
revertCode: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic incoming connections for downloaded signed apps
|
name: Prevent automatically allowing incoming connections to downloaded signed apps
|
||||||
docs: https://daiderd.com/nix-darwin/manual/index.html
|
docs: https://daiderd.com/nix-darwin/manual/index.html
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsignedenabled -bool false
|
code: sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsignedenabled -bool false
|
||||||
@@ -878,16 +845,16 @@ actions:
|
|||||||
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active -bool false
|
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active -bool false
|
||||||
revertCode: sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active
|
revertCode: sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active
|
||||||
-
|
-
|
||||||
category: Enable protective screen saver
|
category: Use screen saver for protection
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Enable password requirement for waking from sleep or screen saver
|
name: Require a password to wake the computer from sleep or screen saver
|
||||||
# The screen saver acts as a session lock and prevents unauthorized users from accessing the current user's account.
|
# The screen saver acts as a session lock and prevents unauthorized users from accessing the current user's account.
|
||||||
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230744
|
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230744
|
||||||
code: sudo defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true
|
code: sudo defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true
|
||||||
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword
|
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword
|
||||||
-
|
-
|
||||||
name: Enable session lock five seconds after screen saver initiation
|
name: Initiate session lock five seconds after screen saver is started
|
||||||
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230745
|
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230745
|
||||||
# An unattended system with an excessive grace period is vulnerable to a malicious user.
|
# An unattended system with an excessive grace period is vulnerable to a malicious user.
|
||||||
code: sudo defaults write /Library/Preferences/com.apple.screensaver 'askForPasswordDelay' -int 5
|
code: sudo defaults write /Library/Preferences/com.apple.screensaver 'askForPasswordDelay' -int 5
|
||||||
@@ -899,29 +866,29 @@ actions:
|
|||||||
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81615
|
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81615
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable guest sign-in from login screen
|
name: Disables signing in as Guest from the login screen
|
||||||
code: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool NO
|
code: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool NO
|
||||||
revertCode: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool YES
|
revetCode: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool YES
|
||||||
-
|
-
|
||||||
name: Disable guest access to file shares over AF
|
name: Disables Guest access to file shares over AF
|
||||||
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool NO
|
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool NO
|
||||||
revertCode: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool YES
|
revetCode: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool YES
|
||||||
-
|
-
|
||||||
name: Disable guest access to file shares over SMB
|
name: Disables Guest access to file shares over SMB
|
||||||
code: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool NO
|
code: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool NO
|
||||||
revertCode: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool YES
|
revetCode: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool YES
|
||||||
-
|
-
|
||||||
category: Disable unauthorized connections
|
category: Prevent unauthorized connections
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable incoming SSH and SFTP remote logins
|
name: Disable remote login (incoming SSH and SFTP connections)
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs: https://osxdaily.com/2016/08/16/enable-ssh-mac-command-line/
|
docs: https://osxdaily.com/2016/08/16/enable-ssh-mac-command-line/
|
||||||
# Check if enabled: sudo systemsetup -getremotelogin, returns "Remote Login: On" or "Off"
|
# Check if enabled: sudo systemsetup -getremotelogin, returns "Remote Login: On" or "Off"
|
||||||
code: echo 'yes' | sudo systemsetup -setremotelogin off
|
code: echo 'yes' | sudo systemsetup -setremotelogin off
|
||||||
revertCode: sudo systemsetup -setremotelogin on
|
revertCode: sudo systemsetup -setremotelogin on
|
||||||
-
|
-
|
||||||
name: Disable the insecure TFTP service
|
name: Disable insecure TFTP service
|
||||||
recommend: standard
|
recommend: standard
|
||||||
# If the system does not require Trivial File Transfer Protocol (TFTP), then support for
|
# If the system does not require Trivial File Transfer Protocol (TFTP), then support for
|
||||||
# it is non-essential and should be disabled. The information system should be configured to
|
# it is non-essential and should be disabled. The information system should be configured to
|
||||||
@@ -954,13 +921,13 @@ actions:
|
|||||||
- https://www.cups.org/doc/security.html # Security risks
|
- https://www.cups.org/doc/security.html # Security risks
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable local printer sharing with other computers
|
name: Disable sharing of local printers with other computers
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs: https://www.cups.org/doc/man-cupsctl.html
|
docs: https://www.cups.org/doc/man-cupsctl.html
|
||||||
code: cupsctl --no-share-printers
|
code: cupsctl --no-share-printers
|
||||||
revertCode: cupsctl --share-printers
|
revertCode: cupsctl --share-printers
|
||||||
-
|
-
|
||||||
name: Disable printing from external addresses, including the internet
|
name: Disable printing from any address including the Internet
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs: https://www.cups.org/doc/man-cupsctl.html
|
docs: https://www.cups.org/doc/man-cupsctl.html
|
||||||
code: cupsctl --no-remote-any
|
code: cupsctl --no-remote-any
|
||||||
@@ -985,7 +952,7 @@ actions:
|
|||||||
category: Clean File Quarantine from downloaded files
|
category: Clean File Quarantine from downloaded files
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Clear logs of all downloaded files from File Quarantine
|
name: Clear File Quarantine logs of all downloaded files
|
||||||
recommend: strict
|
recommend: strict
|
||||||
docs:
|
docs:
|
||||||
- https://www.macobserver.com/tips/how-to/your-mac-remembers-everything-you-download-heres-how-to-clear-download-history/
|
- https://www.macobserver.com/tips/how-to/your-mac-remembers-everything-you-download-heres-how-to-clear-download-history/
|
||||||
@@ -1002,7 +969,7 @@ actions:
|
|||||||
if ls -lO "$db_file" | grep --silent 'schg'; then
|
if ls -lO "$db_file" | grep --silent 'schg'; then
|
||||||
sudo chflags noschg "$db_file"
|
sudo chflags noschg "$db_file"
|
||||||
echo "Found and removed system immutable flag"
|
echo "Found and removed system immutable flag"
|
||||||
has_system_immutable_flag=true
|
has_sytem_immutable_flag=true
|
||||||
fi
|
fi
|
||||||
if ls -lO "$db_file" | grep --silent 'uchg'; then
|
if ls -lO "$db_file" | grep --silent 'uchg'; then
|
||||||
sudo chflags nouchg "$db_file"
|
sudo chflags nouchg "$db_file"
|
||||||
@@ -1011,7 +978,7 @@ actions:
|
|||||||
fi
|
fi
|
||||||
sqlite3 "$db_file" "$db_query"
|
sqlite3 "$db_file" "$db_query"
|
||||||
echo "Executed the query \"$db_query\""
|
echo "Executed the query \"$db_query\""
|
||||||
if [ "$has_system_immutable_flag" = true ] ; then
|
if [ "$has_sytem_immutable_flag" = true ] ; then
|
||||||
sudo chflags schg "$db_file"
|
sudo chflags schg "$db_file"
|
||||||
echo "Added system immutable flag back"
|
echo "Added system immutable flag back"
|
||||||
fi
|
fi
|
||||||
@@ -1045,10 +1012,10 @@ actions:
|
|||||||
' \
|
' \
|
||||||
{} \;
|
{} \;
|
||||||
-
|
-
|
||||||
category: Disable macOS File Quarantine tracking for downloaded files
|
category: Disable File Quarantine from tracking downloaded files
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable downloaded file logging in quarantine
|
name: Prevent quarantine from logging downloaded files
|
||||||
docs:
|
docs:
|
||||||
- https://eclecticlight.co/2019/04/25/%F0%9F%8E%97-quarantine-apps/
|
- https://eclecticlight.co/2019/04/25/%F0%9F%8E%97-quarantine-apps/
|
||||||
- https://eclecticlight.co/2017/12/11/xattr-com-apple-quarantine-the-quarantine-flag/
|
- https://eclecticlight.co/2017/12/11/xattr-com-apple-quarantine-the-quarantine-flag/
|
||||||
@@ -1071,7 +1038,7 @@ actions:
|
|||||||
>&2 echo "Cannot revert immutability, file does not exist at\"$file_to_lock\""
|
>&2 echo "Cannot revert immutability, file does not exist at\"$file_to_lock\""
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Disable extended quarantine attribute for downloaded files (disables warning)
|
name: Disable using extended quarantine attribute on downloaded files (disables warning)
|
||||||
# Disables dialogs shown when opening an application for the first time
|
# Disables dialogs shown when opening an application for the first time
|
||||||
# i.e. "Application Downloaded from Internet" quarantine warning.
|
# i.e. "Application Downloaded from Internet" quarantine warning.
|
||||||
docs:
|
docs:
|
||||||
@@ -1087,7 +1054,7 @@ actions:
|
|||||||
# Can protect against unknown threats.
|
# Can protect against unknown threats.
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable Gatekeeper's automatic reactivation
|
name: Prevent Gatekeeper from automatically reactivating itself
|
||||||
docs:
|
docs:
|
||||||
- https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/
|
- https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/
|
||||||
- https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
|
- https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
|
||||||
@@ -1140,7 +1107,7 @@ actions:
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Disable library validation entitlement (library signature validation)
|
name: Disable Library Validation Entitlement (checks signature of libraries)
|
||||||
docs:
|
docs:
|
||||||
- https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation
|
- https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation
|
||||||
- https://www.macenhance.com/docs/general/sip-library-validation.html
|
- https://www.macenhance.com/docs/general/sip-library-validation.html
|
||||||
@@ -1154,25 +1121,25 @@ actions:
|
|||||||
- https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html
|
- https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable automatic checks for updates
|
name: Disable automatically checking for updates
|
||||||
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic downloads for updates
|
name: Disable automatically downloading new updates when available
|
||||||
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic installation of macOS updates
|
name: Disable automatically installing macOS updates
|
||||||
docs:
|
docs:
|
||||||
# References for AutoUpdateRestartRequired
|
# References for AutoUpdateRestartRequired
|
||||||
- https://kb.vmware.com/s/article/2960635
|
- https://kb.vmware.com/s/article/2960635
|
||||||
@@ -1182,48 +1149,48 @@ actions:
|
|||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
||||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false
|
||||||
# For Mojave and newer (>= 10.14)
|
# For Mojave and later (>= 10.14)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool false
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
||||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool true
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool true
|
||||||
# For Mojave and newer (>= 10.14)
|
# For Mojave and later (>= 10.14)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic app updates from the App Store
|
name: Disable automatically updating app from the App Store
|
||||||
docs:
|
docs:
|
||||||
- https://kb.vmware.com/s/article/2960635
|
- https://kb.vmware.com/s/article/2960635
|
||||||
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
|
||||||
# For Mojave and newer (>= 10.14)
|
# For Mojave and later (>= 10.14)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool false
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
# For OS X Yosemite and newer
|
# For OS X Yosemite and later
|
||||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool true
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool true
|
||||||
# For Mojave and newer (>= 10.14)
|
# For Mojave and later (>= 10.14)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
|
||||||
-
|
-
|
||||||
name: Disable macOS beta release installation
|
name: Disable installation of macOS beta releases
|
||||||
docs: https://support.apple.com/en-gb/HT203018
|
docs: https://support.apple.com/en-gb/HT203018
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic installation for configuration data (e.g. XProtect, Gatekeeper, MRT)
|
name: Disable automatically installing configuration data (e.g. XProtect, Gatekeeper, MRT)
|
||||||
docs: https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
docs: https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic installation for system data files and security updates
|
name: Disable automatically installing system data files and security updates
|
||||||
docs:
|
docs:
|
||||||
# References for CriticalUpdateInstall
|
# References for CriticalUpdateInstall
|
||||||
- https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/
|
- https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/
|
||||||
@@ -1231,10 +1198,10 @@ actions:
|
|||||||
# References for softwareupdate --background-critical
|
# References for softwareupdate --background-critical
|
||||||
- https://managingosx.wordpress.com/2013/04/30/undocumented-options/
|
- https://managingosx.wordpress.com/2013/04/30/undocumented-options/
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool true
|
||||||
# Trigger background check with normal scan (critical updates only)
|
# Trigger background check with normal scan (critical updates only)
|
||||||
sudo softwareupdate --background-critical
|
sudo softwareupdate --background-critical
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
13
src/infrastructure/Clipboard.ts
Normal file
13
src/infrastructure/Clipboard.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export class Clipboard {
|
||||||
|
public static copyText(text: string): void {
|
||||||
|
const el = document.createElement('textarea');
|
||||||
|
el.value = text;
|
||||||
|
el.setAttribute('readonly', ''); // to avoid focus
|
||||||
|
el.style.position = 'absolute';
|
||||||
|
el.style.left = '-9999px';
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +1,31 @@
|
|||||||
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
import os from 'os';
|
||||||
|
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 system = getWindowInjectedSystemOperations(),
|
private readonly node = getNodeJs(),
|
||||||
private readonly environment = RuntimeEnvironment.CurrentEnvironment,
|
private readonly environment = Environment.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 { os } = this.environment;
|
const dir = this.node.path.join(this.node.os.tmpdir(), folderName);
|
||||||
const dir = this.system.location.combinePaths(
|
await this.node.fs.promises.mkdir(dir, { recursive: true });
|
||||||
this.system.operatingSystem.getTempDirectory(),
|
const filePath = this.node.path.join(dir, `run.${fileExtension}`);
|
||||||
folderName,
|
await this.node.fs.promises.writeFile(filePath, code);
|
||||||
);
|
await this.node.fs.promises.chmod(filePath, '755');
|
||||||
await this.system.fileSystem.createDirectory(dir, true);
|
const command = getExecuteCommand(filePath, this.environment);
|
||||||
const filePath = this.system.location.combinePaths(dir, `run.${fileExtension}`);
|
this.node.child_process.exec(command);
|
||||||
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(
|
function getExecuteCommand(scriptPath: string, environment: Environment): string {
|
||||||
scriptPath: string,
|
switch (environment.os) {
|
||||||
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:
|
||||||
@@ -42,6 +36,46 @@ function getExecuteCommand(
|
|||||||
case OperatingSystem.Windows:
|
case OperatingSystem.Windows:
|
||||||
return scriptPath;
|
return scriptPath;
|
||||||
default:
|
default:
|
||||||
throw Error(`unsupported os: ${OperatingSystem[currentOperatingSystem]}`);
|
throw Error(`unsupported os: ${OperatingSystem[environment.os]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
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;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user