Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd2082e8c5 | ||
|
|
8f188acd3c | ||
|
|
0303ef2fd9 | ||
|
|
cb21a970b6 | ||
|
|
203daeb4a2 | ||
|
|
60dde11311 | ||
|
|
8b930fc57c | ||
|
|
f810ed0c14 | ||
|
|
53222fd83c | ||
|
|
a1f2497381 | ||
|
|
c27172c32e | ||
|
|
6e9b65d8b1 | ||
|
|
6d301f9961 | ||
|
|
659fea7afc | ||
|
|
e0303058a3 | ||
|
|
65f121c451 | ||
|
|
821cc62c4c | ||
|
|
4ce327eb6a | ||
|
|
4beb1bb574 | ||
|
|
0a2a1a026b | ||
|
|
eb096d07e2 | ||
|
|
19e42c9c52 | ||
|
|
f4d86fccfd | ||
|
|
ad0576a752 | ||
|
|
35be05df20 | ||
|
|
dae6d114da | ||
|
|
ecce47fdcd |
@@ -1,2 +0,0 @@
|
|||||||
dist/
|
|
||||||
dist_electron/
|
|
||||||
11
.github/actions/npm-install-dependencies/action.yml
vendored
Normal file
11
.github/actions/npm-install-dependencies/action.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
inputs:
|
||||||
|
working-directory:
|
||||||
|
required: false
|
||||||
|
default: '.'
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Run `npm ci` with retries
|
||||||
|
shell: bash
|
||||||
|
run: npm run install-deps -- --ci --root-directory "${{ inputs.working-directory }}"
|
||||||
37
.github/workflows/checks.build.yaml
vendored
37
.github/workflows/checks.build.yaml
vendored
@@ -27,10 +27,13 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Build
|
name: Build web
|
||||||
run: npm run build -- --mode ${{ matrix.mode }}
|
run: npm run build -- --mode ${{ matrix.mode }}
|
||||||
|
-
|
||||||
|
name: Verify web build artifacts
|
||||||
|
run: npm run check:verify-build-artifacts -- --web
|
||||||
|
|
||||||
build-desktop:
|
build-desktop:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -52,30 +55,16 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Prebuild
|
name: Prebuild desktop
|
||||||
run: npm run electron:prebuild -- --mode ${{ matrix.mode }}
|
run: npm run electron:prebuild -- --mode ${{ matrix.mode }}
|
||||||
-
|
-
|
||||||
name: Build
|
name: Verify unbundled desktop build artifacts
|
||||||
|
run: npm run check:verify-build-artifacts -- --electron-unbundled
|
||||||
|
-
|
||||||
|
name: Build (bundle and package) desktop application
|
||||||
run: npm run electron:build -- --publish never
|
run: npm run electron:build -- --publish never
|
||||||
|
|
||||||
create-icons:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ macos, ubuntu, windows ]
|
|
||||||
fail-fast: false # Allows to see results from other combinations
|
|
||||||
runs-on: ${{ matrix.os }}-latest
|
|
||||||
steps:
|
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Verify bundled desktop build artifacts
|
||||||
uses: actions/checkout@v2
|
run: npm run check:verify-build-artifacts -- --electron-bundled
|
||||||
-
|
|
||||||
name: Setup node
|
|
||||||
uses: ./.github/actions/setup-node
|
|
||||||
-
|
|
||||||
name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
-
|
|
||||||
name: Create icons
|
|
||||||
run: npm run icons:build
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-desktop:
|
run-check:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos, ubuntu, windows ]
|
os: [ macos, ubuntu, windows ]
|
||||||
@@ -19,6 +19,9 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Setup node
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Configure Ubuntu
|
name: Configure Ubuntu
|
||||||
if: matrix.os == 'ubuntu'
|
if: matrix.os == 'ubuntu'
|
||||||
@@ -57,7 +60,9 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: node ./scripts/check-desktop-runtime-errors --screenshot
|
run: |-
|
||||||
|
export SCREENSHOT=true
|
||||||
|
npm run check:desktop
|
||||||
-
|
-
|
||||||
name: Upload screenshot
|
name: Upload screenshot
|
||||||
if: always() # Run even if previous step fails
|
if: always() # Run even if previous step fails
|
||||||
|
|||||||
22
.github/workflows/checks.external-urls.yaml
vendored
Normal file
22
.github/workflows/checks.external-urls.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: checks.external-urls
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * 0' # at 00:00 on every Sunday
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Setup node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Test
|
||||||
|
run: npm run check:external-urls
|
||||||
14
.github/workflows/checks.quality.yaml
vendored
14
.github/workflows/checks.quality.yaml
vendored
@@ -16,11 +16,15 @@ jobs:
|
|||||||
os: [ macos, ubuntu, windows ]
|
os: [ macos, ubuntu, windows ]
|
||||||
fail-fast: false # Still interested to see results from other combinations
|
fail-fast: false # Still interested to see results from other combinations
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
-
|
||||||
|
name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Setup node
|
-
|
||||||
|
name: Setup node
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
- name: Install dependencies
|
-
|
||||||
run: npm ci
|
name: Install dependencies
|
||||||
- name: Lint
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Lint
|
||||||
run: ${{ matrix.lint-command }}
|
run: ${{ matrix.lint-command }}
|
||||||
|
|||||||
55
.github/workflows/checks.scripts.yaml
vendored
Normal file
55
.github/workflows/checks.scripts.yaml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: checks.scripts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
icons-build:
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ macos, ubuntu, windows ]
|
||||||
|
fail-fast: false # Still interested to see results from other combinations
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Setup node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Create icons
|
||||||
|
run: npm run icons:build
|
||||||
|
|
||||||
|
install-deps:
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
install-deps-before: [true, false]
|
||||||
|
install-command:
|
||||||
|
- npm run install-deps
|
||||||
|
- npm run install-deps -- --no-errors
|
||||||
|
- npm run install-deps -- --ci
|
||||||
|
- npm run install-deps -- --fresh --non-deterministic
|
||||||
|
- npm run install-deps -- --fresh
|
||||||
|
- npm run install-deps -- --non-deterministic
|
||||||
|
os: [ macos, ubuntu, windows ]
|
||||||
|
fail-fast: false # Still interested to see results from other combinations
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Setup node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
if: matrix.install-deps-before == true
|
||||||
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
|
-
|
||||||
|
name: Run install-deps
|
||||||
|
run: ${{ matrix.install-command }}
|
||||||
2
.github/workflows/release.desktop.yaml
vendored
2
.github/workflows/release.desktop.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Run unit tests
|
name: Run unit tests
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
|
|||||||
17
.github/workflows/release.site.yaml
vendored
17
.github/workflows/release.site.yaml
vendored
@@ -84,8 +84,9 @@ jobs:
|
|||||||
uses: ./app/.github/actions/setup-node
|
uses: ./app/.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: "App: Install dependencies"
|
name: "App: Install dependencies"
|
||||||
run: npm ci
|
uses: ./app/.github/actions/npm-install-dependencies
|
||||||
working-directory: app
|
with:
|
||||||
|
working-directory: app
|
||||||
-
|
-
|
||||||
name: "App: Run unit tests"
|
name: "App: Run unit tests"
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
@@ -94,11 +95,21 @@ jobs:
|
|||||||
name: "App: Build"
|
name: "App: Build"
|
||||||
run: npm run build
|
run: npm run build
|
||||||
working-directory: app
|
working-directory: app
|
||||||
|
-
|
||||||
|
name: "App: Verify web build artifacts"
|
||||||
|
run: npm run check:verify-build-artifacts -- --web
|
||||||
|
working-directory: app
|
||||||
-
|
-
|
||||||
name: "App: Deploy to S3"
|
name: "App: Deploy to S3"
|
||||||
|
shell: bash
|
||||||
run: >-
|
run: >-
|
||||||
|
declare web_output_dir
|
||||||
|
if ! web_output_dir=$(cd app && node scripts/print-dist-dir.js --web); then
|
||||||
|
echo 'Error: Could not determine distribution directory.'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
bash "aws/scripts/deploy/deploy-to-s3.sh" \
|
bash "aws/scripts/deploy/deploy-to-s3.sh" \
|
||||||
--folder app/dist \
|
--folder "${web_output_dir}" \
|
||||||
--web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \
|
--web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \
|
||||||
--storage-class ONEZONE_IA \
|
--storage-class ONEZONE_IA \
|
||||||
--role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \
|
--role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \
|
||||||
|
|||||||
2
.github/workflows/tests.e2e.yaml
vendored
2
.github/workflows/tests.e2e.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Run e2e tests
|
name: Run e2e tests
|
||||||
run: npm run test:cy:run
|
run: npm run test:cy:run
|
||||||
|
|||||||
2
.github/workflows/tests.integration.yaml
vendored
2
.github/workflows/tests.integration.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Run integration tests
|
name: Run integration tests
|
||||||
run: npm run test:integration
|
run: npm run test:integration
|
||||||
|
|||||||
2
.github/workflows/tests.unit.yaml
vendored
2
.github/workflows/tests.unit.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Run unit tests
|
name: Run unit tests
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,7 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
dist/
|
/dist-*/
|
||||||
.vs
|
.vs
|
||||||
.vscode/**/*
|
.vscode/**/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
#Electron-builder output
|
|
||||||
/dist_electron
|
|
||||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,5 +1,35 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.12.3 (2023-09-09)
|
||||||
|
|
||||||
|
* linux: use user.js over prefs.js for Firefox #232 | [dae6d11](https://github.com/undergroundwires/privacy.sexy/commit/dae6d114daab6857d773071211eb57619b136281)
|
||||||
|
* win: fix typo in Defender retention script #213 | [35be05d](https://github.com/undergroundwires/privacy.sexy/commit/35be05df2094ea8bba4ee4725e6fa4956a79493d)
|
||||||
|
* Improve desktop runtime execution tests | [ad0576a](https://github.com/undergroundwires/privacy.sexy/commit/ad0576a752f8fd6ea2f917a59173fe61f9951246)
|
||||||
|
* Fix Windows artifact naming in desktop packaging | [f4d86fc](https://github.com/undergroundwires/privacy.sexy/commit/f4d86fccfd0e73e94c8c6e400a33514900bc5abe)
|
||||||
|
* Refactor and improve external URL checks | [19e42c9](https://github.com/undergroundwires/privacy.sexy/commit/19e42c9c52a18c813ded4265e687e01032cdd4c8)
|
||||||
|
* Fix memory leaks via auto-unsubscribing and DI | [eb096d0](https://github.com/undergroundwires/privacy.sexy/commit/eb096d07e276e1b4c8040220c47f186d02841e14)
|
||||||
|
* Refactor build configs and improve CI/CD checks | [0a2a1a0](https://github.com/undergroundwires/privacy.sexy/commit/0a2a1a026b0efb29624be82b06536c518c1ea439)
|
||||||
|
* Introduce retry mechanism for npm install in CI/CD | [4beb1bb](https://github.com/undergroundwires/privacy.sexy/commit/4beb1bb5748a60886210187ca3cdc7f4b41067c0)
|
||||||
|
* win: fix disable recent apps revert #211, #248 | [4ce327e](https://github.com/undergroundwires/privacy.sexy/commit/4ce327eb6af542ed2916d649553e5e1ba5833882)
|
||||||
|
* Change license to AGPLv3 | [821cc62](https://github.com/undergroundwires/privacy.sexy/commit/821cc62c4c8347cb76d041f82f574754e4d948c5)
|
||||||
|
* Introduce new TreeView UI component | [65f121c](https://github.com/undergroundwires/privacy.sexy/commit/65f121c451af87315e1c91df4198562e0445b2c2)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.2...0.12.3)
|
||||||
|
|
||||||
|
## 0.12.2 (2023-08-25)
|
||||||
|
|
||||||
|
* Add automated checks for desktop app runtime #233 | [04b3133](https://github.com/undergroundwires/privacy.sexy/commit/04b3133500485d0d278a81a177a1677134131405)
|
||||||
|
* win: fix automatic updates revert #234 | [0873769](https://github.com/undergroundwires/privacy.sexy/commit/08737698c2283bdf535d1611a730031ebfc7c0df)
|
||||||
|
* Migrate unit/integration tests to Vitest with Vite | [5f11c8d](https://github.com/undergroundwires/privacy.sexy/commit/5f11c8d98f782dd7c77f27649a1685fb7bd06e13)
|
||||||
|
* Remove Vue ESLint plugin for Vite compatibility | [6e40edd](https://github.com/undergroundwires/privacy.sexy/commit/6e40edd3f8a063c1b7482c27d8368e14c2fbcfbf)
|
||||||
|
* Migrate web builds from Vue CLI to Vite | [7365905](https://github.com/undergroundwires/privacy.sexy/commit/736590558be51a09435bb87e78b6655e8533bc2e)
|
||||||
|
* Migrate Cypress (E2E) tests to Vite and TypeScript | [ec98d84](https://github.com/undergroundwires/privacy.sexy/commit/ec98d8417f779fa818ccdda6bb90f521e1738002)
|
||||||
|
* Migrate to `electron-vite` and `electron-builder` | [75c9b51](https://github.com/undergroundwires/privacy.sexy/commit/75c9b51bf2d1dc7269adfd7b5ed71acfb5031299)
|
||||||
|
* Fix searching/filtering bugs #235 | [62f8bfa](https://github.com/undergroundwires/privacy.sexy/commit/62f8bfac2f481c93598fe19a51594769f522d684)
|
||||||
|
* Improve desktop security by isolating Electron | [e9e0001](https://github.com/undergroundwires/privacy.sexy/commit/e9e0001ef845fa6935c59a4e20a89aac9e71756a)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.1...0.12.2)
|
||||||
|
|
||||||
## 0.12.1 (2023-08-17)
|
## 0.12.1 (2023-08-17)
|
||||||
|
|
||||||
* Transition to eslint-config-airbnb-with-typescript | [ff84f56](https://github.com/undergroundwires/privacy.sexy/commit/ff84f5676e496dd7ec5b3599e34ec9627d181ea2)
|
* Transition to eslint-config-airbnb-with-typescript | [ff84f56](https://github.com/undergroundwires/privacy.sexy/commit/ff84f5676e496dd7ec5b3599e34ec9627d181ea2)
|
||||||
|
|||||||
141
LICENSE
141
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,17 +7,15 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
software for all its users.
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
|||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -72,7 +60,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
under version 3 of the GNU General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU General Public License from time to time. Such new versions will
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU General
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU General Public License, you may choose any version ever published
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
Copyright (C) <year> <name of author>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -76,6 +76,18 @@
|
|||||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.desktop-runtime-errors/badge.svg"
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.desktop-runtime-errors/badge.svg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.scripts.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img
|
||||||
|
alt="Status of script checks"
|
||||||
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.scripts/badge.svg"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.external-urls.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img
|
||||||
|
alt="Status of external URL checks"
|
||||||
|
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.external-urls/badge.svg"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
<!-- Release -->
|
<!-- Release -->
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml" target="_blank" rel="noopener noreferrer">
|
||||||
@@ -110,7 +122,7 @@
|
|||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
||||||
- 🖥️ **Offline**: Check [releases page](https://github.com/undergroundwires/privacy.sexy/releases), or download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-Setup-0.11.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.dmg), [Linux](https://github.com/undergroundwires/pr.vacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.AppImage).
|
- 🖥️ **Offline**: Check [releases page](https://github.com/undergroundwires/privacy.sexy/releases), or download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-Setup-0.12.3.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-0.12.3.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-0.12.3.AppImage).
|
||||||
|
|
||||||
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
|
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
|
||||||
|
|
||||||
|
|||||||
5
dist-dirs.json
Normal file
5
dist-dirs.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"electronUnbundled": "dist-electron-unbundled",
|
||||||
|
"electronBundled": "dist-electron-bundled",
|
||||||
|
"web": "dist-web"
|
||||||
|
}
|
||||||
@@ -5,14 +5,16 @@ Before your commit, a good practice is to:
|
|||||||
1. [Run unit tests](#testing)
|
1. [Run unit tests](#testing)
|
||||||
2. [Lint your code](#linting)
|
2. [Lint your code](#linting)
|
||||||
|
|
||||||
You could run other types of tests as well, but they may take longer time and overkill for your changes. Automated actions executes the tests for a pull request or change in the main branch. See [ci-cd.md](./ci-cd.md) for more information.
|
You could run other types of tests as well, but they may take longer time and overkill for your changes.
|
||||||
|
Automated actions are set up to execute these tests as necessary.
|
||||||
|
See [ci-cd.md](./ci-cd.md) for more information.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Install node >15.x.
|
- Install Node >16.x.
|
||||||
- Install dependencies using `npm install`.
|
- Install dependencies using `npm install` (or [`npm run install-deps`](#utility-scripts) for more options).
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
@@ -21,6 +23,10 @@ You could run other types of tests as well, but they may take longer time and ov
|
|||||||
- Run end-to-end (e2e) tests:
|
- Run end-to-end (e2e) tests:
|
||||||
- `npm run test:cy:open`: Run tests interactively using the development server with hot-reloading.
|
- `npm run test:cy:open`: Run tests interactively using the development server with hot-reloading.
|
||||||
- `npm run test:cy:run`: Run tests on the production build in a headless mode.
|
- `npm run test:cy:run`: Run tests on the production build in a headless mode.
|
||||||
|
- Run checks:
|
||||||
|
- `npm run check:desktop`: Run runtime checks for packaged desktop applications ([README.md](./../tests/checks/desktop-runtime-errors/check-desktop-runtime-errors/README.md)).
|
||||||
|
- You can set environment variables active its flags such as `BUILD=true SCREENSHOT=true npm run check:desktop`
|
||||||
|
- `npm run check:external-urls`: Test whether external URLs used in applications are alive.
|
||||||
|
|
||||||
📖 Read more about testing in [tests](./tests.md).
|
📖 Read more about testing in [tests](./tests.md).
|
||||||
|
|
||||||
@@ -61,13 +67,26 @@ You could run other types of tests as well, but they may take longer time and ov
|
|||||||
- Build desktop application: `npm run electron:build`
|
- Build desktop application: `npm run electron:build`
|
||||||
- (Re)create icons (see [documentation](../img/README.md)): `npm run create-icons`
|
- (Re)create icons (see [documentation](../img/README.md)): `npm run create-icons`
|
||||||
|
|
||||||
### Utility Scripts
|
### Scripts
|
||||||
|
|
||||||
- Run fresh NPM install: [`./scripts/fresh-npm-install.sh`](../scripts/fresh-npm-install.sh)
|
📖 For detailed options and behavior for any of the following scripts, please refer to the script file itself.
|
||||||
- This script provides a clean NPM install, removing existing node modules and optionally the package-lock.json (when run with -n), then installs dependencies and runs unit tests.
|
|
||||||
- Configure VSCode: [`./scripts/configure-vscode.sh`](../scripts/configure-vscode.sh)
|
#### Utility scripts
|
||||||
|
|
||||||
|
- [**`npm run install-deps [-- <options>]`**](../scripts/npm-install.js):
|
||||||
|
- Manages NPM dependency installation, it offers capabilities like doing a fresh install, retries on network errors, and other features.
|
||||||
|
- For example, you can run `npm run install-deps -- --fresh` to do clean installation of dependencies.
|
||||||
|
- [**`./scripts/configure-vscode.sh`**](../scripts/configure-vscode.sh):
|
||||||
- This script checks and sets the necessary configurations for VSCode in `settings.json` file.
|
- This script checks and sets the necessary configurations for VSCode in `settings.json` file.
|
||||||
|
|
||||||
|
#### Automation scripts
|
||||||
|
|
||||||
|
- [**`node scripts/print-dist-dir.js [-- <options>]`**](../scripts/print-dist-dir.js):
|
||||||
|
- Determines the absolute path of a distribution directory based on CLI arguments and outputs its absolute path.
|
||||||
|
- Primarily used by automation scripts.
|
||||||
|
- [**`npm run check:verify-build-artifacts [-- <options>]`**](../scripts/verify-build-artifacts.js):
|
||||||
|
- Verifies the existence and content of build artifacts. Useful for ensuring that the build process is generating the expected output.
|
||||||
|
|
||||||
## Recommended extensions
|
## Recommended extensions
|
||||||
|
|
||||||
You should use EditorConfig to follow project style.
|
You should use EditorConfig to follow project style.
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ The presentation layer uses an event-driven architecture for bidirectional react
|
|||||||
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue components and plugins.
|
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue components and plugins.
|
||||||
- [**`components/`**](./../src/presentation/components/): Contains Vue components and helpers.
|
- [**`components/`**](./../src/presentation/components/): Contains Vue components and helpers.
|
||||||
- [**`Shared/`**](./../src/presentation/components/Shared): Contains shared Vue components and helpers.
|
- [**`Shared/`**](./../src/presentation/components/Shared): Contains shared Vue components and helpers.
|
||||||
- [**`hooks`**](../src/presentation/components/Shared/Hooks): Hooks used by components through [dependency injection](#dependency-injections).
|
- [**`Hooks`**](../src/presentation/components/Shared/Hooks): Hooks used by components through [dependency injection](#dependency-injections).
|
||||||
- [**`/public/`**](../src/presentation/public/): Contains static assets.
|
- [**`/public/`**](../src/presentation/public/): Contains static assets.
|
||||||
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets processed by Vite.
|
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets processed by Vite.
|
||||||
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
|
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
|
||||||
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
|
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
|
||||||
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles for Vue components.
|
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles coupled to Vue components.
|
||||||
- [**`vendors-extensions/`**](./../src/presentation/assets/styles/third-party-extensions): Contains styles for third-party components.
|
|
||||||
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint.
|
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint.
|
||||||
- [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
|
- [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
|
||||||
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
|
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
|
||||||
@@ -91,7 +90,14 @@ Shared components include:
|
|||||||
|
|
||||||
Desktop builds uses `electron-vite` to bundle the code, and `electron-builder` to build and publish the packages.
|
Desktop builds uses `electron-vite` to bundle the code, and `electron-builder` to build and publish the packages.
|
||||||
|
|
||||||
## Sass naming convention
|
## 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;`
|
||||||
|
|||||||
43
electron-builder.cjs
Normal file
43
electron-builder.cjs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/* eslint-disable no-template-curly-in-string */
|
||||||
|
|
||||||
|
const { join } = require('path');
|
||||||
|
const { electronBundled, electronUnbundled } = require('./dist-dirs.json');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// Common options
|
||||||
|
publish: {
|
||||||
|
provider: 'github',
|
||||||
|
vPrefixedTagName: false, // default: true
|
||||||
|
releaseType: 'release', // default: draft
|
||||||
|
},
|
||||||
|
directories: {
|
||||||
|
output: electronBundled,
|
||||||
|
},
|
||||||
|
extraMetadata: {
|
||||||
|
main: join(electronUnbundled, 'main/index.cjs'), // do not `path.resolve`, it expects a relative path
|
||||||
|
},
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
win: {
|
||||||
|
target: 'nsis',
|
||||||
|
},
|
||||||
|
nsis: {
|
||||||
|
artifactName: '${name}-Setup-${version}.${ext}',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Linux
|
||||||
|
linux: {
|
||||||
|
target: 'AppImage',
|
||||||
|
},
|
||||||
|
appImage: {
|
||||||
|
artifactName: '${name}-${version}.${ext}',
|
||||||
|
},
|
||||||
|
|
||||||
|
// macOS
|
||||||
|
mac: {
|
||||||
|
target: 'dmg',
|
||||||
|
},
|
||||||
|
dmg: {
|
||||||
|
artifactName: '${name}-${version}.${ext}',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# -------
|
|
||||||
# Windows
|
|
||||||
# -------
|
|
||||||
win:
|
|
||||||
target: nsis
|
|
||||||
nsis:
|
|
||||||
artifactName: ${name}-${version}-Setup.${ext}
|
|
||||||
|
|
||||||
# -----
|
|
||||||
# Linux
|
|
||||||
# -----
|
|
||||||
linux:
|
|
||||||
target: AppImage
|
|
||||||
appImage:
|
|
||||||
artifactName: ${name}-${version}.${ext}
|
|
||||||
|
|
||||||
# -----
|
|
||||||
# macOS
|
|
||||||
# -----
|
|
||||||
mac:
|
|
||||||
target: dmg
|
|
||||||
dmg:
|
|
||||||
artifactName: ${name}-${version}.${ext}
|
|
||||||
|
|
||||||
# ----------------
|
|
||||||
# Publish options
|
|
||||||
# ----------------
|
|
||||||
publish:
|
|
||||||
provider: 'github'
|
|
||||||
vPrefixedTagName: false # default: true
|
|
||||||
releaseType: release # default: draft
|
|
||||||
@@ -3,11 +3,12 @@ import { mergeConfig, UserConfig } from 'vite';
|
|||||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
|
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
|
||||||
import { getAliasesFromTsConfig, getClientEnvironmentVariables } from './vite-config-helper';
|
import { getAliasesFromTsConfig, getClientEnvironmentVariables } from './vite-config-helper';
|
||||||
import { createVueConfig } from './vite.config';
|
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 MAIN_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/main/index.ts');
|
||||||
const PRELOAD_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/preload/index.ts');
|
const PRELOAD_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/preload/index.ts');
|
||||||
const WEB_INDEX_HTML_PATH = resolvePathFromProjectRoot('src/presentation/index.html');
|
const WEB_INDEX_HTML_PATH = resolvePathFromProjectRoot('src/presentation/index.html');
|
||||||
const DIST_DIR = resolvePathFromProjectRoot('dist_electron/');
|
const DIST_DIR = resolvePathFromProjectRoot(distDirs.electronUnbundled);
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
main: getSharedElectronConfig({
|
main: getSharedElectronConfig({
|
||||||
|
|||||||
196
package-lock.json
generated
196
package-lock.json
generated
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.12.1",
|
"version": "0.12.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.12.1",
|
"version": "0.12.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/vue": "^1.0.2",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||||
@@ -19,11 +20,8 @@
|
|||||||
"cross-fetch": "^4.0.0",
|
"cross-fetch": "^4.0.0",
|
||||||
"electron-progressbar": "^2.1.0",
|
"electron-progressbar": "^2.1.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"install": "^0.13.0",
|
|
||||||
"liquor-tree": "^0.2.70",
|
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"npm": "^9.8.1",
|
"npm": "^9.8.1",
|
||||||
"v-tooltip": "2.1.3",
|
|
||||||
"vue": "^2.7.14"
|
"vue": "^2.7.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1718,6 +1716,7 @@
|
|||||||
"version": "7.22.10",
|
"version": "7.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
|
||||||
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
|
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@@ -1728,7 +1727,8 @@
|
|||||||
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
|
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
|
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.5",
|
||||||
@@ -2522,6 +2522,62 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
|
||||||
|
"integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.4.2",
|
||||||
|
"@floating-ui/utils": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-uvnFKtPgzLnpzzTRfhDlvXX0kLYi9lDRQbcDmT8iXl71Rx+uwSuaUIQl3DNC7w5OweAQ7XQMDObML+KaYDQfng=="
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/vue": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-sImlAl9mAoCKZLNlwWz2P2ZMJIDlOEDXrRD6aD2sIHAka1LPC+nWtB+D3lPe7IE7FGWSbwBPTnlSdlABa3Fr0A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.4.5",
|
||||||
|
"vue-demi": ">=0.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/vue/node_modules/vue-demi": {
|
||||||
|
"version": "0.14.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||||
|
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||||
"version": "6.4.2",
|
"version": "6.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
|
||||||
@@ -9425,14 +9481,6 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/install": {
|
|
||||||
"version": "0.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz",
|
|
||||||
"integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/internal-slot": {
|
"node_modules/internal-slot": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
|
||||||
@@ -10771,11 +10819,6 @@
|
|||||||
"uc.micro": "^1.0.1"
|
"uc.micro": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/liquor-tree": {
|
|
||||||
"version": "0.2.70",
|
|
||||||
"resolved": "https://registry.npmjs.org/liquor-tree/-/liquor-tree-0.2.70.tgz",
|
|
||||||
"integrity": "sha512-5CiMlDVmuveYwwc27mYe1xZ3J4aHhZBErUhIp9ov4v4wIBso+s5JAByOOit4iOCMCQ5ODd8VggbKymzZREYbBQ=="
|
|
||||||
},
|
|
||||||
"node_modules/listr2": {
|
"node_modules/listr2": {
|
||||||
"version": "3.14.0",
|
"version": "3.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz",
|
||||||
@@ -10924,7 +10967,8 @@
|
|||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.debounce": {
|
"node_modules/lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
@@ -16113,16 +16157,6 @@
|
|||||||
"node": ">=12.13.0"
|
"node": ">=12.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/popper.js": {
|
|
||||||
"version": "1.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
|
||||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
|
||||||
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/popperjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.28",
|
"version": "8.4.28",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
|
||||||
@@ -20354,17 +20388,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/v-tooltip": {
|
|
||||||
"version": "2.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.1.3.tgz",
|
|
||||||
"integrity": "sha512-xXngyxLQTOx/yUEy50thb8te7Qo4XU6h4LZB6cvEfVd9mnysUxLEoYwGWDdqR+l69liKsy3IPkdYff3J1gAJ5w==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"popper.js": "^1.16.1",
|
|
||||||
"vue-resize": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/validate-npm-package-license": {
|
"node_modules/validate-npm-package-license": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||||
@@ -20765,17 +20788,6 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-resize": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"vue": "^2.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vue-template-compiler": {
|
"node_modules/vue-template-compiler": {
|
||||||
"version": "2.7.14",
|
"version": "2.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
|
||||||
@@ -22451,6 +22463,7 @@
|
|||||||
"version": "7.22.10",
|
"version": "7.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
|
||||||
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
|
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@@ -22458,7 +22471,8 @@
|
|||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
|
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -22962,6 +22976,45 @@
|
|||||||
"integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==",
|
"integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@floating-ui/core": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==",
|
||||||
|
"requires": {
|
||||||
|
"@floating-ui/utils": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@floating-ui/dom": {
|
||||||
|
"version": "1.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
|
||||||
|
"integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
|
||||||
|
"requires": {
|
||||||
|
"@floating-ui/core": "^1.4.2",
|
||||||
|
"@floating-ui/utils": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@floating-ui/utils": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-uvnFKtPgzLnpzzTRfhDlvXX0kLYi9lDRQbcDmT8iXl71Rx+uwSuaUIQl3DNC7w5OweAQ7XQMDObML+KaYDQfng=="
|
||||||
|
},
|
||||||
|
"@floating-ui/vue": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-sImlAl9mAoCKZLNlwWz2P2ZMJIDlOEDXrRD6aD2sIHAka1LPC+nWtB+D3lPe7IE7FGWSbwBPTnlSdlABa3Fr0A==",
|
||||||
|
"requires": {
|
||||||
|
"@floating-ui/dom": "^1.4.5",
|
||||||
|
"vue-demi": ">=0.13.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": {
|
||||||
|
"version": "0.14.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||||
|
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@fortawesome/fontawesome-common-types": {
|
"@fortawesome/fontawesome-common-types": {
|
||||||
"version": "6.4.2",
|
"version": "6.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
|
||||||
@@ -28223,11 +28276,6 @@
|
|||||||
"integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
|
"integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"install": {
|
|
||||||
"version": "0.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz",
|
|
||||||
"integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA=="
|
|
||||||
},
|
|
||||||
"internal-slot": {
|
"internal-slot": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
|
||||||
@@ -29256,11 +29304,6 @@
|
|||||||
"uc.micro": "^1.0.1"
|
"uc.micro": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"liquor-tree": {
|
|
||||||
"version": "0.2.70",
|
|
||||||
"resolved": "https://registry.npmjs.org/liquor-tree/-/liquor-tree-0.2.70.tgz",
|
|
||||||
"integrity": "sha512-5CiMlDVmuveYwwc27mYe1xZ3J4aHhZBErUhIp9ov4v4wIBso+s5JAByOOit4iOCMCQ5ODd8VggbKymzZREYbBQ=="
|
|
||||||
},
|
|
||||||
"listr2": {
|
"listr2": {
|
||||||
"version": "3.14.0",
|
"version": "3.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz",
|
||||||
@@ -29368,7 +29411,8 @@
|
|||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.debounce": {
|
"lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
@@ -32878,11 +32922,6 @@
|
|||||||
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
|
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"popper.js": {
|
|
||||||
"version": "1.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
|
||||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
|
|
||||||
},
|
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "8.4.28",
|
"version": "8.4.28",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
|
||||||
@@ -36104,17 +36143,6 @@
|
|||||||
"sade": "^1.7.3"
|
"sade": "^1.7.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"v-tooltip": {
|
|
||||||
"version": "2.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.1.3.tgz",
|
|
||||||
"integrity": "sha512-xXngyxLQTOx/yUEy50thb8te7Qo4XU6h4LZB6cvEfVd9mnysUxLEoYwGWDdqR+l69liKsy3IPkdYff3J1gAJ5w==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.13.10",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"popper.js": "^1.16.1",
|
|
||||||
"vue-resize": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"validate-npm-package-license": {
|
"validate-npm-package-license": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||||
@@ -36341,14 +36369,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue-resize": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"vue-template-compiler": {
|
"vue-template-compiler": {
|
||||||
"version": "2.7.14",
|
"version": "2.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -1,27 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.12.1",
|
"version": "0.12.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"slogan": "Now you have the choice",
|
"slogan": "Now you have the choice",
|
||||||
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
|
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
|
||||||
"author": "undergroundwires",
|
"author": "undergroundwires",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"main": "./dist-electron-unbundled/main/index.cjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test:unit": "vitest run --dir tests/unit",
|
"test:unit": "vitest run --dir tests/unit",
|
||||||
"test:integration": "vitest run --dir tests/integration",
|
"test:integration": "vitest run --dir tests/integration",
|
||||||
"test:e2e": "vue-cli-service test:e2e",
|
|
||||||
"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: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\"",
|
"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",
|
||||||
"icons:build": "node scripts/logo-update.js",
|
"icons:build": "node scripts/logo-update.js",
|
||||||
|
"check:desktop": "vitest run --dir tests/checks/desktop-runtime-errors --environment node",
|
||||||
|
"check:external-urls": "vitest run --dir tests/checks/external-urls --environment node",
|
||||||
|
"check:verify-build-artifacts": "node scripts/verify-build-artifacts",
|
||||||
"electron:dev": "electron-vite dev",
|
"electron:dev": "electron-vite dev",
|
||||||
"electron:preview": "electron-vite preview",
|
"electron:preview": "electron-vite preview",
|
||||||
"electron:prebuild": "electron-vite build",
|
"electron:prebuild": "electron-vite build",
|
||||||
"electron:build": "electron-builder",
|
"electron:build": "electron-builder",
|
||||||
"lint:eslint": "eslint .",
|
"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",
|
||||||
@@ -29,8 +33,8 @@
|
|||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"postuninstall": "electron-builder install-app-deps"
|
"postuninstall": "electron-builder install-app-deps"
|
||||||
},
|
},
|
||||||
"main": "./dist_electron/main/index.cjs",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/vue": "^1.0.2",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||||
@@ -41,11 +45,8 @@
|
|||||||
"cross-fetch": "^4.0.0",
|
"cross-fetch": "^4.0.0",
|
||||||
"electron-progressbar": "^2.1.0",
|
"electron-progressbar": "^2.1.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"install": "^0.13.0",
|
|
||||||
"liquor-tree": "^0.2.70",
|
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"npm": "^9.8.1",
|
"npm": "^9.8.1",
|
||||||
"v-tooltip": "2.1.3",
|
|
||||||
"vue": "^2.7.14"
|
"vue": "^2.7.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
require('@rushstack/eslint-patch/modern-module-resolution.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'import/extensions': ['error', 'always'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { unlink, readFile } from 'fs/promises';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { log, die, LOG_LEVELS } from '../utils/log.js';
|
|
||||||
import { exists } from '../utils/io.js';
|
|
||||||
import { SUPPORTED_PLATFORMS, CURRENT_PLATFORM } from '../utils/platform.js';
|
|
||||||
import { getAppName } from '../utils/npm.js';
|
|
||||||
|
|
||||||
export async function clearAppLogFile(projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
const logPath = await determineLogPath(projectDir);
|
|
||||||
if (!logPath || !await exists(logPath)) {
|
|
||||||
log(`Skipping clearing logs, log file does not exist: ${logPath}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await unlink(logPath);
|
|
||||||
log(`Successfully cleared the log file at: ${logPath}.`);
|
|
||||||
} catch (error) {
|
|
||||||
die(`Failed to clear the log file at: ${logPath}. Reason: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readAppLogFile(projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
const logPath = await determineLogPath(projectDir);
|
|
||||||
if (!logPath || !await exists(logPath)) {
|
|
||||||
log(`No log file at: ${logPath}`, LOG_LEVELS.WARN);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const logContent = await readLogFile(logPath);
|
|
||||||
return logContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function determineLogPath(projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
const appName = await getAppName(projectDir);
|
|
||||||
if (!appName) {
|
|
||||||
die('App name not found.');
|
|
||||||
}
|
|
||||||
const logFilePaths = {
|
|
||||||
[SUPPORTED_PLATFORMS.MAC]: () => join(process.env.HOME, 'Library', 'Logs', appName, 'main.log'),
|
|
||||||
[SUPPORTED_PLATFORMS.LINUX]: () => join(process.env.HOME, '.config', appName, 'logs', 'main.log'),
|
|
||||||
[SUPPORTED_PLATFORMS.WINDOWS]: () => join(process.env.USERPROFILE, 'AppData', 'Roaming', appName, 'logs', 'main.log'),
|
|
||||||
};
|
|
||||||
const logFilePath = logFilePaths[CURRENT_PLATFORM]?.();
|
|
||||||
if (!logFilePath) {
|
|
||||||
log(`Cannot determine log path, unsupported OS: ${CURRENT_PLATFORM}`, LOG_LEVELS.WARN);
|
|
||||||
}
|
|
||||||
return logFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readLogFile(logFilePath) {
|
|
||||||
const content = await readFile(logFilePath, 'utf-8');
|
|
||||||
return content?.trim().length > 0 ? content : undefined;
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { splitTextIntoLines, indentText } from '../utils/text.js';
|
|
||||||
import { die } from '../utils/log.js';
|
|
||||||
import { readAppLogFile } from './app-logs.js';
|
|
||||||
|
|
||||||
const ELECTRON_CRASH_TITLE = 'Error'; // Used by electron for early crashes
|
|
||||||
const LOG_ERROR_MARKER = '[error]'; // from electron-log
|
|
||||||
const EXPECTED_LOG_MARKERS = [
|
|
||||||
'[WINDOW_INIT]',
|
|
||||||
'[PRELOAD_INIT]',
|
|
||||||
'[APP_MOUNT_INIT]',
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function checkForErrors(stderr, windowTitles, projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
const errors = await gatherErrors(stderr, windowTitles, projectDir);
|
|
||||||
if (errors.length) {
|
|
||||||
die(formatErrors(errors));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function gatherErrors(stderr, windowTitles, projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
const logContent = await readAppLogFile(projectDir);
|
|
||||||
return [
|
|
||||||
verifyStdErr(stderr),
|
|
||||||
verifyApplicationLogsExist(logContent),
|
|
||||||
...EXPECTED_LOG_MARKERS.map((marker) => verifyLogMarkerExistsInLogs(logContent, marker)),
|
|
||||||
verifyWindowTitle(windowTitles),
|
|
||||||
verifyErrorsInLogs(logContent),
|
|
||||||
].filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatErrors(errors) {
|
|
||||||
if (!errors || !errors.length) { throw new Error('missing errors'); }
|
|
||||||
return [
|
|
||||||
'Errors detected during execution:',
|
|
||||||
...errors.map(
|
|
||||||
(error) => formatError(error),
|
|
||||||
),
|
|
||||||
].join('\n---\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatError(error) {
|
|
||||||
if (!error) { throw new Error('missing error'); }
|
|
||||||
if (!error.reason) { throw new Error(`missing reason, error (${typeof error}): ${JSON.stringify(error)}`); }
|
|
||||||
let message = `Reason: ${indentText(error.reason, 1)}`;
|
|
||||||
if (error.description) {
|
|
||||||
message += `\nDescription:\n${indentText(error.description, 2)}`;
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyApplicationLogsExist(logContent) {
|
|
||||||
if (!logContent || !logContent.length) {
|
|
||||||
return describeError(
|
|
||||||
'Missing application logs',
|
|
||||||
'Application logs are empty not were not found.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyLogMarkerExistsInLogs(logContent, marker) {
|
|
||||||
if (!marker) {
|
|
||||||
throw new Error('missing marker');
|
|
||||||
}
|
|
||||||
if (!logContent?.includes(marker)) {
|
|
||||||
return describeError(
|
|
||||||
'Incomplete application logs',
|
|
||||||
`Missing identifier "${marker}" in application logs.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyWindowTitle(windowTitles) {
|
|
||||||
const errorTitles = windowTitles.filter(
|
|
||||||
(title) => title.toLowerCase().includes(ELECTRON_CRASH_TITLE),
|
|
||||||
);
|
|
||||||
if (errorTitles.length) {
|
|
||||||
return describeError(
|
|
||||||
'Unexpected window title',
|
|
||||||
'One or more window titles suggest an error occurred in the application:'
|
|
||||||
+ `\nError Titles: ${errorTitles.join(', ')}`
|
|
||||||
+ `\nAll Titles: ${windowTitles.join(', ')}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyStdErr(stderrOutput) {
|
|
||||||
if (stderrOutput && stderrOutput.length > 0) {
|
|
||||||
return describeError(
|
|
||||||
'Standard error stream (`stderr`) is not empty.',
|
|
||||||
stderrOutput,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyErrorsInLogs(logContent) {
|
|
||||||
if (!logContent || !logContent.length) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const logLines = getNonEmptyLines(logContent)
|
|
||||||
.filter((line) => line.includes(LOG_ERROR_MARKER));
|
|
||||||
if (!logLines.length) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return describeError(
|
|
||||||
'Application log file',
|
|
||||||
logLines.join('\n'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function describeError(reason, description) {
|
|
||||||
return {
|
|
||||||
reason,
|
|
||||||
description: `${description}\n\nThis might indicate an early crash or significant runtime issue.`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNonEmptyLines(text) {
|
|
||||||
return splitTextIntoLines(text)
|
|
||||||
.filter((line) => line?.trim().length > 0);
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { access, chmod } from 'fs/promises';
|
|
||||||
import { constants } from 'fs';
|
|
||||||
import { findSingleFileByExtension } from '../../utils/io.js';
|
|
||||||
import { log } from '../../utils/log.js';
|
|
||||||
|
|
||||||
export async function prepareLinuxApp(desktopDistPath) {
|
|
||||||
const { absolutePath: appFile } = await findSingleFileByExtension(
|
|
||||||
'AppImage',
|
|
||||||
desktopDistPath,
|
|
||||||
);
|
|
||||||
await makeExecutable(appFile);
|
|
||||||
return {
|
|
||||||
appExecutablePath: appFile,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeExecutable(appFile) {
|
|
||||||
if (!appFile) { throw new Error('missing file'); }
|
|
||||||
if (await isExecutable(appFile)) {
|
|
||||||
log('AppImage is already executable.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log('Making it executable...');
|
|
||||||
await chmod(appFile, 0o755);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function isExecutable(file) {
|
|
||||||
try {
|
|
||||||
await access(file, constants.X_OK);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { mkdtemp, rmdir } from 'fs/promises';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { tmpdir } from 'os';
|
|
||||||
import { findSingleFileByExtension, exists } from '../../utils/io.js';
|
|
||||||
import { log, die } from '../../utils/log.js';
|
|
||||||
import { runCommand } from '../../utils/run-command.js';
|
|
||||||
|
|
||||||
export async function prepareWindowsApp(desktopDistPath) {
|
|
||||||
const workdir = await mkdtemp(join(tmpdir(), 'win-nsis-installation-'));
|
|
||||||
if (await exists(workdir)) {
|
|
||||||
log(`Temporary directory ${workdir} already exists, cleaning up...`);
|
|
||||||
await rmdir(workdir, { recursive: true });
|
|
||||||
}
|
|
||||||
const { appExecutablePath } = await installNsis(workdir, desktopDistPath);
|
|
||||||
return {
|
|
||||||
appExecutablePath,
|
|
||||||
cleanup: async () => {
|
|
||||||
log(`Cleaning up working directory ${workdir}...`);
|
|
||||||
await rmdir(workdir, { recursive: true });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function installNsis(installationPath, desktopDistPath) {
|
|
||||||
const { absolutePath: installerPath } = await findSingleFileByExtension('exe', desktopDistPath);
|
|
||||||
|
|
||||||
log(`Silently installing contents of ${installerPath} to ${installationPath}...`);
|
|
||||||
const { error } = await runCommand(`"${installerPath}" /S /D=${installationPath}`);
|
|
||||||
if (error) {
|
|
||||||
die(`Failed to install.\n${error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { absolutePath: appExecutablePath } = await findSingleFileByExtension('exe', installationPath);
|
|
||||||
|
|
||||||
return {
|
|
||||||
appExecutablePath,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { log } from './utils/log.js';
|
|
||||||
|
|
||||||
const PROCESS_ARGUMENTS = process.argv.slice(2);
|
|
||||||
|
|
||||||
export const COMMAND_LINE_FLAGS = Object.freeze({
|
|
||||||
FORCE_REBUILD: '--build',
|
|
||||||
TAKE_SCREENSHOT: '--screenshot',
|
|
||||||
});
|
|
||||||
|
|
||||||
export function logCurrentArgs() {
|
|
||||||
if (!PROCESS_ARGUMENTS.length) {
|
|
||||||
log('No additional arguments provided.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log(`Arguments: ${PROCESS_ARGUMENTS.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasCommandLineFlag(flag) {
|
|
||||||
return PROCESS_ARGUMENTS.includes(flag);
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
export const DESKTOP_BUILD_COMMAND = 'npm run electron:prebuild && npm run electron:build -- --publish never';
|
|
||||||
export const PROJECT_DIR = process.cwd();
|
|
||||||
export const DESKTOP_DIST_PATH = join(PROJECT_DIR, 'dist');
|
|
||||||
export const APP_EXECUTION_DURATION_IN_SECONDS = 60; // Long enough for CI runners
|
|
||||||
export const SCREENSHOT_PATH = join(PROJECT_DIR, 'screenshot.png');
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { main } from './main.js';
|
|
||||||
|
|
||||||
await main();
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { logCurrentArgs, COMMAND_LINE_FLAGS, hasCommandLineFlag } from './cli-args.js';
|
|
||||||
import { log, die } from './utils/log.js';
|
|
||||||
import { ensureNpmProjectDir, npmInstall, npmBuild } from './utils/npm.js';
|
|
||||||
import { clearAppLogFile } from './app/app-logs.js';
|
|
||||||
import { checkForErrors } from './app/check-for-errors.js';
|
|
||||||
import { runApplication } from './app/runner.js';
|
|
||||||
import { CURRENT_PLATFORM, SUPPORTED_PLATFORMS } from './utils/platform.js';
|
|
||||||
import { prepareLinuxApp } from './app/extractors/linux.js';
|
|
||||||
import { prepareWindowsApp } from './app/extractors/windows.js';
|
|
||||||
import { prepareMacOsApp } from './app/extractors/macos.js';
|
|
||||||
import {
|
|
||||||
DESKTOP_BUILD_COMMAND,
|
|
||||||
PROJECT_DIR,
|
|
||||||
DESKTOP_DIST_PATH,
|
|
||||||
APP_EXECUTION_DURATION_IN_SECONDS,
|
|
||||||
SCREENSHOT_PATH,
|
|
||||||
} from './config.js';
|
|
||||||
|
|
||||||
export async function main() {
|
|
||||||
logCurrentArgs();
|
|
||||||
await ensureNpmProjectDir(PROJECT_DIR);
|
|
||||||
await npmInstall(PROJECT_DIR);
|
|
||||||
await npmBuild(
|
|
||||||
PROJECT_DIR,
|
|
||||||
DESKTOP_BUILD_COMMAND,
|
|
||||||
DESKTOP_DIST_PATH,
|
|
||||||
hasCommandLineFlag(COMMAND_LINE_FLAGS.FORCE_REBUILD),
|
|
||||||
);
|
|
||||||
await clearAppLogFile(PROJECT_DIR);
|
|
||||||
const {
|
|
||||||
stderr, stdout, isCrashed, windowTitles,
|
|
||||||
} = await extractAndRun();
|
|
||||||
if (stdout) {
|
|
||||||
log(`Output (stdout) from application execution:\n${stdout}`);
|
|
||||||
}
|
|
||||||
if (isCrashed) {
|
|
||||||
die('The application encountered an error during its execution.');
|
|
||||||
}
|
|
||||||
await checkForErrors(stderr, windowTitles, PROJECT_DIR);
|
|
||||||
log('🥳🎈 Success! Application completed without any runtime errors.');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function extractAndRun() {
|
|
||||||
const extractors = {
|
|
||||||
[SUPPORTED_PLATFORMS.MAC]: () => prepareMacOsApp(DESKTOP_DIST_PATH),
|
|
||||||
[SUPPORTED_PLATFORMS.LINUX]: () => prepareLinuxApp(DESKTOP_DIST_PATH),
|
|
||||||
[SUPPORTED_PLATFORMS.WINDOWS]: () => prepareWindowsApp(DESKTOP_DIST_PATH),
|
|
||||||
};
|
|
||||||
const extractor = extractors[CURRENT_PLATFORM];
|
|
||||||
if (!extractor) {
|
|
||||||
throw new Error(`Platform not supported: ${CURRENT_PLATFORM}`);
|
|
||||||
}
|
|
||||||
const { appExecutablePath, cleanup } = await extractor();
|
|
||||||
try {
|
|
||||||
return await runApplication(
|
|
||||||
appExecutablePath,
|
|
||||||
APP_EXECUTION_DURATION_IN_SECONDS,
|
|
||||||
hasCommandLineFlag(COMMAND_LINE_FLAGS.TAKE_SCREENSHOT),
|
|
||||||
SCREENSHOT_PATH,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
if (cleanup) {
|
|
||||||
log('Cleaning up post-execution resources...');
|
|
||||||
await cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { extname, join } from 'path';
|
|
||||||
import { readdir, access } from 'fs/promises';
|
|
||||||
import { constants } from 'fs';
|
|
||||||
import { log, die, LOG_LEVELS } from './log.js';
|
|
||||||
|
|
||||||
export async function findSingleFileByExtension(extension, directory) {
|
|
||||||
if (!directory) { throw new Error('Missing directory'); }
|
|
||||||
if (!extension) { throw new Error('Missing file extension'); }
|
|
||||||
|
|
||||||
if (!await exists(directory)) {
|
|
||||||
die(`Directory does not exist: ${directory}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const directoryContents = await readdir(directory);
|
|
||||||
const foundFileNames = directoryContents.filter((file) => extname(file) === `.${extension}`);
|
|
||||||
const withoutUninstaller = foundFileNames.filter(
|
|
||||||
(fileName) => !fileName.toLowerCase().includes('uninstall'), // NSIS build has `Uninstall {app-name}.exe`
|
|
||||||
);
|
|
||||||
if (!withoutUninstaller.length) {
|
|
||||||
die(`No ${extension} found in ${directory} directory.`);
|
|
||||||
}
|
|
||||||
if (withoutUninstaller.length > 1) {
|
|
||||||
log(`Found multiple ${extension} files: ${withoutUninstaller.join(', ')}. Using first occurrence`, LOG_LEVELS.WARN);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
absolutePath: join(directory, withoutUninstaller[0]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function exists(path) {
|
|
||||||
if (!path) { throw new Error('Missing path'); }
|
|
||||||
try {
|
|
||||||
await access(path, constants.F_OK);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isDirMissingOrEmpty(dir) {
|
|
||||||
if (!dir) { throw new Error('Missing directory'); }
|
|
||||||
if (!await exists(dir)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const contents = await readdir(dir);
|
|
||||||
return contents.length === 0;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
export const LOG_LEVELS = Object.freeze({
|
|
||||||
INFO: 'INFO',
|
|
||||||
WARN: 'WARN',
|
|
||||||
ERROR: 'ERROR',
|
|
||||||
});
|
|
||||||
|
|
||||||
export function log(message, level = LOG_LEVELS.INFO) {
|
|
||||||
const timestamp = new Date().toISOString();
|
|
||||||
const config = LOG_LEVEL_CONFIG[level] || LOG_LEVEL_CONFIG[LOG_LEVELS.INFO];
|
|
||||||
const formattedMessage = `[${timestamp}][${config.color}${level}${COLOR_CODES.RESET}] ${message}`;
|
|
||||||
config.method(formattedMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function die(message) {
|
|
||||||
log(message, LOG_LEVELS.ERROR);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const COLOR_CODES = {
|
|
||||||
RESET: '\x1b[0m',
|
|
||||||
LIGHT_RED: '\x1b[91m',
|
|
||||||
YELLOW: '\x1b[33m',
|
|
||||||
LIGHT_BLUE: '\x1b[94m',
|
|
||||||
};
|
|
||||||
|
|
||||||
const LOG_LEVEL_CONFIG = {
|
|
||||||
[LOG_LEVELS.INFO]: {
|
|
||||||
color: COLOR_CODES.LIGHT_BLUE,
|
|
||||||
method: console.log,
|
|
||||||
},
|
|
||||||
[LOG_LEVELS.WARN]: {
|
|
||||||
color: COLOR_CODES.YELLOW,
|
|
||||||
method: console.warn,
|
|
||||||
},
|
|
||||||
[LOG_LEVELS.ERROR]: {
|
|
||||||
color: COLOR_CODES.LIGHT_RED,
|
|
||||||
method: console.error,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
import { rmdir, readFile } from 'fs/promises';
|
|
||||||
import { exists, isDirMissingOrEmpty } from './io.js';
|
|
||||||
import { runCommand } from './run-command.js';
|
|
||||||
import { LOG_LEVELS, die, log } from './log.js';
|
|
||||||
|
|
||||||
export async function ensureNpmProjectDir(projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
if (!await exists(join(projectDir, 'package.json'))) {
|
|
||||||
die(`'package.json' not found in project directory: ${projectDir}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function npmInstall(projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
const npmModulesPath = join(projectDir, 'node_modules');
|
|
||||||
if (!await isDirMissingOrEmpty(npmModulesPath)) {
|
|
||||||
log(`Directory "${npmModulesPath}" exists and has content. Skipping \`npm install\`.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log('Starting dependency installation...');
|
|
||||||
const { error } = await runCommand('npm install --loglevel=error', {
|
|
||||||
stdio: 'inherit',
|
|
||||||
cwd: projectDir,
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
die(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function npmBuild(projectDir, buildCommand, distDir, forceRebuild) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
if (!buildCommand) { throw new Error('missing build command'); }
|
|
||||||
if (!distDir) { throw new Error('missing distribution directory'); }
|
|
||||||
|
|
||||||
const isMissingBuild = await isDirMissingOrEmpty(distDir);
|
|
||||||
|
|
||||||
if (!isMissingBuild && !forceRebuild) {
|
|
||||||
log(`Directory "${distDir}" exists and has content. Skipping build: '${buildCommand}'.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceRebuild) {
|
|
||||||
log(`Removing directory "${distDir}" for a clean build (triggered by --build flag).`);
|
|
||||||
await rmdir(distDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
log('Starting project build...');
|
|
||||||
const { error } = await runCommand(buildCommand, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
cwd: projectDir,
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
log(error, LOG_LEVELS.WARN); // Cannot disable Vue CLI errors, stderr contains false-positives.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAppName(projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
const packageData = await readPackageJsonContents(projectDir);
|
|
||||||
try {
|
|
||||||
const packageJson = JSON.parse(packageData);
|
|
||||||
if (!packageJson.name) {
|
|
||||||
die(`The 'package.json' file doesn't specify a name: ${packageData}`);
|
|
||||||
}
|
|
||||||
return packageJson.name;
|
|
||||||
} catch (error) {
|
|
||||||
die(`Unable to parse 'package.json'. Error: ${error}\nContent: ${packageData}`, LOG_LEVELS.ERROR);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readPackageJsonContents(projectDir) {
|
|
||||||
if (!projectDir) { throw new Error('missing project directory'); }
|
|
||||||
const packagePath = join(projectDir, 'package.json');
|
|
||||||
if (!await exists(packagePath)) {
|
|
||||||
die(`'package.json' file not found at ${packagePath}`);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const packageData = await readFile(packagePath, 'utf8');
|
|
||||||
return packageData;
|
|
||||||
} catch (error) {
|
|
||||||
log(`Error reading 'package.json' from ${packagePath}.`, LOG_LEVELS.ERROR);
|
|
||||||
die(`Error detail: ${error}`, LOG_LEVELS.ERROR);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { platform } from 'os';
|
|
||||||
|
|
||||||
export const SUPPORTED_PLATFORMS = {
|
|
||||||
MAC: 'darwin',
|
|
||||||
LINUX: 'linux',
|
|
||||||
WINDOWS: 'win32',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CURRENT_PLATFORM = platform();
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Description:
|
|
||||||
# This script ensures npm is available, removes existing node modules, optionally
|
|
||||||
# removes package-lock.json (when -n flag is used), installs dependencies and runs unit tests.
|
|
||||||
# Usage:
|
|
||||||
# ./fresh-npm-install.sh # Regular execution
|
|
||||||
# ./fresh-npm-install.sh -n # Non-deterministic mode (removes package-lock.json)
|
|
||||||
|
|
||||||
declare NON_DETERMINISTIC_FLAG=0
|
|
||||||
|
|
||||||
|
|
||||||
main() {
|
|
||||||
parse_args "$@"
|
|
||||||
ensure_npm_is_available
|
|
||||||
ensure_npm_root
|
|
||||||
remove_existing_modules
|
|
||||||
if [[ $NON_DETERMINISTIC_FLAG -eq 1 ]]; then
|
|
||||||
remove_package_lock_json
|
|
||||||
fi
|
|
||||||
install_dependencies
|
|
||||||
run_unit_tests
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_npm_is_available() {
|
|
||||||
if ! command -v npm &> /dev/null; then
|
|
||||||
log::fatal 'npm could not be found, please install it first.'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_npm_root() {
|
|
||||||
if [ ! -f package.json ]; then
|
|
||||||
log::fatal 'Current directory is not a npm root. Please run the script in a npm root directory.'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_existing_modules() {
|
|
||||||
if [ -d ./node_modules ]; then
|
|
||||||
log::info 'Removing existing node modules...'
|
|
||||||
if ! rm -rf ./node_modules; then
|
|
||||||
log::fatal 'Could not remove existing node modules.'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
install_dependencies() {
|
|
||||||
log::info 'Installing dependencies...'
|
|
||||||
if ! npm install; then
|
|
||||||
log::fatal 'Failed to install dependencies.'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_package_lock_json() {
|
|
||||||
if [ -f ./package-lock.json ]; then
|
|
||||||
log::info 'Removing package-lock.json...'
|
|
||||||
if ! rm -rf ./package-lock.json; then
|
|
||||||
log::fatal 'Could not remove package-lock.json.'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
run_unit_tests() {
|
|
||||||
log::info 'Running unit tests...'
|
|
||||||
if ! npm run test:unit; then
|
|
||||||
pwd
|
|
||||||
log::fatal 'Failed to run unit tests.'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info() {
|
|
||||||
local -r message="$1"
|
|
||||||
echo "📣 ${message}"
|
|
||||||
}
|
|
||||||
|
|
||||||
log::fatal() {
|
|
||||||
local -r message="$1"
|
|
||||||
echo "❌ ${message}" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_args() {
|
|
||||||
while getopts "n" opt; do
|
|
||||||
case ${opt} in
|
|
||||||
n)
|
|
||||||
NON_DETERMINISTIC_FLAG=1
|
|
||||||
;;
|
|
||||||
\?)
|
|
||||||
echo "Invalid option: $OPTARG" 1>&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$1"
|
|
||||||
199
scripts/npm-install.js
Normal file
199
scripts/npm-install.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
Description:
|
||||||
|
This script manages NPM dependencies for a project.
|
||||||
|
It offers capabilities like doing a fresh install, retries on network errors, and other features.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
npm run install-deps [-- <options>]
|
||||||
|
node scripts/npm-install.js [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--root-directory <path>
|
||||||
|
Specifies the root directory where package.json resides
|
||||||
|
Defaults to the current working directory.
|
||||||
|
Example: npm run install-deps -- --root-directory /your/path/here
|
||||||
|
|
||||||
|
--no-errors
|
||||||
|
Ignores errors and continues the execution.
|
||||||
|
Example: npm run install-deps -- --no-errors
|
||||||
|
|
||||||
|
--ci
|
||||||
|
Uses 'npm ci' for dependency installation instead of 'npm install'.
|
||||||
|
Example: npm run install-deps -- --ci
|
||||||
|
|
||||||
|
--fresh
|
||||||
|
Removes the existing node_modules directory before installing dependencies.
|
||||||
|
Example: npm run install-deps -- --fresh
|
||||||
|
|
||||||
|
--non-deterministic
|
||||||
|
Removes package-lock.json for a non-deterministic installation.
|
||||||
|
Example: npm run install-deps -- --non-deterministic
|
||||||
|
|
||||||
|
Note:
|
||||||
|
|
||||||
|
Flags can be combined as needed.
|
||||||
|
Example: npm run install-deps -- --fresh --non-deterministic
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { access, rm, unlink } from 'fs/promises';
|
||||||
|
import { constants } from 'fs';
|
||||||
|
|
||||||
|
const MAX_RETRIES = 5;
|
||||||
|
const RETRY_DELAY_IN_MS = 5 /* seconds */ * 1000;
|
||||||
|
const ARG_NAMES = {
|
||||||
|
rootDirectory: '--root-directory',
|
||||||
|
ignoreErrors: '--no-errors',
|
||||||
|
ci: '--ci',
|
||||||
|
fresh: '--fresh',
|
||||||
|
nonDeterministic: '--non-deterministic',
|
||||||
|
};
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const options = getOptions();
|
||||||
|
console.log('Options:', options);
|
||||||
|
await ensureNpmRootDirectory(options.rootDirectory);
|
||||||
|
await ensureNpmIsAvailable();
|
||||||
|
if (options.fresh) {
|
||||||
|
await removeNodeModules(options.rootDirectory);
|
||||||
|
}
|
||||||
|
if (options.nonDeterministic) {
|
||||||
|
await removePackageLockJson(options.rootDirectory);
|
||||||
|
}
|
||||||
|
const command = buildCommand(options.ci, options.outputErrors);
|
||||||
|
console.log('Starting dependency installation...');
|
||||||
|
const exitCode = await executeWithRetry(
|
||||||
|
command,
|
||||||
|
options.workingDirectory,
|
||||||
|
MAX_RETRIES,
|
||||||
|
RETRY_DELAY_IN_MS,
|
||||||
|
);
|
||||||
|
if (exitCode === 0) {
|
||||||
|
console.log('🎊 Installed dependencies...');
|
||||||
|
} else {
|
||||||
|
console.error(`💀 Failed to install dependencies, exit code: ${exitCode}`);
|
||||||
|
}
|
||||||
|
process.exit(exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeNodeModules(workingDirectory) {
|
||||||
|
const nodeModulesDirectory = resolve(workingDirectory, 'node_modules');
|
||||||
|
if (await exists('./node_modules')) {
|
||||||
|
console.log('Removing node_modules...');
|
||||||
|
await rm(nodeModulesDirectory, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removePackageLockJson(workingDirectory) {
|
||||||
|
const packageLockJsonFile = resolve(workingDirectory, 'package-lock.json');
|
||||||
|
if (await exists(packageLockJsonFile)) {
|
||||||
|
console.log('Removing package-lock.json...');
|
||||||
|
await unlink(packageLockJsonFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureNpmIsAvailable() {
|
||||||
|
const exitCode = await executeCommand('npm --version');
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error('`npm` in not available!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureNpmRootDirectory(workingDirectory) {
|
||||||
|
const packageJsonPath = resolve(workingDirectory, 'package.json');
|
||||||
|
if (!await exists(packageJsonPath)) {
|
||||||
|
throw new Error(`Not an NPM project root: ${workingDirectory}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCommand(ci, outputErrors) {
|
||||||
|
const baseCommand = ci ? 'npm ci' : 'npm install';
|
||||||
|
if (!outputErrors) {
|
||||||
|
return `${baseCommand} --loglevel=error`;
|
||||||
|
}
|
||||||
|
return baseCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions() {
|
||||||
|
const processArgs = process.argv.slice(2); // Slice off the node and script name
|
||||||
|
return {
|
||||||
|
rootDirectory: processArgs.includes('--root-directory') ? processArgs[processArgs.indexOf('--root-directory') + 1] : process.cwd(),
|
||||||
|
outputErrors: !processArgs.includes(ARG_NAMES.ignoreErrors),
|
||||||
|
ci: processArgs.includes(ARG_NAMES.ci),
|
||||||
|
fresh: processArgs.includes(ARG_NAMES.fresh),
|
||||||
|
nonDeterministic: processArgs.includes(ARG_NAMES.nonDeterministic),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeWithRetry(
|
||||||
|
command,
|
||||||
|
workingDirectory,
|
||||||
|
maxRetries,
|
||||||
|
retryDelayInMs,
|
||||||
|
currentAttempt = 1,
|
||||||
|
) {
|
||||||
|
const statusCode = await executeCommand(command, workingDirectory, true, true);
|
||||||
|
if (statusCode === 0 || currentAttempt >= maxRetries) {
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`⚠️🔄 Attempt ${currentAttempt} failed. Retrying in ${retryDelayInMs / 1000} seconds...`);
|
||||||
|
await sleep(retryDelayInMs);
|
||||||
|
|
||||||
|
const retryResult = await executeWithRetry(
|
||||||
|
command,
|
||||||
|
workingDirectory,
|
||||||
|
maxRetries,
|
||||||
|
retryDelayInMs,
|
||||||
|
currentAttempt + 1,
|
||||||
|
);
|
||||||
|
return retryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeCommand(
|
||||||
|
command,
|
||||||
|
workingDirectory = process.cwd(),
|
||||||
|
logStdout = false,
|
||||||
|
logCommand = false,
|
||||||
|
) {
|
||||||
|
if (logCommand) {
|
||||||
|
console.log(`▶️ Executing command "${command}" at "${workingDirectory}"`);
|
||||||
|
}
|
||||||
|
const process = exec(
|
||||||
|
command,
|
||||||
|
{
|
||||||
|
cwd: workingDirectory,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (logStdout) {
|
||||||
|
process.stdout.on('data', (data) => {
|
||||||
|
console.log(data.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.stderr.on('data', (data) => {
|
||||||
|
console.error(data.toString());
|
||||||
|
});
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
process.on('exit', (code) => {
|
||||||
|
resolve(code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(milliseconds) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, milliseconds);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exists(path) {
|
||||||
|
try {
|
||||||
|
await access(path, constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await main();
|
||||||
58
scripts/print-dist-dir.js
Normal file
58
scripts/print-dist-dir.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Description:
|
||||||
|
* This script determines the absolute path of a distribution directory based on CLI arguments
|
||||||
|
* and outputs its absolute path. It is designed to be run programmatically by other scripts.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/print-dist-dir.js [options]
|
||||||
|
*
|
||||||
|
* Options:
|
||||||
|
* --electron-unbundled Path for the unbundled Electron application
|
||||||
|
* --electron-bundled Path for the bundled Electron application
|
||||||
|
* --web Path for the web application
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { readFile } from 'fs/promises';
|
||||||
|
|
||||||
|
const DIST_DIRS_JSON_FILE_PATH = resolve(process.cwd(), 'dist-dirs.json'); // cannot statically import because ESLint does not support it https://github.com/eslint/eslint/discussions/15305
|
||||||
|
const CLI_ARGUMENTS = process.argv.slice(2);
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const distDirs = await readDistDirsJsonFile(DIST_DIRS_JSON_FILE_PATH);
|
||||||
|
const relativeDistDir = determineRelativeDistDir(distDirs, CLI_ARGUMENTS);
|
||||||
|
const absoluteDistDir = resolve(process.cwd(), relativeDistDir);
|
||||||
|
console.log(absoluteDistDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapCliFlagsToDistDirs(distDirs) {
|
||||||
|
return {
|
||||||
|
'--electron-unbundled': distDirs.electronUnbundled,
|
||||||
|
'--electron-bundled': distDirs.electronBundled,
|
||||||
|
'--web': distDirs.web,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function determineRelativeDistDir(distDirsJsonObject, cliArguments) {
|
||||||
|
const cliFlagDistDirMap = mapCliFlagsToDistDirs(distDirsJsonObject);
|
||||||
|
const availableCliFlags = Object.keys(cliFlagDistDirMap);
|
||||||
|
const requestedCliFlags = cliArguments.filter((arg) => {
|
||||||
|
return availableCliFlags.includes(arg);
|
||||||
|
});
|
||||||
|
if (!requestedCliFlags.length) {
|
||||||
|
throw new Error(`No distribution directory was requested. Please use one of these flags: ${availableCliFlags.join(', ')}`);
|
||||||
|
}
|
||||||
|
if (requestedCliFlags.length > 1) {
|
||||||
|
throw new Error(`Multiple distribution directories were requested, but this script only supports one: ${requestedCliFlags.join(', ')}`);
|
||||||
|
}
|
||||||
|
const selectedCliFlag = requestedCliFlags[0];
|
||||||
|
return cliFlagDistDirMap[selectedCliFlag];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readDistDirsJsonFile(absoluteConfigJsonFilePath) {
|
||||||
|
const fileContentAsText = await readFile(absoluteConfigJsonFilePath, 'utf8');
|
||||||
|
const parsedJsonData = JSON.parse(fileContentAsText);
|
||||||
|
return parsedJsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
await main();
|
||||||
133
scripts/verify-build-artifacts.js
Normal file
133
scripts/verify-build-artifacts.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Description:
|
||||||
|
* This script verifies the existence and content of build artifacts based on the
|
||||||
|
* provided CLI flags. It exists with exit code `0` if all verifications pass, otherwise
|
||||||
|
* with exit code `1`.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/verify-build-artifacts.js [options]
|
||||||
|
*
|
||||||
|
* Options:
|
||||||
|
* --electron-unbundled Verify artifacts for the unbundled Electron application.
|
||||||
|
* --electron-bundled Verify artifacts for the bundled Electron application.
|
||||||
|
* --web Verify artifacts for the web application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { access, readdir } from 'fs/promises';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
const PROCESS_ARGUMENTS = process.argv.slice(2);
|
||||||
|
const PRINT_DIST_DIR_SCRIPT_BASE_COMMAND = 'node scripts/print-dist-dir';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const buildConfigs = getBuildVerificationConfigs();
|
||||||
|
if (!anyCommandsFound(Object.keys(buildConfigs))) {
|
||||||
|
die(`No valid command found in process arguments. Expected one of: ${Object.keys(buildConfigs).join(', ')}`);
|
||||||
|
}
|
||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
for (const [command, config] of Object.entries(buildConfigs)) {
|
||||||
|
if (PROCESS_ARGUMENTS.includes(command)) {
|
||||||
|
const distDir = await executePrintDistDirScript(config.printDistDirScriptArgument);
|
||||||
|
await verifyDirectoryExists(distDir);
|
||||||
|
await verifyNonEmptyDirectory(distDir);
|
||||||
|
await verifyFilesExist(distDir, config.filePatterns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* eslint-enable no-await-in-loop */
|
||||||
|
console.log('✅ Build completed successfully and all expected artifacts are in place.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBuildVerificationConfigs() {
|
||||||
|
return {
|
||||||
|
'--electron-unbundled': {
|
||||||
|
printDistDirScriptArgument: '--electron-unbundled',
|
||||||
|
filePatterns: [
|
||||||
|
/main[/\\]index\.cjs/,
|
||||||
|
/preload[/\\]index\.cjs/,
|
||||||
|
/renderer[/\\]index\.htm(l)?/,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'--electron-bundled': {
|
||||||
|
printDistDirScriptArgument: '--electron-bundled',
|
||||||
|
filePatterns: [
|
||||||
|
/latest.*\.yml/, // generates latest.yml for auto-updates
|
||||||
|
/.*-\d+\.\d+\.\d+\..*/, // a file with extension and semantic version (packaged application)
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'--web': {
|
||||||
|
printDistDirScriptArgument: '--web',
|
||||||
|
filePatterns: [
|
||||||
|
/index\.htm(l)?/,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function anyCommandsFound(commands) {
|
||||||
|
return PROCESS_ARGUMENTS.some((arg) => commands.includes(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyDirectoryExists(directoryPath) {
|
||||||
|
try {
|
||||||
|
await access(directoryPath);
|
||||||
|
} catch (error) {
|
||||||
|
die(`Directory does not exist at \`${directoryPath}\`:\n\t${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyNonEmptyDirectory(directoryPath) {
|
||||||
|
const files = await readdir(directoryPath);
|
||||||
|
if (files.length === 0) {
|
||||||
|
die(`Directory is empty at \`${directoryPath}\``);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyFilesExist(directoryPath, filePatterns) {
|
||||||
|
const files = await listAllFilesRecursively(directoryPath);
|
||||||
|
for (const pattern of filePatterns) {
|
||||||
|
const match = files.some((file) => pattern.test(file));
|
||||||
|
if (!match) {
|
||||||
|
die(
|
||||||
|
`No file matches the pattern ${pattern.source} in directory \`${directoryPath}\``,
|
||||||
|
`\nFiles in directory:\n${files.map((file) => `\t- ${file}`).join('\n')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listAllFilesRecursively(directoryPath) {
|
||||||
|
const dir = await readdir(directoryPath, { withFileTypes: true });
|
||||||
|
const files = await Promise.all(dir.map(async (dirent) => {
|
||||||
|
const absolutePath = resolve(directoryPath, dirent.name);
|
||||||
|
if (dirent.isDirectory()) {
|
||||||
|
return listAllFilesRecursively(absolutePath);
|
||||||
|
}
|
||||||
|
return absolutePath;
|
||||||
|
}));
|
||||||
|
return files.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executePrintDistDirScript(flag) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const commandToRun = `${PRINT_DIST_DIR_SCRIPT_BASE_COMMAND} ${flag}`;
|
||||||
|
|
||||||
|
exec(commandToRun, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(`Execution failed with error: ${error}`));
|
||||||
|
} else if (stderr) {
|
||||||
|
reject(new Error(`Execution failed with stderr: ${stderr}`));
|
||||||
|
} else {
|
||||||
|
resolve(stdout.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function die(...message) {
|
||||||
|
console.error(...message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await main();
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export type Constructible<T, TArgs extends unknown[] = never> = {
|
export type Constructible<T, TArgs extends unknown[] = never> = {
|
||||||
prototype: T;
|
prototype: T;
|
||||||
apply: (this: unknown, args: TArgs) => void;
|
apply: (this: unknown, args: TArgs) => void;
|
||||||
|
readonly name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropertyKeys<T> = {
|
export type PropertyKeys<T> = {
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
import { Environment } from '@/infrastructure/Environment/Environment';
|
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
||||||
import { IEnvironment } from '@/infrastructure/Environment/IEnvironment';
|
|
||||||
import { IApplicationFactory } from '../IApplicationFactory';
|
import { IApplicationFactory } from '../IApplicationFactory';
|
||||||
import { ApplicationFactory } from '../ApplicationFactory';
|
import { ApplicationFactory } from '../ApplicationFactory';
|
||||||
import { ApplicationContext } from './ApplicationContext';
|
import { ApplicationContext } from './ApplicationContext';
|
||||||
|
|
||||||
export async function buildContext(
|
export async function buildContext(
|
||||||
factory: IApplicationFactory = ApplicationFactory.Current,
|
factory: IApplicationFactory = ApplicationFactory.Current,
|
||||||
environment = Environment.CurrentEnvironment,
|
environment = RuntimeEnvironment.CurrentEnvironment,
|
||||||
): Promise<IApplicationContext> {
|
): Promise<IApplicationContext> {
|
||||||
if (!factory) { throw new Error('missing factory'); }
|
if (!factory) { throw new Error('missing factory'); }
|
||||||
if (!environment) { throw new Error('missing environment'); }
|
if (!environment) { throw new Error('missing environment'); }
|
||||||
const app = await factory.getApp();
|
const app = await factory.getApp();
|
||||||
const os = getInitialOs(app, environment);
|
const os = getInitialOs(app, environment.os);
|
||||||
return new ApplicationContext(app, os);
|
return new ApplicationContext(app, os);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitialOs(app: IApplication, environment: IEnvironment): OperatingSystem {
|
function getInitialOs(app: IApplication, currentOs: OperatingSystem): OperatingSystem {
|
||||||
const currentOs = environment.os;
|
|
||||||
const supportedOsList = app.getSupportedOsList();
|
const supportedOsList = app.getSupportedOsList();
|
||||||
if (supportedOsList.includes(currentOs)) {
|
if (supportedOsList.includes(currentOs)) {
|
||||||
return currentOs;
|
return currentOs;
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ 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/Metadata/IAppMetadata';
|
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
||||||
import { AppMetadataFactory } from '@/infrastructure/Metadata/AppMetadataFactory';
|
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||||
import { parseCategoryCollection } from './CategoryCollectionParser';
|
import { parseCategoryCollection } from './CategoryCollectionParser';
|
||||||
|
|
||||||
export function parseApplication(
|
export function parseApplication(
|
||||||
categoryParser = parseCategoryCollection,
|
categoryParser = parseCategoryCollection,
|
||||||
informationParser = parseProjectInformation,
|
informationParser = parseProjectInformation,
|
||||||
metadata: IAppMetadata = AppMetadataFactory.Current.instance,
|
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
|
||||||
collectionsData = PreParsedCollections,
|
collectionsData = PreParsedCollections,
|
||||||
): IApplication {
|
): IApplication {
|
||||||
validateCollectionsData(collectionsData);
|
validateCollectionsData(collectionsData);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
||||||
import { Version } from '@/domain/Version';
|
import { Version } from '@/domain/Version';
|
||||||
import { AppMetadataFactory } from '@/infrastructure/Metadata/AppMetadataFactory';
|
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||||
import { ConstructorArguments } from '@/TypeHelpers';
|
import { ConstructorArguments } from '@/TypeHelpers';
|
||||||
|
|
||||||
export function
|
export function
|
||||||
parseProjectInformation(
|
parseProjectInformation(
|
||||||
metadata: IAppMetadata = AppMetadataFactory.Current.instance,
|
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
|
||||||
createProjectInformation: ProjectInformationFactory = (
|
createProjectInformation: ProjectInformationFactory = (
|
||||||
...args
|
...args
|
||||||
) => new ProjectInformation(...args),
|
) => new ProjectInformation(...args),
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { CompiledCode } from '../CompiledCode';
|
||||||
|
|
||||||
|
export interface CodeSegmentMerger {
|
||||||
|
mergeCodeParts(codeSegments: readonly CompiledCode[]): CompiledCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { CompiledCode } from '../CompiledCode';
|
||||||
|
import { CodeSegmentMerger } from './CodeSegmentMerger';
|
||||||
|
|
||||||
|
export class NewlineCodeSegmentMerger implements CodeSegmentMerger {
|
||||||
|
public mergeCodeParts(codeSegments: readonly CompiledCode[]): CompiledCode {
|
||||||
|
if (!codeSegments?.length) {
|
||||||
|
throw new Error('missing segments');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: joinCodeParts(codeSegments.map((f) => f.code)),
|
||||||
|
revertCode: joinCodeParts(codeSegments.map((f) => f.revertCode)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinCodeParts(codeSegments: readonly string[]): string {
|
||||||
|
return codeSegments
|
||||||
|
.filter((segment) => segment?.length > 0)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface ICompiledCode {
|
export interface CompiledCode {
|
||||||
readonly code: string;
|
readonly code: string;
|
||||||
readonly revertCode?: string;
|
readonly revertCode?: string;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
|
import { FunctionCall } from '../FunctionCall';
|
||||||
|
import type { SingleCallCompiler } from './SingleCall/SingleCallCompiler';
|
||||||
|
|
||||||
|
export interface FunctionCallCompilationContext {
|
||||||
|
readonly allFunctions: ISharedFunctionCollection;
|
||||||
|
readonly rootCallSequence: readonly FunctionCall[];
|
||||||
|
readonly singleCallCompiler: SingleCallCompiler;
|
||||||
|
}
|
||||||
@@ -1,149 +1,10 @@
|
|||||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
|
||||||
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
|
||||||
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
|
||||||
import { IExpressionsCompiler } from '../../../Expressions/IExpressionsCompiler';
|
|
||||||
import { ExpressionsCompiler } from '../../../Expressions/ExpressionsCompiler';
|
|
||||||
import { ISharedFunction, IFunctionCode } from '../../ISharedFunction';
|
|
||||||
import { FunctionCall } from '../FunctionCall';
|
import { FunctionCall } from '../FunctionCall';
|
||||||
import { FunctionCallArgumentCollection } from '../Argument/FunctionCallArgumentCollection';
|
import { CompiledCode } from './CompiledCode';
|
||||||
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
|
||||||
import { ICompiledCode } from './ICompiledCode';
|
|
||||||
|
|
||||||
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
export interface FunctionCallCompiler {
|
||||||
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
compileFunctionCalls(
|
||||||
|
calls: readonly FunctionCall[],
|
||||||
protected constructor(
|
|
||||||
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public compileCall(
|
|
||||||
calls: IFunctionCall[],
|
|
||||||
functions: ISharedFunctionCollection,
|
functions: ISharedFunctionCollection,
|
||||||
): ICompiledCode {
|
): CompiledCode;
|
||||||
if (!functions) { throw new Error('missing functions'); }
|
|
||||||
if (!calls) { throw new Error('missing calls'); }
|
|
||||||
if (calls.some((f) => !f)) { throw new Error('missing function call'); }
|
|
||||||
const context: ICompilationContext = {
|
|
||||||
allFunctions: functions,
|
|
||||||
callSequence: calls,
|
|
||||||
expressionsCompiler: this.expressionsCompiler,
|
|
||||||
};
|
|
||||||
const code = compileCallSequence(context);
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICompilationContext {
|
|
||||||
allFunctions: ISharedFunctionCollection;
|
|
||||||
callSequence: readonly IFunctionCall[];
|
|
||||||
expressionsCompiler: IExpressionsCompiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICompiledFunctionCall {
|
|
||||||
readonly code: string;
|
|
||||||
readonly revertCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileCallSequence(context: ICompilationContext): ICompiledFunctionCall {
|
|
||||||
const compiledFunctions = context.callSequence
|
|
||||||
.flatMap((call) => compileSingleCall(call, context));
|
|
||||||
return {
|
|
||||||
code: merge(compiledFunctions.map((f) => f.code)),
|
|
||||||
revertCode: merge(compiledFunctions.map((f) => f.revertCode)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileSingleCall(
|
|
||||||
call: IFunctionCall,
|
|
||||||
context: ICompilationContext,
|
|
||||||
): ICompiledFunctionCall[] {
|
|
||||||
const func = context.allFunctions.getFunctionByName(call.functionName);
|
|
||||||
ensureThatCallArgumentsExistInParameterDefinition(func, call.args);
|
|
||||||
if (func.body.code) { // Function with inline code
|
|
||||||
const compiledCode = compileCode(func.body.code, call.args, context.expressionsCompiler);
|
|
||||||
return [compiledCode];
|
|
||||||
}
|
|
||||||
// Function with inner calls
|
|
||||||
return func.body.calls
|
|
||||||
.map((innerCall) => {
|
|
||||||
const compiledArgs = compileArgs(innerCall.args, call.args, context.expressionsCompiler);
|
|
||||||
const compiledCall = new FunctionCall(innerCall.functionName, compiledArgs);
|
|
||||||
return compileSingleCall(compiledCall, context);
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileCode(
|
|
||||||
code: IFunctionCode,
|
|
||||||
args: IReadOnlyFunctionCallArgumentCollection,
|
|
||||||
compiler: IExpressionsCompiler,
|
|
||||||
): ICompiledFunctionCall {
|
|
||||||
return {
|
|
||||||
code: compiler.compileExpressions(code.execute, args),
|
|
||||||
revertCode: compiler.compileExpressions(code.revert, args),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileArgs(
|
|
||||||
argsToCompile: IReadOnlyFunctionCallArgumentCollection,
|
|
||||||
args: IReadOnlyFunctionCallArgumentCollection,
|
|
||||||
compiler: IExpressionsCompiler,
|
|
||||||
): IReadOnlyFunctionCallArgumentCollection {
|
|
||||||
return argsToCompile
|
|
||||||
.getAllParameterNames()
|
|
||||||
.map((parameterName) => {
|
|
||||||
const { argumentValue } = argsToCompile.getArgument(parameterName);
|
|
||||||
const compiledValue = compiler.compileExpressions(argumentValue, args);
|
|
||||||
return new FunctionCallArgument(parameterName, compiledValue);
|
|
||||||
})
|
|
||||||
.reduce((compiledArgs, arg) => {
|
|
||||||
compiledArgs.addArgument(arg);
|
|
||||||
return compiledArgs;
|
|
||||||
}, new FunctionCallArgumentCollection());
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge(codeParts: readonly string[]): string {
|
|
||||||
return codeParts
|
|
||||||
.filter((part) => part?.length > 0)
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureThatCallArgumentsExistInParameterDefinition(
|
|
||||||
func: ISharedFunction,
|
|
||||||
args: IReadOnlyFunctionCallArgumentCollection,
|
|
||||||
): void {
|
|
||||||
const callArgumentNames = args.getAllParameterNames();
|
|
||||||
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
|
|
||||||
const unexpectedParameters = findUnexpectedParameters(callArgumentNames, functionParameterNames);
|
|
||||||
throwIfNotEmpty(func.name, unexpectedParameters, functionParameterNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findUnexpectedParameters(
|
|
||||||
callArgumentNames: string[],
|
|
||||||
functionParameterNames: string[],
|
|
||||||
): string[] {
|
|
||||||
if (!callArgumentNames.length && !functionParameterNames.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return callArgumentNames
|
|
||||||
.filter((callParam) => !functionParameterNames.includes(callParam));
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwIfNotEmpty(
|
|
||||||
functionName: string,
|
|
||||||
unexpectedParameters: string[],
|
|
||||||
expectedParameters: string[],
|
|
||||||
) {
|
|
||||||
if (!unexpectedParameters.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
// eslint-disable-next-line prefer-template
|
|
||||||
`Function "${functionName}" has unexpected parameter(s) provided: `
|
|
||||||
+ `"${unexpectedParameters.join('", "')}"`
|
|
||||||
+ '. Expected parameter(s): '
|
|
||||||
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
||||||
|
import { FunctionCallCompiler } from './FunctionCallCompiler';
|
||||||
|
import { CompiledCode } from './CompiledCode';
|
||||||
|
import { FunctionCallCompilationContext } from './FunctionCallCompilationContext';
|
||||||
|
import { SingleCallCompiler } from './SingleCall/SingleCallCompiler';
|
||||||
|
import { AdaptiveFunctionCallCompiler } from './SingleCall/AdaptiveFunctionCallCompiler';
|
||||||
|
import { CodeSegmentMerger } from './CodeSegmentJoin/CodeSegmentMerger';
|
||||||
|
import { NewlineCodeSegmentMerger } from './CodeSegmentJoin/NewlineCodeSegmentMerger';
|
||||||
|
|
||||||
|
export class FunctionCallSequenceCompiler implements FunctionCallCompiler {
|
||||||
|
public static readonly instance: FunctionCallCompiler = new FunctionCallSequenceCompiler();
|
||||||
|
|
||||||
|
/* The constructor is protected to enforce the singleton pattern. */
|
||||||
|
protected constructor(
|
||||||
|
private readonly singleCallCompiler: SingleCallCompiler = new AdaptiveFunctionCallCompiler(),
|
||||||
|
private readonly codeSegmentMerger: CodeSegmentMerger = new NewlineCodeSegmentMerger(),
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public compileFunctionCalls(
|
||||||
|
calls: readonly FunctionCall[],
|
||||||
|
functions: ISharedFunctionCollection,
|
||||||
|
): CompiledCode {
|
||||||
|
if (!functions) { throw new Error('missing functions'); }
|
||||||
|
if (!calls?.length) { throw new Error('missing calls'); }
|
||||||
|
if (calls.some((f) => !f)) { throw new Error('missing function call'); }
|
||||||
|
const context: FunctionCallCompilationContext = {
|
||||||
|
allFunctions: functions,
|
||||||
|
rootCallSequence: calls,
|
||||||
|
singleCallCompiler: this.singleCallCompiler,
|
||||||
|
};
|
||||||
|
const codeSegments = context.rootCallSequence
|
||||||
|
.flatMap((call) => this.singleCallCompiler.compileSingleCall(call, context));
|
||||||
|
return this.codeSegmentMerger.mergeCodeParts(codeSegments);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
|
||||||
import { IFunctionCall } from '../IFunctionCall';
|
|
||||||
import { ICompiledCode } from './ICompiledCode';
|
|
||||||
|
|
||||||
export interface IFunctionCallCompiler {
|
|
||||||
compileCall(
|
|
||||||
calls: IFunctionCall[],
|
|
||||||
functions: ISharedFunctionCollection): ICompiledCode;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { FunctionCall } from '../../FunctionCall';
|
||||||
|
import { CompiledCode } from '../CompiledCode';
|
||||||
|
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '../../Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { ISharedFunction } from '../../../ISharedFunction';
|
||||||
|
import { SingleCallCompiler } from './SingleCallCompiler';
|
||||||
|
import { SingleCallCompilerStrategy } from './SingleCallCompilerStrategy';
|
||||||
|
import { InlineFunctionCallCompiler } from './Strategies/InlineFunctionCallCompiler';
|
||||||
|
import { NestedFunctionCallCompiler } from './Strategies/NestedFunctionCallCompiler';
|
||||||
|
|
||||||
|
export class AdaptiveFunctionCallCompiler implements SingleCallCompiler {
|
||||||
|
public constructor(
|
||||||
|
private readonly strategies: SingleCallCompilerStrategy[] = [
|
||||||
|
new InlineFunctionCallCompiler(),
|
||||||
|
new NestedFunctionCallCompiler(),
|
||||||
|
],
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public compileSingleCall(
|
||||||
|
call: FunctionCall,
|
||||||
|
context: FunctionCallCompilationContext,
|
||||||
|
): CompiledCode[] {
|
||||||
|
const func = context.allFunctions.getFunctionByName(call.functionName);
|
||||||
|
ensureThatCallArgumentsExistInParameterDefinition(func, call.args);
|
||||||
|
const strategy = this.findStrategy(func);
|
||||||
|
return strategy.compileFunction(func, call, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private findStrategy(func: ISharedFunction): SingleCallCompilerStrategy {
|
||||||
|
const strategies = this.strategies.filter((strategy) => strategy.canCompile(func));
|
||||||
|
if (strategies.length > 1) {
|
||||||
|
throw new Error('Multiple strategies found to compile the function call.');
|
||||||
|
}
|
||||||
|
if (strategies.length === 0) {
|
||||||
|
throw new Error('No strategies found to compile the function call.');
|
||||||
|
}
|
||||||
|
return strategies[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureThatCallArgumentsExistInParameterDefinition(
|
||||||
|
func: ISharedFunction,
|
||||||
|
callArguments: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
): void {
|
||||||
|
const callArgumentNames = callArguments.getAllParameterNames();
|
||||||
|
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
|
||||||
|
const unexpectedParameters = findUnexpectedParameters(callArgumentNames, functionParameterNames);
|
||||||
|
throwIfUnexpectedParametersExist(func.name, unexpectedParameters, functionParameterNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findUnexpectedParameters(
|
||||||
|
callArgumentNames: string[],
|
||||||
|
functionParameterNames: string[],
|
||||||
|
): string[] {
|
||||||
|
if (!callArgumentNames.length && !functionParameterNames.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return callArgumentNames
|
||||||
|
.filter((callParam) => !functionParameterNames.includes(callParam));
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwIfUnexpectedParametersExist(
|
||||||
|
functionName: string,
|
||||||
|
unexpectedParameters: string[],
|
||||||
|
expectedParameters: string[],
|
||||||
|
) {
|
||||||
|
if (!unexpectedParameters.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
// eslint-disable-next-line prefer-template
|
||||||
|
`Function "${functionName}" has unexpected parameter(s) provided: `
|
||||||
|
+ `"${unexpectedParameters.join('", "')}"`
|
||||||
|
+ '. Expected parameter(s): '
|
||||||
|
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { FunctionCall } from '../../FunctionCall';
|
||||||
|
import { CompiledCode } from '../CompiledCode';
|
||||||
|
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
||||||
|
|
||||||
|
export interface SingleCallCompiler {
|
||||||
|
compileSingleCall(
|
||||||
|
call: FunctionCall,
|
||||||
|
context: FunctionCallCompilationContext,
|
||||||
|
): CompiledCode[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import { CompiledCode } from '../CompiledCode';
|
||||||
|
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
||||||
|
|
||||||
|
export interface SingleCallCompilerStrategy {
|
||||||
|
canCompile(func: ISharedFunction): boolean;
|
||||||
|
compileFunction(
|
||||||
|
calledFunction: ISharedFunction,
|
||||||
|
callToFunction: FunctionCall,
|
||||||
|
context: FunctionCallCompilationContext,
|
||||||
|
): CompiledCode[],
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||||
|
|
||||||
|
export interface ArgumentCompiler {
|
||||||
|
createCompiledNestedCall(
|
||||||
|
nestedFunctionCall: FunctionCall,
|
||||||
|
parentFunctionCall: FunctionCall,
|
||||||
|
context: FunctionCallCompilationContext,
|
||||||
|
): FunctionCall;
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||||
|
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
|
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||||
|
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
|
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||||
|
import { ParsedFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/ParsedFunctionCall';
|
||||||
|
import { ArgumentCompiler } from './ArgumentCompiler';
|
||||||
|
|
||||||
|
export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
|
||||||
|
constructor(
|
||||||
|
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public createCompiledNestedCall(
|
||||||
|
nestedFunction: FunctionCall,
|
||||||
|
parentFunction: FunctionCall,
|
||||||
|
context: FunctionCallCompilationContext,
|
||||||
|
): FunctionCall {
|
||||||
|
const compiledArgs = compileNestedFunctionArguments(
|
||||||
|
nestedFunction,
|
||||||
|
parentFunction.args,
|
||||||
|
context,
|
||||||
|
this.expressionsCompiler,
|
||||||
|
);
|
||||||
|
const compiledCall = new ParsedFunctionCall(nestedFunction.functionName, compiledArgs);
|
||||||
|
return compiledCall;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileNestedFunctionArguments(
|
||||||
|
nestedFunction: FunctionCall,
|
||||||
|
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
context: FunctionCallCompilationContext,
|
||||||
|
expressionsCompiler: IExpressionsCompiler,
|
||||||
|
): IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
const requiredParameterNames = context
|
||||||
|
.allFunctions
|
||||||
|
.getRequiredParameterNames(nestedFunction.functionName);
|
||||||
|
const compiledArguments = nestedFunction.args
|
||||||
|
.getAllParameterNames()
|
||||||
|
// Compile each argument value
|
||||||
|
.map((paramName) => ({
|
||||||
|
parameterName: paramName,
|
||||||
|
compiledArgumentValue: compileArgument(
|
||||||
|
paramName,
|
||||||
|
nestedFunction,
|
||||||
|
parentFunctionArgs,
|
||||||
|
expressionsCompiler,
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
// Filter out arguments with absent values
|
||||||
|
.filter(({
|
||||||
|
parameterName,
|
||||||
|
compiledArgumentValue,
|
||||||
|
}) => isValidNonAbsentArgumentValue(
|
||||||
|
parameterName,
|
||||||
|
compiledArgumentValue,
|
||||||
|
requiredParameterNames,
|
||||||
|
))
|
||||||
|
/*
|
||||||
|
Create argument object with non-absent values.
|
||||||
|
This is done after eliminating absent values because otherwise creating argument object
|
||||||
|
with absent values throws error.
|
||||||
|
*/
|
||||||
|
.map(({
|
||||||
|
parameterName,
|
||||||
|
compiledArgumentValue,
|
||||||
|
}) => new FunctionCallArgument(parameterName, compiledArgumentValue));
|
||||||
|
return buildArgumentCollectionFromArguments(compiledArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidNonAbsentArgumentValue(
|
||||||
|
parameterName: string,
|
||||||
|
argumentValue: string | undefined,
|
||||||
|
requiredParameterNames: string[],
|
||||||
|
): boolean {
|
||||||
|
if (argumentValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!requiredParameterNames.includes(parameterName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new Error(`Compilation resulted in empty value for required parameter: "${parameterName}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileArgument(
|
||||||
|
parameterName: string,
|
||||||
|
nestedFunction: FunctionCall,
|
||||||
|
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
expressionsCompiler: IExpressionsCompiler,
|
||||||
|
): string {
|
||||||
|
try {
|
||||||
|
const { argumentValue: codeInArgument } = nestedFunction.args.getArgument(parameterName);
|
||||||
|
return expressionsCompiler.compileExpressions(codeInArgument, parentFunctionArgs);
|
||||||
|
} catch (err) {
|
||||||
|
throw new AggregateError([err], `Error when compiling argument for "${parameterName}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildArgumentCollectionFromArguments(
|
||||||
|
args: FunctionCallArgument[],
|
||||||
|
): FunctionCallArgumentCollection {
|
||||||
|
return args.reduce((compiledArgs, arg) => {
|
||||||
|
compiledArgs.addArgument(arg);
|
||||||
|
return compiledArgs;
|
||||||
|
}, new FunctionCallArgumentCollection());
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||||
|
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||||
|
import { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
|
||||||
|
|
||||||
|
export class InlineFunctionCallCompiler implements SingleCallCompilerStrategy {
|
||||||
|
public constructor(
|
||||||
|
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public canCompile(func: ISharedFunction): boolean {
|
||||||
|
return func.body.code !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public compileFunction(
|
||||||
|
calledFunction: ISharedFunction,
|
||||||
|
callToFunction: FunctionCall,
|
||||||
|
): CompiledCode[] {
|
||||||
|
const { code } = calledFunction.body;
|
||||||
|
const { args } = callToFunction;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
code: this.expressionsCompiler.compileExpressions(code.execute, args),
|
||||||
|
revertCode: this.expressionsCompiler.compileExpressions(code.revert, args),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||||
|
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||||
|
import { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
|
||||||
|
import { ArgumentCompiler } from './Argument/ArgumentCompiler';
|
||||||
|
import { NestedFunctionArgumentCompiler } from './Argument/NestedFunctionArgumentCompiler';
|
||||||
|
|
||||||
|
export class NestedFunctionCallCompiler implements SingleCallCompilerStrategy {
|
||||||
|
public constructor(
|
||||||
|
private readonly argumentCompiler: ArgumentCompiler = new NestedFunctionArgumentCompiler(),
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public canCompile(func: ISharedFunction): boolean {
|
||||||
|
return func.body.calls !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public compileFunction(
|
||||||
|
calledFunction: ISharedFunction,
|
||||||
|
callToFunction: FunctionCall,
|
||||||
|
context: FunctionCallCompilationContext,
|
||||||
|
): CompiledCode[] {
|
||||||
|
const nestedCalls = calledFunction.body.calls;
|
||||||
|
return nestedCalls.map((nestedCall) => {
|
||||||
|
try {
|
||||||
|
const compiledParentCall = this.argumentCompiler
|
||||||
|
.createCompiledNestedCall(nestedCall, callToFunction, context);
|
||||||
|
const compiledNestedCall = context.singleCallCompiler
|
||||||
|
.compileSingleCall(compiledParentCall, context);
|
||||||
|
return compiledNestedCall;
|
||||||
|
} catch (err) {
|
||||||
|
throw new AggregateError([err], `Error with call to "${nestedCall.functionName}" function from "${callToFunction.functionName}" function`);
|
||||||
|
}
|
||||||
|
}).flat();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,6 @@
|
|||||||
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
import { IFunctionCall } from './IFunctionCall';
|
|
||||||
|
|
||||||
export class FunctionCall implements IFunctionCall {
|
export interface FunctionCall {
|
||||||
constructor(
|
readonly functionName: string;
|
||||||
public readonly functionName: string,
|
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
||||||
public readonly args: IReadOnlyFunctionCallArgumentCollection,
|
|
||||||
) {
|
|
||||||
if (!functionName) {
|
|
||||||
throw new Error('missing function name in function call');
|
|
||||||
}
|
|
||||||
if (!args) {
|
|
||||||
throw new Error('missing args');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
||||||
import { IFunctionCall } from './IFunctionCall';
|
import { FunctionCall } from './FunctionCall';
|
||||||
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
||||||
import { FunctionCall } from './FunctionCall';
|
import { ParsedFunctionCall } from './ParsedFunctionCall';
|
||||||
|
|
||||||
export function parseFunctionCalls(calls: FunctionCallsData): IFunctionCall[] {
|
export function parseFunctionCalls(calls: FunctionCallsData): FunctionCall[] {
|
||||||
if (calls === undefined) {
|
if (calls === undefined) {
|
||||||
throw new Error('missing call data');
|
throw new Error('missing call data');
|
||||||
}
|
}
|
||||||
@@ -22,12 +22,12 @@ function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
|
|||||||
return [calls as FunctionCallData];
|
return [calls as FunctionCallData];
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
function parseFunctionCall(call: FunctionCallData): FunctionCall {
|
||||||
if (!call) {
|
if (!call) {
|
||||||
throw new Error('missing call data');
|
throw new Error('missing call data');
|
||||||
}
|
}
|
||||||
const callArgs = parseArgs(call.parameters);
|
const callArgs = parseArgs(call.parameters);
|
||||||
return new FunctionCall(call.function, callArgs);
|
return new ParsedFunctionCall(call.function, callArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseArgs(
|
function parseArgs(
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
|
||||||
|
|
||||||
export interface IFunctionCall {
|
|
||||||
readonly functionName: string;
|
|
||||||
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { FunctionCall } from './FunctionCall';
|
||||||
|
|
||||||
|
export class ParsedFunctionCall implements FunctionCall {
|
||||||
|
constructor(
|
||||||
|
public readonly functionName: string,
|
||||||
|
public readonly args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
) {
|
||||||
|
if (!functionName) {
|
||||||
|
throw new Error('missing function name in function call');
|
||||||
|
}
|
||||||
|
if (!args) {
|
||||||
|
throw new Error('missing args');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
import { IFunctionCall } from './Call/IFunctionCall';
|
import { FunctionCall } from './Call/FunctionCall';
|
||||||
|
|
||||||
export interface ISharedFunction {
|
export interface ISharedFunction {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
@@ -9,8 +9,8 @@ export interface ISharedFunction {
|
|||||||
|
|
||||||
export interface ISharedFunctionBody {
|
export interface ISharedFunctionBody {
|
||||||
readonly type: FunctionBodyType;
|
readonly type: FunctionBodyType;
|
||||||
readonly code: IFunctionCode;
|
readonly code: IFunctionCode | undefined;
|
||||||
readonly calls: readonly IFunctionCall[];
|
readonly calls: readonly FunctionCall[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FunctionBodyType {
|
export enum FunctionBodyType {
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ import { ISharedFunction } from './ISharedFunction';
|
|||||||
|
|
||||||
export interface ISharedFunctionCollection {
|
export interface ISharedFunctionCollection {
|
||||||
getFunctionByName(name: string): ISharedFunction;
|
getFunctionByName(name: string): ISharedFunction;
|
||||||
|
getRequiredParameterNames(functionName: string): string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IFunctionCall } from './Call/IFunctionCall';
|
import { FunctionCall } from './Call/FunctionCall';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FunctionBodyType, IFunctionCode, ISharedFunction, ISharedFunctionBody,
|
FunctionBodyType, IFunctionCode, ISharedFunction, ISharedFunctionBody,
|
||||||
@@ -8,7 +8,7 @@ import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParam
|
|||||||
export function createCallerFunction(
|
export function createCallerFunction(
|
||||||
name: string,
|
name: string,
|
||||||
parameters: IReadOnlyFunctionParameterCollection,
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
callSequence: readonly IFunctionCall[],
|
callSequence: readonly FunctionCall[],
|
||||||
): ISharedFunction {
|
): ISharedFunction {
|
||||||
if (!callSequence || !callSequence.length) {
|
if (!callSequence || !callSequence.length) {
|
||||||
throw new Error(`missing call sequence in function "${name}"`);
|
throw new Error(`missing call sequence in function "${name}"`);
|
||||||
@@ -38,7 +38,7 @@ class SharedFunction implements ISharedFunction {
|
|||||||
constructor(
|
constructor(
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
||||||
content: IFunctionCode | readonly IFunctionCall[],
|
content: IFunctionCode | readonly FunctionCall[],
|
||||||
bodyType: FunctionBodyType,
|
bodyType: FunctionBodyType,
|
||||||
) {
|
) {
|
||||||
if (!name) { throw new Error('missing function name'); }
|
if (!name) { throw new Error('missing function name'); }
|
||||||
@@ -46,7 +46,7 @@ class SharedFunction implements ISharedFunction {
|
|||||||
this.body = {
|
this.body = {
|
||||||
type: bodyType,
|
type: bodyType,
|
||||||
code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined,
|
code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined,
|
||||||
calls: bodyType === FunctionBodyType.Calls ? content as readonly IFunctionCall[] : undefined,
|
calls: bodyType === FunctionBodyType.Calls ? content as readonly FunctionCall[] : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
|||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getRequiredParameterNames(functionName: string): string[] {
|
||||||
|
return this
|
||||||
|
.getFunctionByName(functionName)
|
||||||
|
.parameters
|
||||||
|
.all
|
||||||
|
.filter((parameter) => !parameter.isOptional)
|
||||||
|
.map((parameter) => parameter.name);
|
||||||
|
}
|
||||||
|
|
||||||
private has(functionName: string) {
|
private has(functionName: string) {
|
||||||
return this.functionsByName.has(functionName);
|
return this.functionsByName.has(functionName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmp
|
|||||||
import { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
|
import { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
|
||||||
import { IScriptCompiler } from './IScriptCompiler';
|
import { IScriptCompiler } from './IScriptCompiler';
|
||||||
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
||||||
import { IFunctionCallCompiler } from './Function/Call/Compiler/IFunctionCallCompiler';
|
import { FunctionCallSequenceCompiler } from './Function/Call/Compiler/FunctionCallSequenceCompiler';
|
||||||
import { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
|
import { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
|
||||||
import { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
|
import { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
|
||||||
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
||||||
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
|
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
|
||||||
import { ICompiledCode } from './Function/Call/Compiler/ICompiledCode';
|
import { CompiledCode } from './Function/Call/Compiler/CompiledCode';
|
||||||
|
|
||||||
export class ScriptCompiler implements IScriptCompiler {
|
export class ScriptCompiler implements IScriptCompiler {
|
||||||
private readonly functions: ISharedFunctionCollection;
|
private readonly functions: ISharedFunctionCollection;
|
||||||
@@ -21,7 +21,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
functions: readonly FunctionData[] | undefined,
|
functions: readonly FunctionData[] | undefined,
|
||||||
syntax: ILanguageSyntax,
|
syntax: ILanguageSyntax,
|
||||||
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
||||||
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
private readonly callCompiler: FunctionCallCompiler = FunctionCallSequenceCompiler.instance,
|
||||||
private readonly codeValidator: ICodeValidator = CodeValidator.instance,
|
private readonly codeValidator: ICodeValidator = CodeValidator.instance,
|
||||||
) {
|
) {
|
||||||
if (!syntax) { throw new Error('missing syntax'); }
|
if (!syntax) { throw new Error('missing syntax'); }
|
||||||
@@ -40,7 +40,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
if (!script) { throw new Error('missing script'); }
|
if (!script) { throw new Error('missing script'); }
|
||||||
try {
|
try {
|
||||||
const calls = parseFunctionCalls(script.call);
|
const calls = parseFunctionCalls(script.call);
|
||||||
const compiledCode = this.callCompiler.compileCall(calls, this.functions);
|
const compiledCode = this.callCompiler.compileFunctionCalls(calls, this.functions);
|
||||||
validateCompiledCode(compiledCode, this.codeValidator);
|
validateCompiledCode(compiledCode, this.codeValidator);
|
||||||
return new ScriptCode(
|
return new ScriptCode(
|
||||||
compiledCode.code,
|
compiledCode.code,
|
||||||
@@ -52,7 +52,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateCompiledCode(compiledCode: ICompiledCode, validator: ICodeValidator): void {
|
function validateCompiledCode(compiledCode: CompiledCode, validator: ICodeValidator): void {
|
||||||
[compiledCode.code, compiledCode.revertCode].forEach(
|
[compiledCode.code, compiledCode.revertCode].forEach(
|
||||||
(code) => validator.throwIfInvalid(code, [new NoEmptyLines()]),
|
(code) => validator.throwIfInvalid(code, [new NoEmptyLines()]),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3558,82 +3558,98 @@ functions:
|
|||||||
parameters:
|
parameters:
|
||||||
- name: prefName
|
- name: prefName
|
||||||
- name: jsonValue
|
- name: jsonValue
|
||||||
# prefs.js file (https://web.archive.org/web/20221029211757/https://kb.mozillazine.org/Prefs.js_file) exists at
|
docs: |-
|
||||||
# - Default installation:
|
This script either creates or updates the `user.js` file to set specific Mozilla Firefox preferences.
|
||||||
# ~/.mozilla/firefox/<profile-name>/prefs.js
|
|
||||||
# - Flatpak installation:
|
The `user.js` file can be found in a Firefox profile folder [1] and its location depends on the type of installation:
|
||||||
# ~/.var/app/org.mozilla.firefox/.mozilla/firefox/<profile-name>/prefs.js
|
|
||||||
# - Snap installation:
|
- Default: `~/.mozilla/firefox/<profile-name>/user.js`
|
||||||
# ~/snap/firefox/common/.mozilla/firefox/<profile-name>/prefs.js
|
- Flatpak: `~/.var/app/org.mozilla.firefox/.mozilla/firefox/<profile-name>/user.js`
|
||||||
|
- Snap: `~/snap/firefox/common/.mozilla/firefox/<profile-name>/user.js`
|
||||||
|
|
||||||
|
While the `user.js` file is optional [2], if it's present, the Firefox application will prioritize its settings over
|
||||||
|
those in `prefs.js` upon startup [1][2]. To prevent potential profile corruption, Mozilla advises against editing
|
||||||
|
`prefs.js` directly [2].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230811005205/https://kb.mozillazine.org/User.js_file "User.js file - MozillaZine Knowledge Base"
|
||||||
|
[2]: https://web.archive.org/web/20221029211757/https://kb.mozillazine.org/Prefs.js_file "Prefs.js file - MozillaZine Knowledge Base"
|
||||||
code: |-
|
code: |-
|
||||||
pref_name='{{ $prefName }}'
|
pref_name='{{ $prefName }}'
|
||||||
pref_value='{{ $jsonValue }}'
|
pref_value='{{ $jsonValue }}'
|
||||||
echo "Setting preference \"$pref_name\" to \"$pref_value\"."
|
echo "Setting preference \"$pref_name\" to \"$pref_value\"."
|
||||||
pref_file_paths=(
|
declare -a profile_paths=(
|
||||||
~/.mozilla/firefox/*/prefs.js
|
~/.mozilla/firefox/*/
|
||||||
~/.var/app/org.mozilla.firefox/.mozilla/firefox/*/prefs.js
|
~/.var/app/org.mozilla.firefox/.mozilla/firefox/*/
|
||||||
~/snap/firefox/common/.mozilla/firefox/*/prefs.js
|
~/snap/firefox/common/.mozilla/firefox/*/
|
||||||
)
|
)
|
||||||
declare -i total_files_found=0
|
declare -i total_profiles_found=0
|
||||||
for pref_file in "${pref_file_paths[@]}"; do
|
for profile_dir in "${profile_paths[@]}"; do
|
||||||
if [ -f "$pref_file" ]; then
|
if [ ! -d "$profile_dir" ]; then
|
||||||
((total_files_found++))
|
continue
|
||||||
echo "$pref_file:"
|
fi
|
||||||
pref_start="user_pref(\"$pref_name\","
|
if [[ ! "$(basename "$profile_dir")" =~ ^[a-z0-9]{8}\..+ ]]; then
|
||||||
pref_line="user_pref(\"$pref_name\", $pref_value);"
|
continue # Not a profile folder
|
||||||
if ! grep --quiet "^$pref_start" "${pref_file}"; then
|
fi
|
||||||
echo $'\t'"Preference is not configured before."
|
((total_profiles_found++))
|
||||||
echo -n $'\n'"$pref_line" >> "$pref_file"
|
user_js_file="${profile_dir}user.js"
|
||||||
echo $'\t'"Successfully configured."
|
echo "$user_js_file:"
|
||||||
else
|
if [ ! -f "$user_js_file" ]; then
|
||||||
if grep --quiet "^$pref_line$" "${pref_file}"; then
|
touch "$user_js_file"
|
||||||
echo $'\t'"Skipping. Preference is already configured as expected."
|
echo $'\t''Created new user.js file'
|
||||||
else
|
fi
|
||||||
sed --in-place "/^$pref_start/d" "$pref_file"
|
pref_start="user_pref(\"$pref_name\","
|
||||||
echo $'\t'"Deleted assignment with unexpected value."
|
pref_line="user_pref(\"$pref_name\", $pref_value);"
|
||||||
echo -n $'\n'"$pref_line" >> "$pref_file"
|
if ! grep --quiet "^$pref_start" "${user_js_file}"; then
|
||||||
echo $'\t'"Successfully reconfigured with expected value."
|
echo -n $'\n'"$pref_line" >> "$user_js_file"
|
||||||
fi
|
echo $'\t'"Successfully added a new preference in $user_js_file."
|
||||||
fi
|
elif grep --quiet "^$pref_line$" "$user_js_file"; then
|
||||||
|
echo $'\t'"Skipping, preference is already set as expected in $user_js_file."
|
||||||
|
else
|
||||||
|
sed --in-place "/^$pref_start/c\\$pref_line" "$user_js_file"
|
||||||
|
echo $'\t'"Successfully replaced the existing incorrect preference in $user_js_file."
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [ "$total_files_found" -eq 0 ]; then
|
if [ "$total_profiles_found" -eq 0 ]; then
|
||||||
echo "No changes, no preference file is found."
|
echo 'No profile folders are found, no changes are made.'
|
||||||
else
|
else
|
||||||
echo "Ensured that $total_files_found profiles are compilant."
|
echo "Preferences verified in $total_profiles_found profiles."
|
||||||
fi
|
fi
|
||||||
revertCode: |-
|
revertCode: |-
|
||||||
pref_name='{{ $prefName }}'
|
pref_name='{{ $prefName }}'
|
||||||
pref_value='{{ $jsonValue }}'
|
pref_value='{{ $jsonValue }}'
|
||||||
echo "Restoring \"$pref_name\" to its default."
|
echo "Reverting preference: \"$pref_name\" to its default."
|
||||||
pref_file_paths=(
|
declare -a profile_paths=(
|
||||||
~/.mozilla/firefox/*/prefs.js
|
~/.mozilla/firefox/*/
|
||||||
~/.var/app/org.mozilla.firefox/.mozilla/firefox/*/prefs.js
|
~/.var/app/org.mozilla.firefox/.mozilla/firefox/*/
|
||||||
~/snap/firefox/common/.mozilla/firefox/*/prefs.js
|
~/snap/firefox/common/.mozilla/firefox/*/
|
||||||
)
|
)
|
||||||
declare -i total_files_found=0
|
declare -i total_profiles_found=0
|
||||||
for pref_file in "${pref_file_paths[@]}"; do
|
for profile_dir in "${profile_paths[@]}"; do
|
||||||
if [ -f "$pref_file" ]; then
|
user_js_file="${profile_dir}user.js"
|
||||||
((total_files_found++))
|
if [ ! -f "$user_js_file" ]; then
|
||||||
echo "$pref_file:"
|
continue
|
||||||
pref_start="user_pref(\"$pref_name\","
|
fi
|
||||||
pref_line="user_pref(\"$pref_name\", $pref_value);"
|
((total_profiles_found++))
|
||||||
if ! grep --quiet "^$pref_start" "${pref_file}"; then
|
echo "$user_js_file:"
|
||||||
echo $'\t'"Skipping. Preference is not configured before."
|
pref_start="user_pref(\"$pref_name\","
|
||||||
else
|
pref_line="user_pref(\"$pref_name\", $pref_value);"
|
||||||
if grep --quiet "^$pref_line$" "${pref_file}"; then
|
if ! grep --quiet "^$pref_start" "${user_js_file}"; then
|
||||||
sed --in-place "/^$pref_line/d" "$pref_file"
|
echo $'\t''Skipping, preference was not configured before.'
|
||||||
echo $'\t'"Successfully restored preference value to its default."
|
elif grep --quiet "^$pref_line$" "${user_js_file}"; then
|
||||||
else
|
sed --in-place "/^$pref_line/d" "$user_js_file"
|
||||||
echo $'\t'"Skipping, the preference has value that is not configured by privacy.sexy."
|
echo $'\t''Successfully reverted preference to default.'
|
||||||
fi
|
if ! grep --quiet '[^[:space:]]' "$user_js_file"; then
|
||||||
|
rm "$user_js_file"
|
||||||
|
echo $'\t''Removed user.js file as it became empty.'
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo $'\t''Skipping, the preference has value that is not configured by privacy.sexy.'
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [ "$total_files_found" -eq 0 ]; then
|
if [ "$total_profiles_found" -eq 0 ]; then
|
||||||
echo "No changes, no preference file is found."
|
echo 'No reversion was necessary.'
|
||||||
else
|
else
|
||||||
echo "Ensured that $total_files_found profiles are compilant."
|
echo "Preferences verified in $total_profiles_found profiles."
|
||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: RenameFile
|
name: RenameFile
|
||||||
|
|||||||
@@ -606,9 +606,46 @@ actions:
|
|||||||
wevtutil.exe cl %1 "%%i"
|
wevtutil.exe cl %1 "%%i"
|
||||||
)
|
)
|
||||||
-
|
-
|
||||||
name: Clean Windows Defender scan history
|
name: Clear Defender scan (protection) history
|
||||||
docs: https://www.thewindowsclub.com/clear-windows-defender-protection-history
|
docs: |-
|
||||||
code: del "%ProgramData%\Microsoft\Windows Defender\Scans\History\" /s /f /q
|
This script deletes the scan history kept by Windows Defender on your computer. Windows Defender logs detected threats but also gathers
|
||||||
|
and stores data about various other files it scans [1] [2]. While removing this history enhances your privacy, it might decrease security,
|
||||||
|
as these logs assist in monitoring threats. By eliminating traces of your system's files, activities and any threats detected, you ensure
|
||||||
|
no residual data can be utilized to study or analyze your computer's activities, thus protecting your privacy.
|
||||||
|
|
||||||
|
Defender keeps a log of various details whenever it scans your computer for threats. This includes [3] [4]:
|
||||||
|
|
||||||
|
- **Time**: The moment the threat was discovered.
|
||||||
|
- **Threat Status**: The action carried out against the threat.
|
||||||
|
- **Virus Type**: The type or category of the virus.
|
||||||
|
- **Threat ID**: A unique identifier for the threat.
|
||||||
|
- **Virus Name**: The name of the virus.
|
||||||
|
- **File Path**: The location of the threat on your computer.
|
||||||
|
- **File Hash**: A unique code representing the file.
|
||||||
|
- **Quarantine File Name (GUID)**: The name given to the quarantined threat.
|
||||||
|
- **File Size**: The size of the file.
|
||||||
|
|
||||||
|
When you first set up Windows, it conducts an initial scan [1]. This scan identifies system files that won't require future
|
||||||
|
scans [1]. These 'safe' files are saved in a unique folder, which becomes a part of the scan history [1].
|
||||||
|
|
||||||
|
If a threat is recognized, Windows Defender will notify you [4]. Regardless of whether you choose to run the file or not, a
|
||||||
|
`DetectionHistory` file is created [2]. This file is stored in a specific folder
|
||||||
|
(`%ProgramData%\Microsoft\Windows Defender\Scans\History\Service\DetectionHistory\[numbered folder]\`), and it contains a
|
||||||
|
system-generated ID for the event [2].
|
||||||
|
|
||||||
|
> **Caution**: Deleting these logs may decrease your security. These logs help in keeping track of potential threats and their sources,
|
||||||
|
allowing for a more proactive response in future encounters. Without this history, Windows Defender might not recognize recurring threats
|
||||||
|
as quickly, possibly leaving your system more vulnerable. It's essential to understand that you're making a trade-off between enhanced
|
||||||
|
privacy and potentially reduced security.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230829142700/https://download.microsoft.com/download/7/e/7/7e7662cf-cbea-470b-a97e-ce7ce0d98dc2/win7perf.docx "Performance Testing Guide for Windows | Microsoft"
|
||||||
|
[2]: https://web.archive.org/web/20230829143754/https://www.sans.org/blog/uncovering-windows-defender-real-time-protection-history-with-dhparser/ "Uncovering Windows Defender Real-time Protection History with DHParser | SANS Alumni Blog"
|
||||||
|
[3]: https://web.archive.org/web/20230829144957/https://learn.microsoft.com/en-us/previous-versions/windows/desktop/defender/msft-mpthreatdetection "MSFT\_MpThreatDetection class | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230829144434/https://forensafe.com/blogs/windows_defender.html "Windows Defender | Forensafe"
|
||||||
|
call:
|
||||||
|
function: RunInlineCodeAsTrustedInstaller # Otherwise it cannot access/delete files under `Scans\History`, see https://github.com/undergroundwires/privacy.sexy/issues/246
|
||||||
|
parameters:
|
||||||
|
code: del "%ProgramData%\Microsoft\Windows Defender\Scans\History" /s /f /q
|
||||||
-
|
-
|
||||||
name: Clear credentials from Windows Credential Manager
|
name: Clear credentials from Windows Credential Manager
|
||||||
code: |-
|
code: |-
|
||||||
@@ -1094,32 +1131,130 @@ actions:
|
|||||||
serviceName: wercplsupport # Check: (Get-Service -Name wercplsupport).StartType
|
serviceName: wercplsupport # Check: (Get-Service -Name wercplsupport).StartType
|
||||||
defaultStartupMode: Manual # Allowed values: Automatic | Manual
|
defaultStartupMode: Manual # Allowed values: Automatic | Manual
|
||||||
-
|
-
|
||||||
category: Disable automatic driver updates by Windows Update
|
category: Disable Windows Update data collection
|
||||||
children:
|
children:
|
||||||
-
|
|
||||||
name: Disable device metadata retrieval (breaks auto updates)
|
|
||||||
recommend: strict
|
|
||||||
docs:
|
|
||||||
- https://www.stigviewer.com/stig/windows_server_2012_member_server/2014-01-07/finding/V-21964
|
|
||||||
- https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-deviceinstallation#deviceinstallation-preventdevicemetadatafromnetwork
|
|
||||||
code: |-
|
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 1 /f
|
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 1 /f
|
|
||||||
revertCode: |-
|
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 0 /f
|
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 0 /f
|
|
||||||
-
|
-
|
||||||
name: Do not include drivers with Windows Updates
|
category: Disable automatic driver updates by Windows Update
|
||||||
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsUpdate::ExcludeWUDriversInQualityUpdate
|
children:
|
||||||
recommend: strict
|
-
|
||||||
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v "ExcludeWUDriversInQualityUpdate" /t REG_DWORD /d 1 /f
|
name: Disable device metadata retrieval (breaks auto updates)
|
||||||
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v "ExcludeWUDriversInQualityUpdate" /t REG_DWORD /d 0 /f
|
recommend: strict
|
||||||
|
docs:
|
||||||
|
- https://www.stigviewer.com/stig/windows_server_2012_member_server/2014-01-07/finding/V-21964
|
||||||
|
- https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-deviceinstallation#deviceinstallation-preventdevicemetadatafromnetwork
|
||||||
|
code: |-
|
||||||
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 1 /f
|
||||||
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 1 /f
|
||||||
|
revertCode: |-
|
||||||
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 0 /f
|
||||||
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" /v "PreventDeviceMetadataFromNetwork" /t REG_DWORD /d 0 /f
|
||||||
|
-
|
||||||
|
name: Do not include drivers with Windows Updates
|
||||||
|
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsUpdate::ExcludeWUDriversInQualityUpdate
|
||||||
|
recommend: strict
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v "ExcludeWUDriversInQualityUpdate" /t REG_DWORD /d 1 /f
|
||||||
|
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v "ExcludeWUDriversInQualityUpdate" /t REG_DWORD /d 0 /f
|
||||||
|
-
|
||||||
|
name: Prevent Windows Update for device driver search
|
||||||
|
docs: https://www.stigviewer.com/stig/windows_7/2018-02-12/finding/V-21965
|
||||||
|
recommend: strict
|
||||||
|
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 0 /f
|
||||||
|
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 1 /f
|
||||||
-
|
-
|
||||||
name: Prevent Windows Update for device driver search
|
category: Disable obtaining updates from other PCs on the Internet (delivery optimization)
|
||||||
docs: https://www.stigviewer.com/stig/windows_7/2018-02-12/finding/V-21965
|
docs: |-
|
||||||
recommend: strict
|
Windows Delivery Optimization is a feature introduced by Microsoft to facilitate a more efficient downloading process for Windows
|
||||||
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 0 /f
|
updates, upgrades, and applications [1] [2]. Instead of exclusively relying on Microsoft's servers, this feature identifies other
|
||||||
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 1 /f
|
PCs on a user's local network or even across the internet that already possess the desired updates or applications [2]. By breaking
|
||||||
|
the download into smaller segments and fetching each from the fastest and most reliable source, which can include other PCs, the
|
||||||
|
system ensures more efficient downloads [2]. To support this process, Delivery Optimization uses a local cache to temporarily store
|
||||||
|
downloaded files [2].
|
||||||
|
|
||||||
|
While Delivery Optimization is designed for speed and reliability, its operation raises privacy concerns. Specifically, when enabled,
|
||||||
|
it can distribute updates and applications from one user's PC to others [2], sharing users' data such as their IP addresses [3].
|
||||||
|
|
||||||
|
Benefits of disabling Delivery Optimization for privacy:
|
||||||
|
|
||||||
|
- **Minimizing Data Sharing**: By turning off Delivery Optimization, users ensure that updates and apps are neither downloaded from nor sent
|
||||||
|
to other devices [2]. This guarantees that all data remains strictly on the user's device [2] and the user IP is not shared [3].
|
||||||
|
- **Storage Conservation**: Users can save storage space by eliminating the local cache utilized by Delivery Optimization.
|
||||||
|
- **Guaranteed Source Authenticity**: Although Microsoft ensures the authenticity of updates and apps shared via Delivery Optimization [2],
|
||||||
|
disabling the feature guarantees that all updates and apps come directly from Microsoft's servers, eliminating potential intermediaries.
|
||||||
|
- **Bandwidth Conservation**: With the feature off, updates are restricted to direct downloads from Microsoft [1]. This is beneficial
|
||||||
|
for users on metered or capped internet connections, as it allows for more effective bandwidth monitoring [2].
|
||||||
|
- **Enhanced Security**: Devices using Delivery Optimization open port 7680 to accept peer requests [4]. Disabling the feature avoids this,
|
||||||
|
ensuring users are not exposed to unwanted inbound traffic and enhancing security [5].
|
||||||
|
- **VPN Protection**: Although Delivery Optimization attempts to detect VPNs and halts uploads when a VPN connection is detected [4], disabling
|
||||||
|
it removes any risk of unintended data sharing over a VPN.
|
||||||
|
|
||||||
|
Notably, the USA government [5] and Department of Defense (DoD) in the USA [6] recommends disabling this feature.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230914164204/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization "What is Delivery Optimization? - Windows Deployment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230914164355/https://support.microsoft.com/en-us/windows/windows-update-delivery-optimization-and-privacy-bf86a244-8f26-a3c7-a137-a43bfbe688e8 "Windows Update Delivery Optimization and privacy - Microsoft Support"
|
||||||
|
[3]: https://web.archive.org/web/20230914164646/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-monitor "Monitor Delivery Optimization - Windows Deployment | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230905120220/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-faq "Delivery Optimization Frequently Asked Questions - Windows Deployment | Microsoft Learn"
|
||||||
|
[5]: https://web.archive.org/web/20230914171139/https://www.irs.gov/pub/irs-utl/win10.xlsx "Internal Revenue Service Office of Safeguards - Windows 10 | irs.gov"
|
||||||
|
[6]: https://web.archive.org/web/20230914171410/https://www.stigviewer.com/stig/windows_10/2019-01-04/finding/V-65681 "Windows Update must not obtain updates from other PCs on the Internet | stigviewer.com"
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable peering download method for Windows Updates
|
||||||
|
recommend: standard
|
||||||
|
docs: |-
|
||||||
|
This script modifies Delivery Optimization's download method for Windows Updates [1] to disable peering. When this script is run, it sets the
|
||||||
|
download method to `0`, which means "HTTP only, no peering" [1] [2]. As a result, Windows Updates are downloaded solely from the internet and
|
||||||
|
not from other computers on the network (referred to as "peer-to-peer") [3].
|
||||||
|
|
||||||
|
Peer-to-peer is a method where multiple computers share data amongst themselves. For Windows Updates, the default setting is for computers
|
||||||
|
within a network to share updates (called LAN mode, represented by the value `1`) [1] [2].
|
||||||
|
|
||||||
|
Changing the setting to "HTTP only" reduces potential vulnerabilities [3]. When updates are fetched only from official servers, there's
|
||||||
|
less chance of unwanted or malicious data entering the system. This is why the Department of Defense (DoD) in the USA [4] and USA government [3]
|
||||||
|
recommends this setting. They assert that leaving it in its default configuration could expose the system to additional risks [3].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230914171524/https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-deliveryoptimization "DeliveryOptimization Policy CSP - Windows Client Management | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230914171842/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-reference "Delivery Optimization reference - Windows Deployment | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230914171139/https://www.irs.gov/pub/irs-utl/win10.xlsx "Internal Revenue Service Office of Safeguards - Windows 10 | irs.gov"
|
||||||
|
[4]: https://web.archive.org/web/20230914171410/https://www.stigviewer.com/stig/windows_10/2019-01-04/finding/V-65681 "Windows Update must not obtain updates from other PCs on the Internet | stigviewer.com"
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization" /v "DODownloadMode" /t "REG_DWORD" /d 0 /f
|
||||||
|
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization" /v "DODownloadMode" /f 2>nul # Key does not exists since Windows 10 21H2, Windows 11 22H2
|
||||||
|
-
|
||||||
|
name: Disable "Delivery Optimization" service (breaks Microsoft Store downloads)
|
||||||
|
recommend: strict
|
||||||
|
docs: |-
|
||||||
|
Delivery Optimization is a Windows feature that provides the Windows Updates through peer-to-peer sharing [1]. In simple terms, instead of solely
|
||||||
|
relying on Microsoft's servers for updates, your computer can also fetch them from other devices that already possess the necessary files.
|
||||||
|
|
||||||
|
The "Delivery Optimization" service manages these content delivery tasks [2] [3]. It orchestrates the retrieval of updates both from other Windows users [3].
|
||||||
|
In doing so, it connects to various Microsoft service points to collect data, such as policies, content details, device specifications, and information about
|
||||||
|
other Windows users [3]. This data sharing raises privacy concerns.
|
||||||
|
|
||||||
|
This service also logs IP addresses [4] of peers which can be considered personal data. It listens on port 7680 for TCP/UDP traffic [5] that may expose the user
|
||||||
|
to unwanted inbound traffic and enhancing security [6].
|
||||||
|
|
||||||
|
By default, the "Delivery Optimization" service is set to start automatically when Windows boots up [2]. This script alters that behavior, ensuring
|
||||||
|
it doesn't run unless explicitly started by the user.
|
||||||
|
|
||||||
|
Taking control of this service prevents Microsoft from activating peer-to-peer sharing, enhancing user privacy. It ensures your device doesn't share update data
|
||||||
|
or fetch it from arbitrary peers.
|
||||||
|
|
||||||
|
> **Caution**: Disabling this service affects the functionality of Windows Store. It plays a role not just in Windows Updates but also in Microsoft Store app
|
||||||
|
downloads, especially since Windows 11 [7]. There have been reported issues with some app downloads on Windows 10 [8].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230914164204/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization "What is Delivery Optimization? - Windows Deployment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230905120815/https://learn.microsoft.com/en-us/windows/iot/iot-enterprise/optimize/services#delivery-optimization "Guidance on disabling system services on Windows IoT Enterprise | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230914172129/https://learn.microsoft.com/en-us/windows/deployment/do/delivery-optimization-workflow "Delivery Optimization client-service communication explained - Windows Deployment | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230914164646/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-monitor "Monitor Delivery Optimization - Windows Deployment | Microsoft Learn"
|
||||||
|
[5]: https://web.archive.org/web/20230914172319/https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-deployment "Deploying a privileged access solution | Microsoft Learn"
|
||||||
|
[6]: https://web.archive.org/web/20230914171139/https://www.irs.gov/pub/irs-utl/win10.xlsx "Internal Revenue Service Office of Safeguards - Windows 10 | irs.gov"
|
||||||
|
[7]: https://web.archive.org/web/20230914164355/https://support.microsoft.com/en-us/windows/windows-update-delivery-optimization-and-privacy-bf86a244-8f26-a3c7-a137-a43bfbe688e8 "Windows Update Delivery Optimization and privacy - Microsoft Support"
|
||||||
|
[8]: https://github.com/undergroundwires/privacy.sexy/issues/173 "[BUG] Error 0x80004002 on Microsoft Store when attempting to download an app · Issue #173 · undergroundwires/privacy.sexy"
|
||||||
|
call:
|
||||||
|
function: DisableServiceInRegistry
|
||||||
|
# Using registry way because because other options such as "sc config" or
|
||||||
|
# "Set-Service" returns "Access is denied" since Windows 10 1809.
|
||||||
|
parameters:
|
||||||
|
serviceName: DoSvc # Check: (Get-Service -Name 'DoSvc').StartType
|
||||||
|
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
|
||||||
-
|
-
|
||||||
name: Disable cloud speech recognition
|
name: Disable cloud speech recognition
|
||||||
recommend: standard
|
recommend: standard
|
||||||
@@ -1776,12 +1911,26 @@ actions:
|
|||||||
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "0" /f
|
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "0" /f
|
||||||
-
|
-
|
||||||
name: Disable Windows Spotlight (random wallpaper on lock screen)
|
name: Disable Windows Spotlight (random wallpaper on lock screen)
|
||||||
recommend: standard
|
recommend: strict
|
||||||
docs:
|
docs: |-
|
||||||
- https://docs.microsoft.com/en-us/windows/configuration/windows-spotlight
|
The script disables the Windows Spotlight feature. Windows Spotlight is a feature in Windows 10 and Windows 11 [1] that automatically downloads
|
||||||
- https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#25-windows-spotlight
|
and displays random wallpapers on the lock screen [1] [2]. These images are sourced from the internet [1] [2] [3]. At times, it might also promote
|
||||||
|
various Microsoft products, services [1] [2], or even third-party apps and content [4].
|
||||||
|
|
||||||
|
When the lock screen fetches images from the internet, there's a silent data exchange happening. This can inadvertently reveal details about the
|
||||||
|
user's device or their preferences.
|
||||||
|
|
||||||
|
To mitigate this potential privacy risk, the script makes a change to a key (`DisableWindowsSpotlightFeatures`) in the Windows operating system [3].
|
||||||
|
Originally, Windows Spotlight is turned on unless the user decides otherwise [2].
|
||||||
|
By applying this script, users can be sure their lock screen remains private and doesn't retrieve wallpapers from the internet, eliminating potential
|
||||||
|
data leaks.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230911110727/https://support.microsoft.com/en-us/windows/personalize-your-lock-screen-81dab9b0-35cf-887c-84a0-6de8ef72bea0 "Personalize your lock screen - Microsoft Support"
|
||||||
|
[2]: https://web.archive.org/web/20230911110748/https://learn.microsoft.com/en-us/windows/configuration/windows-spotlight "Configure Windows Spotlight on the lock screen - Configure Windows | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230911110911/https://learn.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#25-windows-spotlight "Manage connections from Windows 10 and Windows 11 Server/Enterprise editions operating system components to Microsoft services - Windows Privacy | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230911110921/https://download.microsoft.com/download/8/F/B/8FBD2E85-8852-45EC-8465-92756EBD9365/Windows10andWindowsServer2016PolicySettings.xlsx "Group Policy Settings Reference - Microsoft"
|
||||||
code: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "1" /f
|
code: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "1" /f
|
||||||
revertCode: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "0" /f
|
revertCode: reg delete "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /f 2>nul # Key does not exists since Windows 10 21H2, Windows 11 22H2
|
||||||
-
|
-
|
||||||
name: Disable Microsoft consumer experiences
|
name: Disable Microsoft consumer experiences
|
||||||
recommend: standard
|
recommend: standard
|
||||||
@@ -2272,8 +2421,7 @@ actions:
|
|||||||
function: SetVsCodeSetting
|
function: SetVsCodeSetting
|
||||||
parameters:
|
parameters:
|
||||||
setting: update.mode
|
setting: update.mode
|
||||||
powerShellValue: >-
|
powerShellValue: manual
|
||||||
'manual'
|
|
||||||
-
|
-
|
||||||
name: Show Release Notes from Microsoft online service after an update
|
name: Show Release Notes from Microsoft online service after an update
|
||||||
call:
|
call:
|
||||||
@@ -2415,21 +2563,25 @@ actions:
|
|||||||
category: Chromium Edge settings
|
category: Chromium Edge settings
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable Edge usage and crash-related data reporting (shows "Your browser is managed") # Obselete since Microsoft Edge version 89
|
name: Disable Edge diagnostic data sending (shows "Your browser is managed")
|
||||||
recommend: standard
|
|
||||||
docs:
|
|
||||||
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled
|
|
||||||
- https://docs.microsoft.com/en-us/DeployEdge/microsoft-edge-policies#metricsreportingenabled
|
|
||||||
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
|
|
||||||
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /f
|
|
||||||
-
|
|
||||||
name: Disable sending site information (shows "Your browser is managed") # Obselete since Microsoft Edge version 89
|
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs:
|
docs:
|
||||||
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices
|
- http://archive.today/2023.08.26-152941/https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::DiagnosticData
|
||||||
- https://docs.microsoft.com/en-us/DeployEdge/microsoft-edge-policies#sendsiteinfotoimproveservices
|
- https://learn.microsoft.com/DeployEdge/microsoft-edge-policies#diagnosticdata
|
||||||
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /t REG_DWORD /d 0 /f
|
- http://archive.today/2023.08.26-152952/https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled
|
||||||
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /f
|
- https://learn.microsoft.com/en-gb/DeployEdge/microsoft-edge-policies#metricsreportingenabled
|
||||||
|
- http://archive.today/2023.08.26-153019/https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices
|
||||||
|
- https://learn.microsoft.com/DeployEdge/microsoft-edge-policies#sendsiteinfotoimproveservices
|
||||||
|
code: |-
|
||||||
|
:: Disabling metrics and site info sending for Edge v88 ≥
|
||||||
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
|
||||||
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /t REG_DWORD /d 0 /f
|
||||||
|
:: Disabling diagnostic data (replacing metrics and site info sending since Edge v89 ≤)
|
||||||
|
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "DiagnosticData" /t REG_DWORD /d 0 /f
|
||||||
|
revertCode: |-
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /f 2>nul
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /f 2>nul
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "DiagnosticData" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Disable Automatic Installation of Microsoft Edge Chromium
|
name: Disable Automatic Installation of Microsoft Edge Chromium
|
||||||
docs:
|
docs:
|
||||||
@@ -2456,7 +2608,7 @@ actions:
|
|||||||
recommend: standard
|
recommend: standard
|
||||||
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.EdgeUI::DisableRecentApps
|
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.EdgeUI::DisableRecentApps
|
||||||
code: reg add "HKCU\Software\Policies\Microsoft\Windows\EdgeUI" /v "DisableRecentApps" /t REG_DWORD /d 1 /f
|
code: reg add "HKCU\Software\Policies\Microsoft\Windows\EdgeUI" /v "DisableRecentApps" /t REG_DWORD /d 1 /f
|
||||||
revertCode: reg add "HKCU\Software\Policies\Microsoft\Windows\EdgeUI" /v "DisableRecentApps" /t REG_DWORD /d 0/f
|
revertCode: reg add "HKCU\Software\Policies\Microsoft\Windows\EdgeUI" /v "DisableRecentApps" /t REG_DWORD /d 0 /f
|
||||||
-
|
-
|
||||||
name: Turn off backtracking
|
name: Turn off backtracking
|
||||||
recommend: standard
|
recommend: standard
|
||||||
@@ -3871,7 +4023,7 @@ actions:
|
|||||||
code: reg add "HKLM\Software\Policies\Microsoft\Windows Defender\Scan" /v "DisableRestorePoint" /t REG_DWORD /d "1" /f
|
code: reg add "HKLM\Software\Policies\Microsoft\Windows Defender\Scan" /v "DisableRestorePoint" /t REG_DWORD /d "1" /f
|
||||||
revertCode: reg delete "HKLM\Software\Policies\Microsoft\Windows Defender\Scan" /v "DisableRestorePoint" /f 2>nul
|
revertCode: reg delete "HKLM\Software\Policies\Microsoft\Windows Defender\Scan" /v "DisableRestorePoint" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Set minumum time for keeping files in scan history folder
|
name: Set minimum time for keeping files in scan history folder
|
||||||
docs:
|
docs:
|
||||||
- https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefender::Scan_PurgeItemsAfterDelay
|
- https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefender::Scan_PurgeItemsAfterDelay
|
||||||
# Managing with MpPreference module:
|
# Managing with MpPreference module:
|
||||||
@@ -4891,29 +5043,233 @@ actions:
|
|||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /t REG_DWORD /d "1" /f
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /t REG_DWORD /d "1" /f
|
||||||
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /f 2>nul
|
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Disable automatic updates
|
category: Disable automatic updates
|
||||||
docs:
|
docs: |-
|
||||||
- https://docs.microsoft.com/fr-fr/security-updates/windowsupdateservices/18127152
|
Disabling automatic updates is often considered counterintuitive when it comes to securing your system. However, there are substantial arguments
|
||||||
- http://batcmd.com/windows/10/services/usosvc/
|
to consider this option if you're privacy-centric:
|
||||||
call:
|
|
||||||
|
1. **Patching and Pre-Approval**: Manual control over update deployment allows for pre-emptive approval of patches. This strategy is useful
|
||||||
|
in environments requiring the highest level of security. For instance, military agencies frequently employ air-gapped systems that mandate
|
||||||
|
careful review of each update to mitigate risks such as potential backdoors or data leaks. Similarly, financial institutions often
|
||||||
|
resort to staged rollouts of updates, subjecting them to an in-depth analysis of their implications on security and privacy before broad
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
2. **Telemetry and Data Transmission**: Automatic updates often come embedded with telemetry data collection mechanisms. Disabling these
|
||||||
|
updates facilitates granular control over the data transmitted back to Microsoft servers. Thus, the decision to disable automatic updates
|
||||||
|
allows you to control the timing and nature of information relayed to these servers.
|
||||||
|
|
||||||
|
3. **Peer-to-Peer Data Exposure**: Windows employs a Peer-to-Peer (P2P) approach to facilitate update distribution, which can
|
||||||
|
reveal your IP address and some system details to peer systems [1].
|
||||||
|
|
||||||
|
4. **Configurational integrity**: Updates have the capacity to change pre-configured settings without explicit user consent. This could
|
||||||
|
result in unintended alteration of your privacy settings, leaving you exposed until you realize the change.
|
||||||
|
|
||||||
|
**Security implications**: While controlling updates enhances your privacy, it can leave your system vulnerable to unpatched exploits.
|
||||||
|
Ensure that you manually review and apply updates on a regular basis. You're essentially trading off some security for a heightened level of
|
||||||
|
privacy.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230905120220/https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-faq "Delivery Optimization Frequently Asked Questions - Windows Deployment | Microsoft Learn"
|
||||||
|
children:
|
||||||
-
|
-
|
||||||
function: RunInlineCode
|
name: Disable Automatic Updates (AU) feature
|
||||||
parameters:
|
docs: |-
|
||||||
code: |-
|
This script deactivates the Automatic Updates feature in Windows. By disabling Automatic Updates,
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "0" /f
|
you gain control over when your system is updated, which may be preferable in specific
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "2" /f
|
privacy-sensitive environments.
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /t "REG_DWORD" /d "0" /f
|
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /t "REG_DWORD" /d "3" /f
|
The script changes a specific setting in your computer's registry, with a key called `NoAutoUpdate`, which has
|
||||||
revertCode: |-
|
two possible states [1] [2]:
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "1" /f
|
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "3" /f
|
- `0`: Automatic Updates are enabled.
|
||||||
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /f 2>nul
|
- `1`: Automatic Updates are disabled.
|
||||||
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /f 2>nul
|
|
||||||
|
By default, Windows comes with Automatic Updates enabled, meaning the `NoAutoUpdate` is set to `0` [3].
|
||||||
|
|
||||||
|
Running this script will set `NoAutoUpdate` to `1`, turning off Automatic Updates [1] [2] [3].
|
||||||
|
In doing so, you prevent your computer from automatically receiving updates, which is a feature
|
||||||
|
that could be considered intrusive or unwanted in some privacy-conscious settings.
|
||||||
|
|
||||||
|
It configure your computer to not automatically download and install updates without your explicit permission.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230807165936/https://learn.microsoft.com/de-de/security-updates/windowsupdateservices/18127499 "Configure Automatic Updates in a Non–Active Directory Environment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20221001051250/https://support.microsoft.com/en-us/topic/incorrect-automatic-updates-notification-is-received-even-though-au-options-are-disabled-in-windows-8-1-and-windows-server-2012-r2-18b4b73a-3910-9408-809c-7eaad0e1fbc7 "Incorrect Automatic Updates notification is received even though AU options are disabled in Windows 8.1 and Windows Server 2012 R2 - Microsoft Support"
|
||||||
|
[3]: https://web.archive.org/web/20230711172555/https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry "Manage additional Windows Update settings - Windows Deployment | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: RunInlineCode
|
||||||
|
parameters:
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "1" /f
|
||||||
|
# Default value is `0` since Windows 10 21H2 and Windows 11 21H2
|
||||||
|
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "0" /f
|
||||||
-
|
-
|
||||||
function: DisableService
|
name: Disable installing Windows updates without user approval
|
||||||
parameters:
|
docs: |-
|
||||||
serviceName: UsoSvc # Check: (Get-Service -Name 'UsoSvc').StartType
|
This script changes how your Windows computer handles automatic updates by modifying the `AUOptions` registry key.
|
||||||
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
|
After running this script, your computer will notify you before downloading any updates [1] [2] [3].
|
||||||
|
|
||||||
|
In the default setup, your Windows system is configured to download and install updates automatically without notifying you [4].
|
||||||
|
This means that new updates could be installed on your system without your explicit approval.
|
||||||
|
|
||||||
|
By forcing Windows to notify you before downloading updates, this script hands back control over your system to you.
|
||||||
|
This feature enhances your privacy and minimizes risks because you get to manually review and approve each update before it's installed.
|
||||||
|
|
||||||
|
To explain the technical aspect, the `AUOptions` registry key is a setting stored under
|
||||||
|
`HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU` in your computer's registry [1] [3].
|
||||||
|
A value of `2` for `AUOptions` means that you will be notified before any updates are downloaded and installed [1] [2].
|
||||||
|
On older versions of Windows, setting this key to `1` would prevent the system from even checking for updates [5].
|
||||||
|
However, starting from Windows 10, the key `1` has a different meaning [2][3].
|
||||||
|
|
||||||
|
Running this script doesn't disable updates; it just ensures that you are informed and have the final say on
|
||||||
|
whether to download them or not.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230807165936/https://learn.microsoft.com/de-de/security-updates/windowsupdateservices/18127499 "Configure Automatic Updates in a Non–Active Directory Environment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230711172555/https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry "Manage additional Windows Update settings - Windows Deployment | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230815051303/https://learn.microsoft.com/en-us/windows/deployment/update/waas-restart#registry-keys-used-to-manage-restart "Manage device restarts after updates - Windows Deployment | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230826081345/https://learn.microsoft.com/en-US/troubleshoot/windows-client/deployment/update-windows-update-agent "Update Windows Update Agent to latest version - Windows Client | Microsoft Learn"
|
||||||
|
[5]: https://web.archive.org/web/20221001051250/https://support.microsoft.com/en-us/topic/incorrect-automatic-updates-notification-is-received-even-though-au-options-are-disabled-in-windows-8-1-and-windows-server-2012-r2-18b4b73a-3910-9408-809c-7eaad0e1fbc7 "Incorrect Automatic Updates notification is received even though AU options are disabled in Windows 8.1 and Windows Server 2012 R2 - Microsoft Support"
|
||||||
|
call:
|
||||||
|
function: RunInlineCode
|
||||||
|
parameters:
|
||||||
|
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "2" /f
|
||||||
|
# Default value is `4` since Windows 10 21H2 and Windows 11 21H2
|
||||||
|
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "4" /f
|
||||||
|
-
|
||||||
|
name: Disable automatic daily installation of Windows updates
|
||||||
|
docs: |-
|
||||||
|
This script stops Windows from automatically installing updates every day. By doing so, you gain control over when update
|
||||||
|
happen on your computer [1] [2].
|
||||||
|
|
||||||
|
By default, Windows is set to automatically update every day [2]. Having control over the update timing allows you to review
|
||||||
|
what is being changed, thereby protecting your privacy and enhancing your system's security.
|
||||||
|
|
||||||
|
Technically, what the script does is remove a specific setting in the computer's system registry, the `ScheduledInstallDay` key
|
||||||
|
from `HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU` [1] [2].
|
||||||
|
|
||||||
|
Disabling the scheduled install day ensures that updates won't be forcibly applied on a specific day of the week.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230711172555/https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry "Manage additional Windows Update settings - Windows Deployment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230708165017/https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-update#scheduledinstallday "Update Policy CSP - Windows Client Management | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: RunInlineCode
|
||||||
|
parameters:
|
||||||
|
code: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /f 2>nul
|
||||||
|
revertCode: >-
|
||||||
|
:: This key does not exist by default since Windows 10 21H2 and Windows 11 21H2
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /f 2>nul
|
||||||
|
-
|
||||||
|
name: Disable scheduled automatic updates
|
||||||
|
docs: |-
|
||||||
|
This script turns off the automatic installation of Windows updates that are set to occur at a specific time.
|
||||||
|
By doing this, you take back control over when your computer updates itself [1] [2] [3].
|
||||||
|
The default behavior is to install updates at 3 AM [3].
|
||||||
|
|
||||||
|
Windows updates can be important for system security, but automatic installation could occur at inconvenient times and may even
|
||||||
|
restart your computer without prior warning. This could interrupt your tasks and may send data about your system to external servers.
|
||||||
|
By disabling the automatic scheduled installation time, you can manually control when updates are installed [3], ensuring that you're
|
||||||
|
aware of any changes to your system.
|
||||||
|
|
||||||
|
The script works by removing a specific registry key called `ScheduledInstallTime` under
|
||||||
|
`HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU` [2] [3]. This is the system setting that controls the scheduled update time.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230813094618/https://learn.microsoft.com/fr-fr/security-updates/windowsupdateservices/18127152 "Configure Automatic Updates in a Non–Active Directory Environment | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230711172555/https://learn.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry "Manage additional Windows Update settings - Windows Deployment | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230708165017/https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-update#scheduledinstalltime "Update Policy CSP - Windows Client Management | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: RunInlineCode
|
||||||
|
parameters:
|
||||||
|
code: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /f 2>nul
|
||||||
|
revertCode: >-
|
||||||
|
:: This key does not exist by default since Windows 10 21H2 and Windows 11 21H2
|
||||||
|
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /f 2>nul
|
||||||
|
-
|
||||||
|
category: Disable Windows update services
|
||||||
|
docs: |-
|
||||||
|
The scripts in this category offer users the ability to control Windows services related to system updates.
|
||||||
|
These services manage how and when your system receives updates from Microsoft. By limiting or disabling these services,
|
||||||
|
users can decide when to update their system, reducing unexpected changes. Moreover, a system with fewer running
|
||||||
|
services uses fewer resources, which can improve overall performance.
|
||||||
|
|
||||||
|
Disabling these update services is also a privacy measure. Some updates can change privacy settings or add features that
|
||||||
|
collect user data. By controlling update services, users can review and approve any changes before they take effect.
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable "Windows Update" (`wuauserv`) service
|
||||||
|
docs: |-
|
||||||
|
This script turns off the Windows Update service, which is technically known as Windows Update Agent [1] [2].
|
||||||
|
By disabling this service, the automatic detection, download, and installation of updates for both Windows and other
|
||||||
|
installed programs are halted [3] [4].
|
||||||
|
|
||||||
|
Update can often come bundled with changes that could affect your privacy settings or introduce features that collect
|
||||||
|
more of your data. Taking control of when and how updates are applied provides you with the opportunity to review any changes
|
||||||
|
before they take effect.
|
||||||
|
|
||||||
|
By default, the service is enabled and set to start up manually [5].
|
||||||
|
|
||||||
|
If you disable this service, you won't be able to use the Windows Update feature for automatic updates [5]. Additionally,
|
||||||
|
other software on your c omputer won't be able to access the functionalities provided by the Windows Update Agent,
|
||||||
|
commonly known as WUA API [5].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230902020255/https://learn.microsoft.com/en-us/troubleshoot/windows-client/deployment/additional-resources-for-windows-update "Additional resources for Windows Update - Windows Client | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230711221240/https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/update-management/troubleshoot-software-update-scan-failures "Troubleshoot software update scan failures - Configuration Manager | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230905120348/https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/windows-devices-fail-boot-after-installing-kb4041676-kb4041691 "Windows devices may fail to boot after installing October 10 version of KB 4041676 or 4041691 that contained a publishing issue - Windows Client | Microsoft Learn"
|
||||||
|
[4]: https://web.archive.org/web/20230905120345/https://learn.microsoft.com/en-us/windows-server/administration/server-core/server-core-servicing "Patching Server Core | Microsoft Learn"
|
||||||
|
[5]: https://web.archive.org/web/20230905120445/https://learn.microsoft.com/en-us/windows/deployment/update/prepare-deploy-windows "Security guidelines for system services in Windows Server 2016 | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: DisableService
|
||||||
|
parameters:
|
||||||
|
serviceName: wuauserv # Check: (Get-Service -Name 'wuauserv').StartType
|
||||||
|
defaultStartupMode: Manual # Allowed values: Automatic | Manual
|
||||||
|
-
|
||||||
|
name: Disable "Update Orchestrator Service" (`UsoSvc`)
|
||||||
|
docs: |-
|
||||||
|
This script disables the Update Orchestrator Service, also known as "Update Orchestrator Service for Windows Update" [1].
|
||||||
|
This service is in charge of managing the download and installation of Windows updates [1] [2].
|
||||||
|
|
||||||
|
By default, the service is enabled and set to start up manually [1].
|
||||||
|
|
||||||
|
While updates can be crucial for the security of your system, this service can sometimes install them without your approval.
|
||||||
|
This lack of control can pose risks to your privacy, as data might be sent from your system without your knowledge.
|
||||||
|
|
||||||
|
Windows updates relies on this service [1] [3].
|
||||||
|
If stopped, your devices will not be able to download and install latest updates [1].
|
||||||
|
|
||||||
|
Turning off this service can affect the update process and might cause issues like freezing during update scanning [3].
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230905120757/https://learn.microsoft.com/en-us/windows-server/security/windows-services/security-guidelines-for-disabling-system-services-in-windows-server "Security guidelines for system services in Windows Server 2016 | Microsoft Learn"
|
||||||
|
[2]: https://web.archive.org/web/20230905120348/https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/windows-devices-fail-boot-after-installing-kb4041676-kb4041691 "Windows devices may fail to boot after installing October 10 version of KB 4041676 or 4041691 that contained a publishing issue - Windows Client | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230905120445/https://learn.microsoft.com/en-us/windows/deployment/update/prepare-deploy-windows "Security guidelines for system services in Windows Server 2016 | Microsoft Learn"
|
||||||
|
call:
|
||||||
|
function: DisableService
|
||||||
|
parameters:
|
||||||
|
serviceName: UsoSvc # Check: (Get-Service -Name 'UsoSvc').StartType
|
||||||
|
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
|
||||||
|
-
|
||||||
|
name: Disable "Windows Update Medic Service" (`WaaSMedicSvc`)
|
||||||
|
docs: |-
|
||||||
|
This script disables the Windows Update Medic Service. This service runs quietly in the background [1],
|
||||||
|
making sure that parts related to Windows updates are working as they should [1] [2].
|
||||||
|
|
||||||
|
By default, the service is enabled and its startup setting is set to manual [3].
|
||||||
|
|
||||||
|
This service can undo any adjustments you've made to your Windows Update settings without your consent.
|
||||||
|
For example, it can re-enable automatic Windows updates [4].
|
||||||
|
That can interfere if you've tailored these settings for better privacy or security.
|
||||||
|
|
||||||
|
When you disable this service using our script, you're taking back control. You get to choose how your system
|
||||||
|
handles updates and data transfers, ensuring that your privacy settings stay as you intended. This is a reliable
|
||||||
|
way to strengthen both your privacy and your control over your computer.
|
||||||
|
|
||||||
|
[1]: https://web.archive.org/web/20230905120805/https://support.microsoft.com/en-us/topic/kb5005322-some-devices-cannot-install-new-updates-after-installing-kb5003214-may-25-2021-and-kb5003690-june-21-2021-66edf7cf-5d3c-401f-bd32-49865343144f "KB5005322—Some devices cannot install new updates after installing KB5003214 (May 25, 2021) and KB5003690 (June 21, 2021) - Microsoft Support"
|
||||||
|
[2]: https://web.archive.org/web/20230905120445/https://learn.microsoft.com/en-us/windows/deployment/update/prepare-deploy-windows "Security guidelines for system services in Windows Server 2016 | Microsoft Learn"
|
||||||
|
[3]: https://web.archive.org/web/20230905120815/https://learn.microsoft.com/en-us/windows/iot/iot-enterprise/optimize/services "Guidance on disabling system services on Windows IoT Enterprise | Microsoft Learn"
|
||||||
|
[4]: https://github.com/undergroundwires/privacy.sexy/issues/252
|
||||||
|
call:
|
||||||
|
function: DisableServiceInRegistry
|
||||||
|
# Since Windows 10 21H2 and Windows 11 21H2:
|
||||||
|
# - Using `sc config` resulsts in "Access in denied", so registry should be used to disable the service.
|
||||||
|
# - Default startup mode is Manual
|
||||||
|
parameters:
|
||||||
|
serviceName: WaaSMedicSvc # Check: (Get-Service -Name 'WaaSMedicSvc').StartType
|
||||||
|
defaultStartupMode: Manual # Allowed values: Automatic | Manual
|
||||||
-
|
-
|
||||||
category: Configure handling of downloaded files
|
category: Configure handling of downloaded files
|
||||||
docs: |-
|
docs: |-
|
||||||
@@ -5169,25 +5525,6 @@ actions:
|
|||||||
-
|
-
|
||||||
category: Disable OS services
|
category: Disable OS services
|
||||||
children:
|
children:
|
||||||
-
|
|
||||||
name: Delivery Optimization (P2P Windows Updates)
|
|
||||||
recommend: standard
|
|
||||||
docs:
|
|
||||||
# Delivery Optimization is a cloud-managed solution to offer Windows updates through
|
|
||||||
# other users' network (peer-to-peer).
|
|
||||||
- https://docs.microsoft.com/en-us/windows/deployment/update/waas-delivery-optimization
|
|
||||||
# Delivery Optimization service performs content delivery optimization tasks.
|
|
||||||
- http://batcmd.com/windows/10/services/dosvc/
|
|
||||||
# Connects to various Microsoft service endpoints to get metadata, policies, content, device information
|
|
||||||
# and information of other peers (Windows users).
|
|
||||||
- https://docs.microsoft.com/en-us/windows/deployment/update/delivery-optimization-workflow
|
|
||||||
call:
|
|
||||||
function: DisableServiceInRegistry
|
|
||||||
# Using registry way because because other options such as "sc config" or
|
|
||||||
# "Set-Service" returns "Access is denied" since Windows 10 1809.
|
|
||||||
parameters:
|
|
||||||
serviceName: DoSvc # Check: (Get-Service -Name 'DoSvc').StartType
|
|
||||||
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
|
|
||||||
-
|
-
|
||||||
name: Microsoft Account Sign-in Assistant (breaks Microsoft Store and Microsoft Account sign-in)
|
name: Microsoft Account Sign-in Assistant (breaks Microsoft Store and Microsoft Account sign-in)
|
||||||
recommend: strict
|
recommend: strict
|
||||||
@@ -6613,16 +6950,75 @@ actions:
|
|||||||
code: reg delete "HKCU\Environment" /v "OneDrive" /f 2>nul
|
code: reg delete "HKCU\Environment" /v "OneDrive" /f 2>nul
|
||||||
-
|
-
|
||||||
name: Uninstall Edge (chromium-based)
|
name: Uninstall Edge (chromium-based)
|
||||||
|
docs: |-
|
||||||
|
This script automates the uninstallation of Microsoft Edge (also known as "Chromium Edge" or "New Edge" [1]), the web browser that comes
|
||||||
|
pre-installed with many versions of Windows.
|
||||||
|
|
||||||
|
Microsoft Edge collects various types of data, some of which pertain to your browsing habits, such as the websites you visit, your search
|
||||||
|
queries, and the data you enter into forms [2]. Additionally, it tracks usage metrics and diagnostic data about your device data and
|
||||||
|
how the browser is functioning [2]. These pieces of information could be used for targeted advertising or profiling. Removing Microsoft
|
||||||
|
Edge ensures that it is not silently accumulating this data in the background, thereby improving your overall privacy.
|
||||||
|
|
||||||
|
By default, Microsoft Edge doesn't allow easy uninstallation and has officially declared Microsoft Edge as uninstallable on Windows [3].
|
||||||
|
|
||||||
|
This scripts uses two steps to achieve this:
|
||||||
|
|
||||||
|
1. **Enable Uninstallation**: The script modifies a specific registry key to allow the uninstallation of Microsoft Edge. This step is crucial
|
||||||
|
because, starting from version 116 of Edge, you cannot uninstall it unless this registry key is set.
|
||||||
|
2. **Run Uninstaller**: The script then finds the Microsoft Edge installer (`setup.exe`) for every Microsoft Edge installation (it is possible
|
||||||
|
to have multiple versions) and executes it to perform a system-level uninstall.
|
||||||
|
|
||||||
|
There's no official documentation for the Edge installer or registry keys codes, which this script relies on. However, these have been verified
|
||||||
|
through testing and community support to work as expected.
|
||||||
|
|
||||||
|
[1]: https://en.wikipedia.org/w/index.php?title=Microsoft_Edge&oldid=1174053020#New_Edge_(2019%E2%80%93present) "Microsoft Edge - Wikipedia"
|
||||||
|
[2]: https://web.archive.org/web/20230907002709/https://support.microsoft.com/en-us/microsoft-edge/learn-more-about-diagnostic-data-collection-in-microsoft-edge-7fcee15b-39f7-ba02-bc59-9eef622c1a9f "Learn more about diagnostic data collection in Microsoft Edge - Microsoft Support"
|
||||||
|
[3]: https://web.archive.org/web/20230907002011/https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d "Why can't I uninstall Microsoft Edge? - Microsoft Support"
|
||||||
call:
|
call:
|
||||||
function: RunPowerShell
|
-
|
||||||
parameters:
|
function: RunInlineCode
|
||||||
code: |-
|
parameters:
|
||||||
$installer = (Get-ChildItem "$env:ProgramFiles*\Microsoft\Edge\Application\*\Installer\setup.exe")
|
code: reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdateDev" /v "AllowUninstall" /t REG_DWORD /d "1" /f
|
||||||
if (!$installer) {
|
revertCode: reg delete "HKLM\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdateDev" /v "AllowUninstall" /f 2>nul # It does not exists since Windows 10 21H2 and Windows 11 21H2
|
||||||
Write-Host 'Could not find the installer'
|
-
|
||||||
} else {
|
function: RunPowerShell
|
||||||
& $installer.FullName -Uninstall -System-Level -Verbose-Logging -Force-Uninstall
|
parameters:
|
||||||
}
|
code: |-
|
||||||
|
$installer = (Get-ChildItem "$($env:ProgramFiles)*\Microsoft\Edge\Application\*\Installer\setup.exe")
|
||||||
|
if (!$installer) {
|
||||||
|
Write-Host 'Installer not found. Microsoft Edge may already be uninstalled.'
|
||||||
|
} else {
|
||||||
|
$installer | ForEach-Object {
|
||||||
|
$uninstallerPath = $_.FullName
|
||||||
|
$installerArguments = @("--uninstall", "--system-level", "--verbose-logging", "--force-uninstall")
|
||||||
|
Write-Output "Uninstalling through uninstaller: $uninstallerPath"
|
||||||
|
$process = Start-Process -FilePath "$uninstallerPath" -ArgumentList $installerArguments -Wait -PassThru
|
||||||
|
if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 19) {
|
||||||
|
Write-Host "Successfully uninstalled Edge."
|
||||||
|
} else {
|
||||||
|
Write-Error "Failed to uninstall, uninstaller failed with exit code $($process.ExitCode)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
revertCode: |-
|
||||||
|
$edgeExePath = Get-ChildItem -Path "$($env:ProgramFiles)*\Microsoft\Edge\Application" -Filter 'msedge.exe' -Recurse
|
||||||
|
if ($edgeExePath) {
|
||||||
|
Write-Host 'Microsoft Edge is already installed. Skipping reinstallation.'
|
||||||
|
Exit 0
|
||||||
|
}
|
||||||
|
Write-Host 'Downloading Microsoft Edge...'
|
||||||
|
$edgeInstallerUrl = 'https://c2rsetup.officeapps.live.com/c2r/downloadEdge.aspx?platform=Default&Channel=Stable&language=en'
|
||||||
|
$downloadPath = "$($env:TEMP)\MicrosoftEdgeSetup.exe"
|
||||||
|
Invoke-WebRequest -Uri "$edgeInstallerUrl" -OutFile "$downloadPath"
|
||||||
|
$installerArguments = @('/install', '/silent')
|
||||||
|
Write-Host 'Installing Microsoft Edge...'
|
||||||
|
$process = Start-Process -FilePath "$downloadPath" -ArgumentList "$installerArguments" -Wait -PassThru
|
||||||
|
Remove-Item -Path $downloadPath -Force
|
||||||
|
if ($process.ExitCode -eq 0) {
|
||||||
|
Write-Host 'Successfully reinstalled Microsoft Edge.'
|
||||||
|
} else {
|
||||||
|
Write-Error "Failed to reinstall Microsoft Edge. Installer failed with exit code $($process.ExitCode)."
|
||||||
|
}
|
||||||
-
|
-
|
||||||
category: Disable built-in Windows features
|
category: Disable built-in Windows features
|
||||||
children:
|
children:
|
||||||
@@ -7473,6 +7869,7 @@ functions:
|
|||||||
parameters:
|
parameters:
|
||||||
- name: code
|
- name: code
|
||||||
- name: revertCode
|
- name: revertCode
|
||||||
|
optional: true
|
||||||
call:
|
call:
|
||||||
function: RunPowerShell
|
function: RunPowerShell
|
||||||
parameters:
|
parameters:
|
||||||
@@ -7540,7 +7937,8 @@ functions:
|
|||||||
Remove-Item $streamOutFile, $batchFile
|
Remove-Item $streamOutFile, $batchFile
|
||||||
}
|
}
|
||||||
revertCode: |- # Duplicated until custom pipes are implemented
|
revertCode: |- # Duplicated until custom pipes are implemented
|
||||||
$command = '{{ $revertCode }}'
|
{{ with $revertCode }}
|
||||||
|
$command = '{{ . }}'
|
||||||
$trustedInstallerSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464')
|
$trustedInstallerSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464')
|
||||||
$trustedInstallerName = $trustedInstallerSid.Translate([System.Security.Principal.NTAccount])
|
$trustedInstallerName = $trustedInstallerSid.Translate([System.Security.Principal.NTAccount])
|
||||||
$streamOutFile = New-TemporaryFile
|
$streamOutFile = New-TemporaryFile
|
||||||
@@ -7583,6 +7981,7 @@ functions:
|
|||||||
} finally {
|
} finally {
|
||||||
Remove-Item $streamOutFile, $batchFile
|
Remove-Item $streamOutFile, $batchFile
|
||||||
}
|
}
|
||||||
|
{{ end }}
|
||||||
-
|
-
|
||||||
name: DisableServiceInRegistry
|
name: DisableServiceInRegistry
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
@@ -1,32 +1,37 @@
|
|||||||
import { Environment } from '@/infrastructure/Environment/Environment';
|
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
||||||
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 environment = Environment.CurrentEnvironment,
|
private readonly system = getWindowInjectedSystemOperations(),
|
||||||
|
private readonly environment = RuntimeEnvironment.CurrentEnvironment,
|
||||||
) {
|
) {
|
||||||
if (!environment.system) {
|
if (!system) {
|
||||||
throw new Error('missing system operations');
|
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 { system } = this.environment;
|
const { os } = this.environment;
|
||||||
const dir = system.location.combinePaths(
|
const dir = this.system.location.combinePaths(
|
||||||
system.operatingSystem.getTempDirectory(),
|
this.system.operatingSystem.getTempDirectory(),
|
||||||
folderName,
|
folderName,
|
||||||
);
|
);
|
||||||
await system.fileSystem.createDirectory(dir, true);
|
await this.system.fileSystem.createDirectory(dir, true);
|
||||||
const filePath = system.location.combinePaths(dir, `run.${fileExtension}`);
|
const filePath = this.system.location.combinePaths(dir, `run.${fileExtension}`);
|
||||||
await system.fileSystem.writeToFile(filePath, code);
|
await this.system.fileSystem.writeToFile(filePath, code);
|
||||||
await system.fileSystem.setFilePermissions(filePath, '755');
|
await this.system.fileSystem.setFilePermissions(filePath, '755');
|
||||||
const command = getExecuteCommand(filePath, this.environment);
|
const command = getExecuteCommand(filePath, os);
|
||||||
system.command.execute(command);
|
this.system.command.execute(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExecuteCommand(scriptPath: string, environment: Environment): string {
|
function getExecuteCommand(
|
||||||
switch (environment.os) {
|
scriptPath: string,
|
||||||
|
currentOperatingSystem: OperatingSystem,
|
||||||
|
): string {
|
||||||
|
switch (currentOperatingSystem) {
|
||||||
case OperatingSystem.Linux:
|
case OperatingSystem.Linux:
|
||||||
return `x-terminal-emulator -e '${scriptPath}'`;
|
return `x-terminal-emulator -e '${scriptPath}'`;
|
||||||
case OperatingSystem.macOS:
|
case OperatingSystem.macOS:
|
||||||
@@ -37,6 +42,6 @@ function getExecuteCommand(scriptPath: string, environment: Environment): string
|
|||||||
case OperatingSystem.Windows:
|
case OperatingSystem.Windows:
|
||||||
return scriptPath;
|
return scriptPath;
|
||||||
default:
|
default:
|
||||||
throw Error(`unsupported os: ${OperatingSystem[environment.os]}`);
|
throw Error(`unsupported os: ${OperatingSystem[currentOperatingSystem]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { ISystemOperations } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
|
||||||
|
|
||||||
export interface IEnvironment {
|
|
||||||
readonly isDesktop: boolean;
|
|
||||||
readonly os: OperatingSystem | undefined;
|
|
||||||
readonly system: ISystemOperations | undefined;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { ISystemOperations } from './SystemOperations/ISystemOperations';
|
|
||||||
|
|
||||||
export type WindowVariables = {
|
|
||||||
system: ISystemOperations;
|
|
||||||
isDesktop: boolean;
|
|
||||||
os: OperatingSystem;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
||||||
interface Window extends WindowVariables { }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { IEnvironmentVariablesFactory } from './IEnvironmentVariablesFactory';
|
||||||
|
import { validateEnvironmentVariables } from './EnvironmentVariablesValidator';
|
||||||
|
import { ViteEnvironmentVariables } from './Vite/ViteEnvironmentVariables';
|
||||||
|
import { IEnvironmentVariables } from './IEnvironmentVariables';
|
||||||
|
|
||||||
|
export class EnvironmentVariablesFactory implements IEnvironmentVariablesFactory {
|
||||||
|
public static readonly Current = new EnvironmentVariablesFactory();
|
||||||
|
|
||||||
|
public readonly instance: IEnvironmentVariables;
|
||||||
|
|
||||||
|
protected constructor(validator: EnvironmentVariablesValidator = validateEnvironmentVariables) {
|
||||||
|
const environment = new ViteEnvironmentVariables();
|
||||||
|
validator(environment);
|
||||||
|
this.instance = environment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnvironmentVariablesValidator = typeof validateEnvironmentVariables;
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
import { IEnvironmentVariables } from './IEnvironmentVariables';
|
||||||
|
|
||||||
/* Validation is externalized to keep the environment objects simple */
|
/* Validation is externalized to keep the environment objects simple */
|
||||||
export function validateMetadata(metadata: IAppMetadata): void {
|
export function validateEnvironmentVariables(environment: IEnvironmentVariables): void {
|
||||||
if (!metadata) {
|
if (!environment) {
|
||||||
throw new Error('missing metadata');
|
throw new Error('missing environment');
|
||||||
}
|
}
|
||||||
const keyValues = capturePropertyValues(metadata);
|
const keyValues = capturePropertyValues(environment);
|
||||||
if (!Object.keys(keyValues).length) {
|
if (!Object.keys(keyValues).length) {
|
||||||
throw new Error('Unable to capture metadata key/value pairs');
|
throw new Error('Unable to capture key/value pairs');
|
||||||
}
|
}
|
||||||
const keysMissingValue = getMissingMetadataKeys(keyValues);
|
const keysMissingValue = getKeysMissingValues(keyValues);
|
||||||
if (keysMissingValue.length > 0) {
|
if (keysMissingValue.length > 0) {
|
||||||
throw new Error(`Metadata keys missing: ${keysMissingValue.join(', ')}`);
|
throw new Error(`Environment keys missing: ${keysMissingValue.join(', ')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMissingMetadataKeys(keyValuePairs: Record<string, unknown>): string[] {
|
function getKeysMissingValues(keyValuePairs: Record<string, unknown>): string[] {
|
||||||
return Object.entries(keyValuePairs)
|
return Object.entries(keyValuePairs)
|
||||||
.reduce((acc, [key, value]) => {
|
.reduce((acc, [key, value]) => {
|
||||||
if (!value) {
|
if (!value && typeof value !== 'boolean') {
|
||||||
acc.push(key);
|
acc.push(key);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Represents essential metadata about the application.
|
* Represents essential metadata about the application.
|
||||||
*
|
|
||||||
* Designed to decouple the process of retrieving metadata
|
|
||||||
* (e.g., from the build environment) from the rest of the application.
|
|
||||||
*/
|
*/
|
||||||
export interface IAppMetadata {
|
export interface IAppMetadata {
|
||||||
readonly version: string;
|
readonly version: string;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { IAppMetadata } from './IAppMetadata';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Designed to decouple the process of retrieving environment variables
|
||||||
|
* (e.g., from the build environment) from the rest of the application.
|
||||||
|
*/
|
||||||
|
export interface IEnvironmentVariables extends IAppMetadata {
|
||||||
|
readonly isNonProduction: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { IEnvironmentVariables } from './IEnvironmentVariables';
|
||||||
|
|
||||||
|
export interface IEnvironmentVariablesFactory {
|
||||||
|
readonly instance: IEnvironmentVariables;
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
// Only variables prefixed with VITE_ are exposed to Vite-processed code
|
// Only variables prefixed with VITE_ are exposed to Vite-processed code
|
||||||
export const VITE_ENVIRONMENT_KEYS = {
|
export const VITE_USER_DEFINED_ENVIRONMENT_KEYS = {
|
||||||
VERSION: 'VITE_APP_VERSION',
|
VERSION: 'VITE_APP_VERSION',
|
||||||
NAME: 'VITE_APP_NAME',
|
NAME: 'VITE_APP_NAME',
|
||||||
SLOGAN: 'VITE_APP_SLOGAN',
|
SLOGAN: 'VITE_APP_SLOGAN',
|
||||||
REPOSITORY_URL: 'VITE_APP_REPOSITORY_URL',
|
REPOSITORY_URL: 'VITE_APP_REPOSITORY_URL',
|
||||||
HOMEPAGE_URL: 'VITE_APP_HOMEPAGE_URL',
|
HOMEPAGE_URL: 'VITE_APP_HOMEPAGE_URL',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const VITE_ENVIRONMENT_KEYS = {
|
||||||
|
...VITE_USER_DEFINED_ENVIRONMENT_KEYS,
|
||||||
|
DEV: 'DEV',
|
||||||
|
} as const;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { IAppMetadata } from '../IAppMetadata';
|
import { IEnvironmentVariables } from '../IEnvironmentVariables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the application's metadata using Vite's environment variables.
|
* Provides the application's environment variables.
|
||||||
*/
|
*/
|
||||||
export class ViteAppMetadata implements IAppMetadata {
|
export class ViteEnvironmentVariables implements IEnvironmentVariables {
|
||||||
// Ensure the use of import.meta.env prefix for the following properties.
|
// Ensure the use of import.meta.env prefix for the following properties.
|
||||||
// Vue will replace these statically during production builds.
|
// Vue will replace these statically during production builds.
|
||||||
|
|
||||||
@@ -26,4 +26,8 @@ export class ViteAppMetadata implements IAppMetadata {
|
|||||||
public get homepageUrl(): string {
|
public get homepageUrl(): string {
|
||||||
return import.meta.env.VITE_APP_HOMEPAGE_URL;
|
return import.meta.env.VITE_APP_HOMEPAGE_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isNonProduction(): boolean {
|
||||||
|
return import.meta.env.DEV;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,20 @@
|
|||||||
import { IEventSubscription } from './IEventSource';
|
|
||||||
import { IEventSubscriptionCollection } from './IEventSubscriptionCollection';
|
import { IEventSubscriptionCollection } from './IEventSubscriptionCollection';
|
||||||
|
import { IEventSubscription } from './IEventSource';
|
||||||
|
|
||||||
export class EventSubscriptionCollection implements IEventSubscriptionCollection {
|
export class EventSubscriptionCollection implements IEventSubscriptionCollection {
|
||||||
private readonly subscriptions = new Array<IEventSubscription>();
|
private readonly subscriptions = new Array<IEventSubscription>();
|
||||||
|
|
||||||
public register(...subscriptions: IEventSubscription[]) {
|
public get subscriptionCount() {
|
||||||
|
return this.subscriptions.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public register(subscriptions: IEventSubscription[]) {
|
||||||
|
if (!subscriptions || subscriptions.length === 0) {
|
||||||
|
throw new Error('missing subscriptions');
|
||||||
|
}
|
||||||
|
if (subscriptions.some((subscription) => !subscription)) {
|
||||||
|
throw new Error('missing subscription in list');
|
||||||
|
}
|
||||||
this.subscriptions.push(...subscriptions);
|
this.subscriptions.push(...subscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,4 +22,9 @@ export class EventSubscriptionCollection implements IEventSubscriptionCollection
|
|||||||
this.subscriptions.forEach((listener) => listener.unsubscribe());
|
this.subscriptions.forEach((listener) => listener.unsubscribe());
|
||||||
this.subscriptions.splice(0, this.subscriptions.length);
|
this.subscriptions.splice(0, this.subscriptions.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsubscribeAllAndRegister(subscriptions: IEventSubscription[]) {
|
||||||
|
this.unsubscribeAll();
|
||||||
|
this.register(subscriptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
|
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
|
||||||
|
|
||||||
export interface IEventSubscriptionCollection {
|
export interface IEventSubscriptionCollection {
|
||||||
register(...subscriptions: IEventSubscription[]);
|
readonly subscriptionCount: number;
|
||||||
|
|
||||||
unsubscribeAll();
|
register(subscriptions: IEventSubscription[]): void;
|
||||||
|
unsubscribeAll(): void;
|
||||||
|
unsubscribeAllAndRegister(subscriptions: IEventSubscription[]);
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/infrastructure/Log/ConsoleLogger.ts
Normal file
13
src/infrastructure/Log/ConsoleLogger.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
export class ConsoleLogger implements ILogger {
|
||||||
|
constructor(private readonly globalConsole: Partial<Console> = console) {
|
||||||
|
if (!globalConsole) {
|
||||||
|
throw new Error('missing console');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(...params: unknown[]): void {
|
||||||
|
this.globalConsole.info(...params);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/infrastructure/Log/ElectronLogger.ts
Normal file
12
src/infrastructure/Log/ElectronLogger.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ElectronLog } from 'electron-log';
|
||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
// Using plain-function rather than class so it can be used in Electron's context-bridging.
|
||||||
|
export function createElectronLogger(logger: Partial<ElectronLog>): ILogger {
|
||||||
|
if (!logger) {
|
||||||
|
throw new Error('missing logger');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
info: (...params) => logger.info(...params),
|
||||||
|
};
|
||||||
|
}
|
||||||
3
src/infrastructure/Log/ILogger.ts
Normal file
3
src/infrastructure/Log/ILogger.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface ILogger {
|
||||||
|
info (...params: unknown[]): void;
|
||||||
|
}
|
||||||
5
src/infrastructure/Log/ILoggerFactory.ts
Normal file
5
src/infrastructure/Log/ILoggerFactory.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
export interface ILoggerFactory {
|
||||||
|
readonly logger: ILogger;
|
||||||
|
}
|
||||||
5
src/infrastructure/Log/NoopLogger.ts
Normal file
5
src/infrastructure/Log/NoopLogger.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
export class NoopLogger implements ILogger {
|
||||||
|
public info(): void { /* NOOP */ }
|
||||||
|
}
|
||||||
20
src/infrastructure/Log/WindowInjectedLogger.ts
Normal file
20
src/infrastructure/Log/WindowInjectedLogger.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { WindowVariables } from '../WindowVariables/WindowVariables';
|
||||||
|
import { ILogger } from './ILogger';
|
||||||
|
|
||||||
|
export class WindowInjectedLogger implements ILogger {
|
||||||
|
private readonly logger: ILogger;
|
||||||
|
|
||||||
|
constructor(windowVariables: WindowVariables = window) {
|
||||||
|
if (!windowVariables) {
|
||||||
|
throw new Error('missing window');
|
||||||
|
}
|
||||||
|
if (!windowVariables.log) {
|
||||||
|
throw new Error('missing log');
|
||||||
|
}
|
||||||
|
this.logger = windowVariables.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(...params: unknown[]): void {
|
||||||
|
this.logger.info(...params);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { IAppMetadata } from './IAppMetadata';
|
|
||||||
import { IAppMetadataFactory } from './IAppMetadataFactory';
|
|
||||||
import { validateMetadata } from './MetadataValidator';
|
|
||||||
import { ViteAppMetadata } from './Vite/ViteAppMetadata';
|
|
||||||
|
|
||||||
export class AppMetadataFactory implements IAppMetadataFactory {
|
|
||||||
public static readonly Current = new AppMetadataFactory();
|
|
||||||
|
|
||||||
public readonly instance: IAppMetadata;
|
|
||||||
|
|
||||||
protected constructor(validator: MetadataValidator = validateMetadata) {
|
|
||||||
const metadata = new ViteAppMetadata();
|
|
||||||
validator(metadata);
|
|
||||||
this.instance = metadata;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MetadataValidator = typeof validateMetadata;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { IAppMetadata } from './IAppMetadata';
|
|
||||||
|
|
||||||
export interface IAppMetadataFactory {
|
|
||||||
readonly instance: IAppMetadata;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
|
||||||
|
export interface IRuntimeEnvironment {
|
||||||
|
readonly isDesktop: boolean;
|
||||||
|
readonly os: OperatingSystem | undefined;
|
||||||
|
readonly isNonProduction: boolean;
|
||||||
|
}
|
||||||
@@ -1,29 +1,29 @@
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { ISystemOperations } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||||
|
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||||
|
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||||
import { BrowserOsDetector } from './BrowserOs/BrowserOsDetector';
|
import { BrowserOsDetector } from './BrowserOs/BrowserOsDetector';
|
||||||
import { IBrowserOsDetector } from './BrowserOs/IBrowserOsDetector';
|
import { IBrowserOsDetector } from './BrowserOs/IBrowserOsDetector';
|
||||||
import { IEnvironment } from './IEnvironment';
|
import { IRuntimeEnvironment } from './IRuntimeEnvironment';
|
||||||
import { WindowVariables } from './WindowVariables';
|
|
||||||
import { validateWindowVariables } from './WindowVariablesValidator';
|
|
||||||
|
|
||||||
export class Environment implements IEnvironment {
|
export class RuntimeEnvironment implements IRuntimeEnvironment {
|
||||||
public static readonly CurrentEnvironment: IEnvironment = new Environment(window);
|
public static readonly CurrentEnvironment: IRuntimeEnvironment = new RuntimeEnvironment(window);
|
||||||
|
|
||||||
public readonly isDesktop: boolean;
|
public readonly isDesktop: boolean;
|
||||||
|
|
||||||
public readonly os: OperatingSystem | undefined;
|
public readonly os: OperatingSystem | undefined;
|
||||||
|
|
||||||
public readonly system: ISystemOperations | undefined;
|
public readonly isNonProduction: boolean;
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(
|
||||||
window: Partial<Window>,
|
window: Partial<Window>,
|
||||||
|
environmentVariables: IEnvironmentVariables = EnvironmentVariablesFactory.Current.instance,
|
||||||
browserOsDetector: IBrowserOsDetector = new BrowserOsDetector(),
|
browserOsDetector: IBrowserOsDetector = new BrowserOsDetector(),
|
||||||
windowValidator: WindowValidator = validateWindowVariables,
|
|
||||||
) {
|
) {
|
||||||
if (!window) {
|
if (!window) {
|
||||||
throw new Error('missing window');
|
throw new Error('missing window');
|
||||||
}
|
}
|
||||||
windowValidator(window);
|
this.isNonProduction = environmentVariables.isNonProduction;
|
||||||
this.isDesktop = isDesktop(window);
|
this.isDesktop = isDesktop(window);
|
||||||
if (this.isDesktop) {
|
if (this.isDesktop) {
|
||||||
this.os = window?.os;
|
this.os = window?.os;
|
||||||
@@ -34,7 +34,6 @@ export class Environment implements IEnvironment {
|
|||||||
this.os = browserOsDetector.detect(userAgent);
|
this.os = browserOsDetector.detect(userAgent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.system = window?.system;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,5 +44,3 @@ function getUserAgent(window: Partial<Window>): string {
|
|||||||
function isDesktop(window: Partial<WindowVariables>): boolean {
|
function isDesktop(window: Partial<WindowVariables>): boolean {
|
||||||
return window?.isDesktop === true;
|
return window?.isDesktop === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WindowValidator = typeof validateWindowVariables;
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface ISanityCheckOptions {
|
export interface ISanityCheckOptions {
|
||||||
readonly validateMetadata: boolean;
|
readonly validateEnvironmentVariables: boolean;
|
||||||
readonly validateEnvironment: boolean;
|
readonly validateWindowVariables: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ISanityCheckOptions } from './Common/ISanityCheckOptions';
|
import { ISanityCheckOptions } from './Common/ISanityCheckOptions';
|
||||||
import { ISanityValidator } from './Common/ISanityValidator';
|
import { ISanityValidator } from './Common/ISanityValidator';
|
||||||
import { MetadataValidator } from './Validators/MetadataValidator';
|
import { EnvironmentVariablesValidator } from './Validators/EnvironmentVariablesValidator';
|
||||||
|
|
||||||
const DefaultSanityValidators: ISanityValidator[] = [
|
const DefaultSanityValidators: ISanityValidator[] = [
|
||||||
new MetadataValidator(),
|
new EnvironmentVariablesValidator(),
|
||||||
];
|
];
|
||||||
|
|
||||||
/* Helps to fail-fast on errors */
|
/* Helps to fail-fast on errors */
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Environment } from '@/infrastructure/Environment/Environment';
|
|
||||||
import { IEnvironment } from '@/infrastructure/Environment/IEnvironment';
|
|
||||||
import { ISanityCheckOptions } from '../Common/ISanityCheckOptions';
|
|
||||||
import { FactoryValidator, FactoryFunction } from '../Common/FactoryValidator';
|
|
||||||
|
|
||||||
export class EnvironmentValidator extends FactoryValidator<IEnvironment> {
|
|
||||||
constructor(factory: FactoryFunction<IEnvironment> = () => Environment.CurrentEnvironment) {
|
|
||||||
super(factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override name = 'environment';
|
|
||||||
|
|
||||||
public override shouldValidate(options: ISanityCheckOptions): boolean {
|
|
||||||
return options.validateEnvironment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||||
|
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||||
|
import { ISanityCheckOptions } from '../Common/ISanityCheckOptions';
|
||||||
|
import { FactoryValidator, FactoryFunction } from '../Common/FactoryValidator';
|
||||||
|
|
||||||
|
export class EnvironmentVariablesValidator extends FactoryValidator<IEnvironmentVariables> {
|
||||||
|
constructor(
|
||||||
|
factory: FactoryFunction<IEnvironmentVariables> = () => {
|
||||||
|
return EnvironmentVariablesFactory.Current.instance;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
super(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override name = 'environment variables';
|
||||||
|
|
||||||
|
public override shouldValidate(options: ISanityCheckOptions): boolean {
|
||||||
|
return options.validateEnvironmentVariables;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user