Compare commits

...

86 Commits

Author SHA1 Message Date
undergroundwires
5bb13e34f8 win: fix store revert for multiple installs #260
This commit improves the revert script for store apps to handle
scenarios where `Get-AppxPackage` returns multiple packages. Instead of
relying on a single package result, the script now iterates over all
found packages and attempts installation using the `AppxManifest.xml`
for each. This ensures that even if multiple versions or instances of a
package are found, the script will robustly handle and attempt to install
each one until successful.

Other changes:

- Add better message with suggestion if the revert code fails, as
  discussed in #270.
- Improve robustness of finding manifest path by using `Join-Path`
  instead of basic string concatenation. This resolves wrong paths being
  built due to missing `\` in file path.
- Add check for null or empty `InstallLocation` before accessing
  manifest path. It prevents errors when accessing `AppxManifest.xml`,
  enhancing script robustness and reliability.
- Improve error handling in manifest file existence check with try-catch
  block to catch and log exceptions, ensuring uninterrupted script
  execution in edge cases such as when the script lacks access to read
  the directory.
- Add verification of package installation before attempting to install
  the package for increased robustness.
- Add documentation for revertCode.
2023-11-03 15:24:15 +01:00
undergroundwires
0466b86f10 win, linux: unify & improve Firefox clean-up #273
This commit unifies some of the logic, documentation and naming for
Firefox clean-up with improvements on both Linux and Windows platforms.

Windows:

- 'Clear browsing history and cache':
  - Not recommend.
  - Align script name and logic with Linux implementation.
  - New documentation and not including the script in recommendation
    provides safety against unintended data loss as discussed in #273.
- 'Clear Firefox user profiles, settings, and data':
  - Rename to 'Clear all Firefox user information and preferences' for
    improved clarity.
  - Add more documentation.

Linux:

- Replace `DeleteFromFirefoxProfiles` with
  `DeleteFilesFromFirefoxProfiles`.
- Migrate implementation to Python:
  - Add more user-friendly outputs.
  - Exclude removing directory itself for additional safety.

Both Linux and Windows:

- Improve documentation for:
  - 'Clear Firefox user profiles, settings, and data'
  - 'Clear Firefox history'
2023-11-02 13:18:54 +01:00
undergroundwires
ca81f68ff1 Migrate to Vue 3.0 #230
- Migrate from "Vue 2.X" to "Vue 3.X"
- Migrate from "Vue Test Utils v1" to "Vue Test Utils v2"

Changes in detail:

- Change `inserted` to `mounted`.
- Change `::v-deep` to `:deep`.
- Change to Vue 3.0 `v-modal` syntax.
- Remove old Vue 2.0 transition name, keep the ones for Vue 3.0.
- Use new global mounting API `createApp`.
- Change `destroy` to `unmount`.
- Bootstrapping:
  - Move `provide`s for global dependencies to a bootsrapper from
    `App.vue`.
  - Remove `productionTip` setting (not in Vue 3).
  - Change `IVueBootstrapper` for simplicity and Vue 3 compatible API.
  - Add missing tests.
- Remove `.text` access on `VNode` as it's now internal API of Vue.
- Import `CSSProperties` from `vue` instead of `jsx` package.
- Shims:
  - Remove unused `shims-tsx.d.ts`.
  - Remove `shims-vue.d.ts` that's missing in quickstart template.
- Unit tests:
  - Remove old typing workaround for mounting components.
  - Rename `propsData` to `props`.
  - Remove unneeded `any` cast workarounds.
  - Move stubs and `provide`s under `global` object.

Other changes:

- Add `dmg-license` dependency explicitly due to failing electron builds
  on macOS (electron-userland/electron-builder#6520,
  electron-userland/electron-builder#6489). This was a side-effect of
  updating dependencies for this commit.
2023-11-01 13:39:39 +01:00
undergroundwires
4995e49c46 Improve UI performance by optimizing reactivity
- Replace `ref`s with `shallowRef` when deep reactivity is not needed.
- Replace `readonly`s with `shallowReadonly` where the goal is to only
  prevent `.value` mutation.
- Remove redundant `ref` in `SizeObserver.vue`.
- Remove redundant nested `ref` in `TooltipWrapper.vue`.
- Remove redundant `events` export from `UseCollectionState.ts`.
- Remove redundant `computed` from `UseCollectionState.ts`.
- Remove `timestamp` from `TreeViewFilterEvent` that becomes unnecessary
  after using `shallowRef`.
- Add missing unit tests for `UseTreeViewFilterEvent`.
- Add missing stub for `FilterChangeDetails`.
2023-10-31 13:57:57 +01:00
undergroundwires
77123d8c92 win: change system app removal to hard delete #260
This commit changes the system app removal functionality in privacy.sexy
to perform a hard delete, while preserving the soft-delete logic for
handling residual files.

It improves in-code documentation to facilitate a clearer understanding
of the code execution flow, as the logic for removing system apps has
grown in complexity and length.

Transitioning to a hard-delete approach resolves issues related to
residual links to soft-deleted apps:

- Resolves issue with Edge remaining in the installed apps list (#236).
- Resolves issue with Edge remaining in the programs list (#194).
- Resolves issue with Edge shortcuts persisting in the start menu (#73).

Other changes:

- `RunPowerShell`:
  - Introduce `codeComment` and `revertCodeComment` parameters for
    improved in-code documentation.
- `CommentCode`:
  - Simplify naming to `Comment`.
  - Rename `comment` to `codeComment` for clarity.
  - Add functionality to comment on revert with the `revertCodeComment`
    parameter.
2023-10-30 12:39:10 +01:00
undergroundwires
e72c1c13ea win: improve file delete
This commit unifies the way the files are being deleted by introducing
the `DeleteFiles` function. It refactors existing scripts that are
deleting files to use the new function, to improve their documentation
and increase their safety.

Script changes:

- 'Clear Software Reporter Tool logs':
  - Rename to: 'Clear Google's "Software Reporter Tool" logs'
- 'Clear credentials in Windows Credential Manager':
  - Migrate code to PowerShell, removing the need to delete files.
  - Improve error messages and robustness of the implementation.
- 'Clear Nvidia residual telemetry files':
  - Extract to two scripts for more granularity and better
    documentation:
      1. 'Disable Nvidia telemetry components'
      2. 'Disable Nvidia telemetry drivers'
  - Change the logic so instead of clearing directory contents and
    deleting drivers, it conducts a soft delete for reversibility to
    prioritize user safety.
- 'Remove OneDrive residual files':
  - Improve documentation
- 'Clear primary Windows telemetry file':
  - Rename to 'Clear diagnostics tracking logs'.
  - Add missing file paths seen on modern versions of Windows.
  - Add more documentation.
- 'Clear Windows Update History (`WUAgent`) system logs':
  - Rename to 'Clear Windows update files'.
  - Add more documentation.
- 'Clear Cryptographic Services diagnostic traces':
  - Rename to 'Clear "Cryptographic Services" diagnostic traces'.
  - Add more documentation.

Other changes:

- Improve `DeleteGlob`:
  - Add iteration callbacks for its reusability.
  - Improve its documentation.
  - Make recursion optional.
  - Improve sanity check (verification) logic for given glob when
    granting permissions.
  - Fix granting permissions using wrong variable to find out parent
    directory.
- Improve `IterateGlob`:
  - Use `Get-Item` to get results. This fixes `DeleteDirectory` not
    being able to delete directory itself but just its contents.
  - Introduce and use `recurse` parameter to provide optional recursive
    search logic (to search in subdirectories) using `Get-ChildItem`.
  - Fix wrong PowerShell syntax for `$revert` variable value for
    `revertCode`: replace `true` with `$true`.
  - Order iterated paths based on their length to process the deepest
    item first.
  - Improve handling of missing files with correct/informative outputs
    when granting permissions.
- Improve `SoftDeleteFiles`:
  - Introduce and use `recurse` parameter for explicitness.
  - Fix undefined `$backupFilePath` by replacing it with correct
    `$originalFilePath`.
  - Improve documentation.
- Ensure consistent use of instructive language in code comments.
2023-10-29 18:42:41 +01:00
undergroundwires
e775d68a9b linux: fix string formatting of Firefox configs
This commit fixes some configurations being set wrong values to wrong
YAML notation used for string values.
2023-10-28 13:58:41 +02:00
undergroundwires
f8e5f1a5a2 Fix incorrect tooltip position after window resize
This commit fixes an issue where the tooltip position becomes inaccurate
after resizing the window.

The solution uses `autoUpdate` functionality of `floating-ui` to update
the position automatically on resize events. This function depends on
browser APIs: `IntersectionObserver` and `ResizeObserver`. The official
documentation recommends polyfilling those to support old browsers.

Polyfilling `ResizeObserver` is already part of the codebase, used by
`SizeObserver.vue`. This commit refactors polyfill logic to be reusable
across different components, and reuses it on `TooltipWrapper.vue`.

Polyfilling `IntersectionObserver` is ignored due to this API being
older and more widely supported.
2023-10-27 20:58:07 +02:00
undergroundwires
f4a74f058d win: improve soft file/app delete security #260
This commit improves soft file delete logic:

- Unify logic for soft deleting single files and system apps.
- Rename `RenameSystemFile` templating function to `SoftDeleteFiles` so
  new name gives clarity to:
   - It's not necessarily single file being renamed but can be multiple
     files.
   - It's not necessarily system files being renamed, but can also work
     without granting extra permissions.
- Grant permissions for only files that will be backed up, skipping
  unnecessarily granting permissions to folders/other files. Both
  `SeRestorePrivilege` and `SeTakeownershipPrivileges` are claimed and
  revoked as necessary.
- Make granting permissions optional through `grantPermissions`
  parameter. Do not take permissions if not needed.
- Restore permissions to system default after file is renamed. Before
  both deletion of system apps and renaming system files did not restore
  their original permissions. This might leave user computers
  vulnerable, which is fixed in this commit. It ensures that the
  system's original security posture is preserved.
- Deleting system apps is now independent of `Get-AppxPackage`,
  improving its robustness and enabling their execution once system apps
  are hard-deleted (#260)
- Introduce common way to share glob iteration logic of how the
  directories are being cleaned up. It reuses most of the logic from
  former `DeleteGlob` with some improvements:
  - Simplify call to `Get-ChildItem` by avoiding `-Filter` parameter.
  - Improve reliability of getting parent directory in `DeleteGlob`
    sanity check to use .NET's `[System.IO.Path]` methods.
2023-10-26 18:35:39 +02:00
undergroundwires
80821fca07 Fix compiler failing with nested with expression
The previous implementation of `WithParser` used regex, which struggles
with parsing nested structures correctly. This commit improves
`WithParser` to track and parse all nested `with` expressions.

Other improvements:

- Throw meaningful errors when syntax is wrong. Replacing the prior
  behavior of silently ignoring such issues.
- Remove `I` prefix from related interfaces to align with newer code
  conventions.
- Add more unit tests for `with` expression.
- Improve documentation for templating.
- `ExpressionRegexBuilder`:
  - Use words `capture` and `match` correctly.
  - Fix minor issues revealed by new and improved tests:
     - Change regex for matching anything except surrounding
       whitespaces. The new regex ensures that it works even without
       having any preceeding text.
     - Change regex for capturing pipelines. The old regex was only
       matching (non-greedy) first character of the pipeline in tests,
       new regex matches the full pipeline.
- `ExpressionRegexBuilder.spec.ts`:
  - Ensure consistent way to define `describe` and `it` blocks.
  - Replace `expectRegex` tests, regex expectations test internal
    behavior of the class, not the external.
  - Simplified tests by eliminating the need for UUID suffixes/prefixes.
2023-10-25 19:39:12 +02:00
undergroundwires
dfd4451561 win: improve script environment robustness #221
This commit ensures the script functions as expected, even when invoked
from unexpected environments.

Using `setlocal` initializes a distinct environment for privacy.sexy.
It's strategically placed after the admin privilege check to avoid
unnecessary setup in case of a relaunch. The script concludes with
`endlocal` right before the exit, maintaining a clean environment
throughout its execution and ensuring no unintentional global
environment modifications.

Changes:

- Enhance script's environment robustness.
- Add descriptive comments for script start/end sequences.
2023-10-24 16:56:54 +02:00
undergroundwires
8570b02dde win: prevent updates from reinstalling apps #260
This commit addresses the issue of unwanted applications being
reinstalled during a Windows update. By adding a specific registry
entry, this commit ensures that Windows apps, once removed, do not
return with subsequent updates.

This change ensures more control over the applications present on a
Windows system, particularly after an update, enhancing user experience
and systeam cleanliness.
2023-10-23 16:52:52 +02:00
undergroundwires
d6da406c61 Centralize Electron entry file path configuration
This commit refactors configuration to use centrally defined Electron
entry file path to improve maintainability and reduce duplication.

- Replace the hardcoded file path in the `main` field of `package.json`
  with a reference to the `ELECTRON_ENTRY` environment variable, managed
  by `electron-vite`.
- Update `electron-vite` to version 1.0.28, enabling the use of
  `ELECTRON_ENTRY` environment variable feature (details in
  alex8088/electron-vite#270).
2023-10-22 15:03:58 +02:00
undergroundwires
060e789662 win: improve directory cleanup security
This commit improves the security, reliability, and robustness of
directory cleanup operations on Windows.

The focus is shifted from deleting entire directories to purging their
contents, addressing potential unintended side effects. Previously,
numerous directories were removed, which could destabilize system
behavior.

This improvement has crucial security implications. The prior approach
involved changing ownership and assigning permissions to the directory
itself, leading to an altered and potentially less secure OS security
posture.

Directory removal improvements include:

- Output user-friendly messages.
- Improved ownership and permission handling for file deletion.
- Explicit shared functions for enhanced reliability/security.
- Centralized way to delete glob (wildcard) patterns in Windows.
Notable script improvements:

- 'Clear Steam dumps, logs, and traces':
  - Convert the script to a category to provide more granularity.
  - Improve cache cleaning, ensuring the entire cache directory is
    cleared, not just the log files.
- 'Clear "Temporary Internet Files" (browser cache)':
  - Add more documentation.
  - Grant necessary permissions to folders, fixing errors due to
    lack of permissions before.
- 'Clear Windows Update Medic Service logs':
  - Remove redundant permission grants, as they are unnecessary in
    recent Windows versions.
- 'Clear Server-initiated Healing Events system logs',
  'Clear Windows Update events logs':
  - Merge due to identical functionalities.
  - Add more documentation.
- 'Clear Defender scan (protection) history':
  - Remove the execution with `TrustedInstallerPrivileges`, uniformly
    using `grantPermissions` as with other scripts. This addresses the
    false-positive alerts from Microsoft Defender, as discussed in #264.
- 'Clear "Temporary Internet Files" (browser cache)':
  - Retain `INetCache` and `Temporary Internet Files` directories,
    purging only their contents. This approach aims to resolve the issue
    mentioned in #145, where the absence of these folders could prevent
    Microsoft Office applications from launching.
2023-10-21 17:41:37 +02:00
undergroundwires
e40b9a3cf5 win: fix Microsoft Advertising app removal #200
This commit fixes the issue where the Microsoft Advertising app fails to
be removed using the standard script. The problem is due to Microsoft
Advertising SDK (`Microsoft.Advertising.Xaml`) acting as a framework
package. Such packages are automatically installed when a specific
application requires them, and they cannot be individually removed if
there are applications that depend on them. The only way to effectively
remove this library is by uninstalling the dependent applications.

Key findings:

- On Windows 11 22H2, the issue does not arise as the package does not
  exist.
- On Windows 10 22H2, the user is prompted to delete dependent
  applications, like MSN Weather and Mail And Calendar apps. Once these
  apps are removed, the Microsoft Advertising app is automatically
  removed.

Given the nuances and potential for confusion, this script is removed.
This means that the app will no longer be removed directly but will be
handled as part of the removal of its dependencies.
2023-10-20 16:04:25 +02:00
undergroundwires
237d9944f9 Fix YAML error for site release in CI/CD
Fix the syntax error in the GitHub action script that was caused by
improper multi-line YAML notation. This correction ensures the action
can successfully parse and execute.
2023-10-19 12:33:26 +02:00
undergroundwires
79b46bf210 Improve performance of rendering during search
Optimize the tree view rendering during searches by enhancing the render
queue ordering. This update changes the rendering order to prioritize
visible nodes, leading to faster appearance of these nodes during
searches. The ordering logic now ignores the depth in the hierarchy and
instead focused on the node order. The collapsed check for the node
itself is removed, ensuring that visible collapsed parents are first
while their invisible children are rendered later.
2023-10-18 16:44:49 +02:00
undergroundwires
98a26f9ae4 win: improve system app uninstall /w fallback #260
This commit improves soft deletion of system apps. Before if the package
was missing, it failed to recover or delete system apps. Now, it works
even though if `Get-AppxPackage` returns null (i.e. package is
non-existing), so it can be executed even after a hard delete. This
allows safely introducing hard-delete of system apps (as discussed in
 #260) with still keeping a robust soft-delete as complement.

Before, the script was dependent on `Get-AppxPackage.InstallLocation`,
however a system app can only be located in one of these folders:

- C:\Windows\SystemApps\{PackageFamilyName}
- C:\Windows\{ShortAppName}

To ensure resilience, this commit adjust the script to rename the files
within these directories if Get-AppxPackage fails, this provides a
fallback.
2023-10-17 13:56:32 +02:00
undergroundwires
dbe3c5cfb9 win: improve system app uninstall cleanup #73
- Add documentation about folders.
- Add more user-friendly logging.
- Continue uninstallation if single folder fails (remove throw).
- Continue uninstallation if renaming single file fails.
- Add handling of `Metadata` folder as suggested in #73.
2023-10-16 18:42:29 +02:00
undergroundwires
25d7f7b2a4 Bump dependencies to latest
This commit updates various dependencies to their latest versions.

Other changes include:

- Moved the following from `devDependencies` to `dependencies`:
  - `electron-log`
  - `electron-updater`
- Remove `npm` dependency.
- Code changes:
  - Add type casting in several places to align with the latest
    `typescript` version.
  - Adopt to new return type of `setTimeout`.
- Dependencies not upgraded due to
  `@vue/eslint-config-airbnb-with-typescript` not supporting
  `@eslint-typescript` V6 (see vuejs/eslint-config-airbnb#58):
  - `vue/eslint-config-typescript`
  - `@typescript-eslint/eslint-plugin`
  - `@typescript-eslint/parser`
- Enable video recording for cypress as it's disabled by default since
  13.X.X.
2023-10-16 02:06:19 +02:00
undergroundwires-bot
b76e99ac0f ⬆️ bump everywhere to 0.12.5 2023-10-14 14:17:25 +00:00
undergroundwires
67c3677621 win, linux, mac: fix typos and improve naming
- Use instruction format such as "do this, do that" to provide clear,
  direct instructions. This format minimize confusion and is easy to
  follow. They are specific and leave no room for interpretation,
  stating precisely what needs to be done without ambiguity.
- Fix typos and grammar issues.
- Improve consistency in script and category names.
- Revise sentences for more natural English language flow.
- Change brand name casing to match official branding.
- Change title case (all words start capitalized) to sentence case.
- Prioritize consistency over variations.
- Add minor documentation to explain scripts where the names are not
  clear.
- Add naming guidelines.
2023-10-13 20:14:33 +02:00
undergroundwires
bab6316e76 win: fix and improve AppCompat disabling #255
- Introduce a new parent category: 'Disable Application Compatibility
  framework" for better categorization.
- Move following existing scripts under the new category:
  - Disable Application Impact Telemetry (AIT)
  - Disable steps recorder
  - Disable Inventory Collector
  - Program Compatibility Assistant Service
- Add new scripts new scripts within the same category:
  - Disable Application Compatibility Engine
  - Disable "Program Compatibility Assistant (PCA)" feature
  - Disable "Program Compatibility Assistant Service" (`PcaSvc`)
- Add missing revert codes for:
  - 'Disable steps recorder'
- Fix revert codes for scripts:
  - 'Disable Inventory Collector'
  - 'Disable Application Impact Telemetry (AIT)' (as pointed in #255).
- Add extensive documentation for all related scripts.
- Rename scripts for clarity:
  - 'Disable Inventory Collector' > 'Disable "Inventory Collector"
    task'.
  - 'Program Compatibility Assistant Service' > 'Disable "Program
    Compatibility Assistant Service" (`PcaSvc`) service'.
  - 'Disable steps recorder' > 'Disable Steps Recorder (collects
    screenshots, mouse/keyboard input and UI data)'.
2023-10-12 14:49:35 +02:00
undergroundwires
48730bca05 Implement new UI component for icons #230
- Introduce `AppIcon.vue`, offering improved performance over the
  previous `fort-awesome` dependency. This implementation reduces bundle
  size by 67.31KB (tested for web using `npm run build -- --mode prod`).
- Migrate Font Awesome 5 icons to Font Awesome 6.

This commit facilitates migration to Vue 3.0 (#230) and ensures no Vue
component remains tightly bound to a specific Vue version, enhancing
code portability.

Font Awesome license is not included because Font Awesome revokes its
right:

> "Attribution is no longer required as of Font Awesome 3.0"
>
> Sources:
>
> - https://fontawesome.com/v4/license/ (archived: https://web.archive.org/web/20231003213441/https://fontawesome.com/v4/license/, https://archive.ph/Yy9j5)
> - https://github.com/FortAwesome/Font-Awesome/wiki (archived: https://web.archive.org/web/20231003214646/https://github.com/FortAwesome/Font-Awesome/wiki, https://archive.ph/C6sXv)

This commit removes following third-party production dependencies:

- `@fortawesome/vue-fontawesome`
- `@fortawesome/free-solid-svg-icons`
- `@fortawesome/free-regular-svg-icons`
- `@fortawesome/free-brands-svg-icons`
- `@fortawesome/fontawesome-svg-core`
2023-10-11 18:38:19 +02:00
undergroundwires
698b570ee6 Fix working directory in CI/CD web release
This commit fixes the CI/CD website release process which was failing
due to an incorrect working directory setting. The `working-directory`
is now correctly set within the action workflow, ensuring the `npm run
install-deps` command runs in project root directory where
`package.json` exists.
2023-10-10 15:37:59 +02:00
undergroundwires
a3f11dff18 win: improve app reversion and docs #260
This commit prepares for #260, aiming for a hard delete of system apps,
and necessitating a more reliable app reversion method.

- Improve documentation:
  - Add existence status for latest OS versions.
  - Add command for quick future testing.
  - Use archive links.
  - Document categories.
  - Add documentation to list of default apps to give context about why
    the package is here.
  - Fix wrong store URL for Cortana app.
  - Unify documentation of excluded apps.
- Fix categorization:
  - Categorize uninstallation of Windows store apps.
  - Remove "Zune" category (flatten children apps) to be able to align
    with latest branding.
  - Categorize uninstallation of Candy Crush apps.
  - Categorize uninstallation of OOBE apps.
- Rename:
  - "Uninstall Windows store apps" to "Uninstall Windows apps" as these
    apps are not necessarily store apps.
  - "Xbox Game Bar Plugin appcache" to "Xbox Game Bar Plugin".
  - "Groove Music" to "Windows Media Player".
  - "Movies and TV" to "Movies & TV".
  - "Your Phone" to "Phone Link".
  - "Cred Dialog Host" to "Credentials Dialog Host".
  - "Windows Voice Recorder" to "Windows Sound Recorder".
  - "Remote Desktop" to "Microsoft Remote Desktop"
  - "Microsoft To Do" to "Microsoft To Do: Lists, Tasks & Reminders".
  - "People Hub app (People Experience Host)" to "People Hub app".
  - "My Office" to "Microsoft 365 (Office)".
  - "iHeartRadio" to "iHeart: Radio, Music, Podcasts".
  - "Duolingo" to "Duolingo - Language Lessons".
  - "Photoshop Express" to "Adobe Photoshop Express".
  - "Spotify" to "Spotify - Music and Podcasts".
  - "Windows Alarms and Clock" to "Windows Clock".
  - "OOBE Network Captive Port" to "OOBE Network Captive Portal".
  - "Secure Assessment Browser app (breaks Microsoft Intune/Graph)" to
    "Take a Test app".
  - "Windows 10 Family Safety / Parental Controls" > "Microsoft Family
    Safety / Parental control".
  - "People / People Bar App on taskbar (People Experience Host)" > "My
    People"
  - "MSN News" > "Microsoft News"
  - "Minecraft for Windows 10" > "Minecraft for Windows"
  - "Snip & Sketch" > "Snipping Tool"
  - "Bio enrollment" > "Hello setup UI"
- Fix package names for:
  - `AdobeSystemIncorporated.AdobePhotoshop` >
    `AdobeSystemsIncorporated.AdobePhotoshopExpress`
2023-10-09 16:21:26 +02:00
undergroundwires
5e359c2fb8 win: fix and improve network data usage reset #265
Fix `Clear (Reset) Network Data Usage` trying to delete other files from
Windows system directory.

Changes:

- Precisely target the deletion of `C:\System32\sru\SRUDB.dat`.
- Improve documentation.
- Handle explicitly and better if `DPS` service is missing.
- Rename script from `Clear (Reset) Network Data Usage` to `Clear System
  Resource Usage Monitor (SRUM) data` for clearer representation.
- Migrate script from batchfile to PowerShell for better
  maintainability and readability.
- Add user-friendly output messages.
- Improve script logic to avoid unnecessary service start/stop when the
  file doesn't exist.
2023-10-08 15:55:06 +02:00
undergroundwires
2147eae687 Add developer toolkit UI component
The commit adds a new a UI component that's enabled in development mode.
This component, initially, provides a button that wen clicked, logs all
the script and category names to the console. It helps revising names
used throughout the application.

By having this component in a conditionally rendered component, it's
excluded from the production builds.
2023-10-07 15:14:53 +02:00
undergroundwires
286295128d win: relocate and document SecHealthUI #190
- Move removal of `SecHealthUI` app to "Privacy over security" category.
- Emphasize disruptive behavior in the script name.
- Add comprehensive documentation
2023-10-06 14:02:11 +02:00
undergroundwires
8501495c17 win: improve Edge & OneDrive shortcut removal #73
- Add script to remove Edge shortcuts upon uninstallation.
- Unify OneDrive shortcut removal logic with Edge's, introducing revert
  feature to the OneDrive removal script.
- Add more extensive documentation.
- Rename "Delete OneDrive shortcuts" to "Remove OneDrive shortcuts" to
  have consistent naming.
2023-10-05 11:50:21 +02:00
undergroundwires
888c9166fc win: add removal of Edge assocations #64
This commit introduces scripts for cleaning up file and URL associations
related to Microsoft Edge, enhancing the uninstallation process. The
changes adress the issues detailed in #64, improving system reliability,
integrity and security by preventing lingering associations.

Changes include:

- Introduce scripts to clear Edge browser file and URL associations.
- Provide extensive documentation for related scripts.
- Ensure thorough cleanup of URL, file, OpenWith menu, and toast
  associations.
- Recommend removing Microsoft Edge (Legacy) Dev Tools Client app on
  Strict to align with other Edge legacy removal recommendations.
2023-10-04 11:22:47 +02:00
undergroundwires
e5f6edf405 linux: fix obsolete Firefox DPI script #239
- Replace obsolete "Firefox First party isolation" with "Firefox state
  partitioning".
- Add comprehensive documentation for the new scripts.
- Introduce enabling dynamic First-Party Isolation (dFPI)
- Disable deprecated First-Party Isolation (FPI) to avoid conflicts with
  dFPI.
- Add script to enable Firefox network partitioning to cover
  functionality of older FPI script.
2023-10-03 12:36:06 +02:00
undergroundwires
e8a52f717d win, linux: improve VSCode setting robustness #196
This commit enhances the robustness of setting VSCode configurations,
ensuring consistent and reliable operation even in edge cases, such as
when the settings file is empty. This commit also uniforms behavior of
Linux and Windows modification of VSCode settings.

On Windows:

- Move parameters to on top of scripts to be able to easily test the
  scripts using PowerShell without compiling.
- Add a check to exit the script with an error message if the attempt to
  parse the JSON content fails.
- Omit the `OutString` cmdlet from the pipeline in the script for
  converting JSON file content to a PowerShell object. `Out-String` is
  unnecessary in this context because `Get-Content` already outputs the
  file content as a string array, which `ConvertFrom-Json` effectively.
  Additionally, using `Out-String` could potentially introduce issues by
  concatenating file content into a single string, causing
  `ConvertFrom-Json` to fail when processing pretty-printed JSON. By
  removing `Out-String`, the script is streamlined and potential errors
  are avoided.
- Add logic to handle empty settings file. Add an additional check for
  empty settings file, if the file is empty, the script writes a default
  empty JSON object (`{}`) to the file. The operation is logged to
  ensure transparency, notifying the user of the action taken. This
  change removes fails due to empty setting files.
- When reverting, do not fail if the setting file is missing because it
  means that default settings are already in-place.
- When reverting, show informative message if the key does not exist or
  does not have the value set by privacy.sexy and do not take any
  further action.
- If the desired value is already set, show a message for it and skip
  updating the setting file.

On Linux:

- Handles empty `settings.json` similarly to Windows.
- Add more user friendly error if JSON file cannot be parsed.
2023-10-02 14:33:55 +02:00
undergroundwires
d45750428c win: fix and improve temp dir cleanup #176, #89
This commit improve cleanup of temporary directories on Windows,
addressing issues #176 and #89.

Changes include:

- Fix side-effects caused by this script by clearing the contents of
  directories rather than deleting the directories themselves.
- Add the removal of Prefetch directory contents, which stores temporary
  files and can enhance privacy and free up disk space when cleared.
- Remove the command `del /f /q %localappdata%\Temp\*` due to its
  redundancy.
- Improve the granularity and documentation of cleanup scripts, and
  moving the `Clear temporary Windows files` category up in the hierarchy
  for better structure and clarity.

Co-authored-by: iam-py-test <84232764+iam-py-test@users.noreply.github.com>
2023-10-01 17:42:25 +02:00
undergroundwires
cf55ca9e28 Add Scoop download instructions #174
- Add "Further Installation Options" section.
- Move releases page reference to the new section to keep Get Started
  simple.

Co-authored-by: MrEddX <101912712+Zliced13@users.noreply.github.com>
2023-09-29 14:03:07 +02:00
undergroundwires
3e5239f7d3 Add SAST security checks with SECURITY.md #178
This commit incorporates Static Analysis Security Testing (SAST) using
CodeQL. This integration will enforce consistent security assessments
with every change and on a predetermined schedule.

This commit also involves a restructure of security checks. The existing
security-checks workflow is renamed to better reflect its functionality
related to dependency audits.

These changes will enhance the project's resilience against potential
vulnerabilities in both the codebase and third-party dependencies.

Changes include:

- Remove older LGTM badge that's replaced by SAST checks.
- Rename `checks.security.yaml` to `checks.security.dependencies.yaml`,
  reinforcing the focus on dependency audits.
- Update `README.md`, ensuring the clear representation of security
  check statuses, including new SAST integration.
- Add new `SECURITY.md`, establishing the protocol for reporting
  vulnerabilities and outlining the project's commitment to robust
  security testing.
- Enhance `docs/tests.md` with detailed information on the newly
  integrated security checks.
- Add reference to SECURITY.md in README.md.
2023-09-28 15:19:09 +02:00
undergroundwires
7669985f8e Fix Docker build and improve checks #220
This commit improves multiple aspects of Docker builds:

- Enable artifact output validation for Dockerfile.
- Correct the path references in Dockerfile for the distribution
  directory.
- Add Dockerfile specific indentation rules to `.editorconfig`.
- Use `npm run install-deps` for dependency installation, enhancing
  build reliability.
- Add automation script `verify-web-server-status.js` to verify running
  web server on given URL.
- Introduce automated build verification for Dockerfile:
  - On macOS, install Docker with colima as the container runtime
    because default agents do not include Docker and Docker runtime is
    not installed due to licensing issues (see actions/runner-images#17).
  - On Windows, there's no Linux container support (actions/runner#904,
    actions/runner-images#1143), so keep the checks for macOS and Ubuntu
    only.
2023-09-27 19:53:40 +02:00
undergroundwires-bot
5047c9b6e7 ⬆️ bump everywhere to 0.12.4 2023-09-26 11:45:04 +00:00
undergroundwires
bd2082e8c5 Fix slow appearance of nodes on tree view
The tree view rendering performance is optimized by improving the node
render queue ordering. The node rendering order is modified based on the
expansion state and the depth in the hierarchy, leading to faster
rendering of visible nodes. This optimization is applied when the tree
nodes are not expanded to improve the rendering speed.

This new ordering ensures that nodes are rendered more efficiently,
prioritizing nodes that are collapsed and are at a higher level in the
hierarchy.
2023-09-25 14:21:29 +02:00
undergroundwires
8f188acd3c Fix loss of tree node state when switching views
This commit fixes an issue where the check state of categories was lost
when toggling between card and tree views. This is solved by immediately
emitting node state changes for all nodes. This ensures consistent view
transitions without any loss of node state information.

Furthermore, this commit includes added unit tests for the modified code
sections.
2023-09-24 20:34:47 +02:00
undergroundwires
0303ef2fd9 Fix outdated and broken links in README #161
This commit fixes issues with download URLs of desktop application
artifacts on README.md

- Corrected typo in Linux AppImage link
- Updated older version links to the newest release

Co-authored-by: MrEddX <66828538+MrEddX@users.noreply.github.com>
2023-09-23 10:33:46 +02:00
undergroundwires
cb21a970b6 win: fix Defender scan artifacts removal #246
- Modify script to run as `TrustedInstaller`, resolving access right
  problems discussed in #246.
- Change script name for better alignment with its functionality.
- Improve script description for clarity and detailed documentation.
2023-09-22 14:11:52 +02:00
undergroundwires
203daeb4a2 win: fix delivery optimization side-effects #173
- Add non-intrusive way to disable delivery optimization. This new
  script do not introduce side-effects caused by disabling Delivery
  Optimization service.
- Recomend delivery optimization service (`DoSvc`) only on Strict
  mode, removing it from Standard recommendation.
- Categorize delivery optimization disabling under one category.
- Move disabling delivery optimization to "Disable OS collection" >
  "Disable Windows Update data collection".
- Add more documentation.
2023-09-21 11:40:15 +02:00
undergroundwires
60dde11311 win: fix uninstallation of newer Edge #236
- Fix script failing when multiple installations of Edge is found.
- Fix Edge not being able to be uninstalled due in newer Edge versions.
- Add documentation
- Add missing revert script
2023-09-20 07:48:50 +02:00
undergroundwires
8b930fc57c Rewrite tooltip UI for efficiency and Vue 3.0 #230
- Introduce a new UI component for tooltips.
- Fix tooltip arrow misalignment issues in code download/execution
  instructions dialogs.

Reasons for dropping `v-tooltip` dependency:

- Lack of support for Vue 3.0, which blocks migration to Vue 3.0 (see
  #230).
- Inability to render HTML content that's required for privacy.sexy.
- Inefficient, adding an extra 162.48 KB to the production bundle for
  web distribution (tested using `npm run build -- --mode production`).

Advantages of adopting `floating-ui` (Floating UI):

- Compatibility across multiple Vue versions including 2.0, 2.7, and 3.0.
- Reduced boilerplate resulting in cleaner, more maintainable code.
- Efficient position recalculations without reinventing the wheel.
2023-09-18 17:57:50 +02:00
undergroundwires
f810ed0c14 Fix no spacing after lists in documentation text
This commit adds missing vertical margin paragraphs that appear after
lists. It also changes vertical margin gap to match the font size along
with refactoring that makes paragraph gap modification easier to
understand.
2023-09-17 13:38:40 +02:00
undergroundwires
53222fd83c Fix compiler bug with nested optional arguments
This commit fixes compiler bug where it fails when optional values are
compiled into absent values in nested calls.

- Throw exception with more context for easier future debugging.
- Add better validation of argument values for nested calls.
- Refactor `FunctionCallCompiler` for better clarity and modularize it
  to make it more maintainable and testable.
- Refactor related interface to not have `I` prefix, and
  function/variable names for better clarity.

Context:

Discovered this issue while attempting to call
`RunInlineCodeAsTrustedInstaller` which in turn invokes `RunPowerShell`
for issue #246. This led to the realization that despite parameters
flagged as optional, the nested argument compilation didn't support
them.
2023-09-16 16:11:41 +02:00
undergroundwires
a1f2497381 Fix wrong action path in website CI deployment 2023-09-15 13:36:05 +02:00
Couleur
c27172c32e win: refactor update.mode key for VSCode #215
Removed unnecessary single quotes wrapping the value `manual` in yaml.
2023-09-14 12:47:33 +02:00
undergroundwires
6e9b65d8b1 win: fix, improve disabling automatic updates #252
- Add script to disable `WaaSMedicSvc` service (#252)
- Refine script granularity for more precise control.
- Introduce detailed documentation for the category and associated
  scripts.
- Fix `ScheduledInstallTime` being set to `3` which schedules updates to
  install at 3 AM.
- Fix `ScheduledInstallDay` is being set to `0` which schedules daily
  update installation.
- Fix `NoAutoUpdate` being set to `0` (enable) instead of `1` (disable).
- Add disabling of missing `wuauserv` service.
- Add parent category for disabling Windows update services for better
  organization.
2023-09-13 13:18:14 +02:00
billy
6d301f9961 win: fix Edge telemetry disabling for v116+ #242 2023-09-12 13:28:22 +02:00
undergroundwires
659fea7afc win: fix Windows spotlight revert, docs, recommend
- Move disabling Windows Spotlight from Standard to Strict
  recommendation due to unexpected behavior for some users (#65).
- Enhance documentation.
- Correct revert code to ensure return to the default OS state.
2023-09-11 14:08:33 +02:00
undergroundwires-bot
e0303058a3 ⬆️ bump everywhere to 0.12.3 2023-09-10 11:21:25 +00:00
undergroundwires
65f121c451 Introduce new TreeView UI component
Key highlights:

- Written from scratch to cater specifically to privacy.sexy's
  needs and requirements.
- The visual look mimics the previous component with minimal changes,
  but its internal code is completely rewritten.
- Lays groundwork for future functionalities like the "expand all"
  button a flat view mode as discussed in #158.
- Facilitates the transition to Vue 3 by omitting the Vue 2.0 dependent
  `liquour-tree` as part of #230.

Improvements and features:

- Caching for quicker node queries.
- Gradual rendering of nodes that introduces a noticable boost in
  performance, particularly during search/filtering.
  - `TreeView` solely governs the check states of branch nodes.

Changes:

- Keyboard interactions now alter the background color to highlight the
  focused item. Previously, it was changing the color of the text.
- Better state management with clear separation of concerns:
  - `TreeView` exclusively manages indeterminate states.
  - `TreeView` solely governs the check states of branch nodes.
  - Introduce transaction pattern to update state in batches to minimize
    amount of events handled.
- Improve keyboard focus, style background instead of foreground. Use
  hover/touch color on keyboard focus.
- `SelectableTree` has been removed. Instead, `TreeView` is now directly
  integrated with `ScriptsTree`.
- `ScriptsTree` has been refactored to incorporate hooks for clearer
  code and separation of duties.
- Adopt Vue-idiomatic bindings instead of keeping a reference of the
  tree component.
- Simplify and change filter event management.
- Abandon global styles in favor of class-scoped styles.
- Use global mixins with descriptive names to clarify indended
  functionality.
2023-09-09 22:26:21 +02:00
undergroundwires
821cc62c4c Change license to AGPLv3 2023-09-08 16:52:41 +02:00
Snowz
4ce327eb6a win: fix disable recent apps revert #211, #248
Add missing space
2023-09-07 00:02:02 +02:00
undergroundwires
4beb1bb574 Introduce retry mechanism for npm install in CI/CD
This commit addresses occasional pipeline failures caused by transient
network errors during dependency installation with `npm ci`. It
centralizes the logic for installing npm dependencies and introduces a
retry mechanism.

The new approach will attempt `npm ci` up to 5 times with a 5-second
interval between each attempt, thereby increasing the resilience of
CI/CD pipelines.

This commit adds a new script `npm-install.js` with `npm run
install-deps` command to centralize npm dependency installation process
throughout the project. Separate testing of scripts to a separate
workflow.

It removes unused `install` dependency from `package.json`.
2023-09-05 13:39:15 +02:00
undergroundwires
0a2a1a026b Refactor build configs and improve CI/CD checks
This commit makes the build process more robust, simplifies
configurations and reduce the risk of incomplete or erroneous
deployments.

- Centralize output directory definitions by introducing
  `dist-dirs.json`.
- Add `verify-build-artifacts` utility to ensure correct build outputs
  and `print-dist-dir` to determine distribution directory.
- Add steps in CI/CD pipeline to verify build artifacts.
- Migrate Electron Builder config from YAML to CJS for capability to
  read JSON.
- Fix `release-site.yaml` failing due to pointing to wrong distribution
  directory, change it to use `print-dist-dir`.
- Improve `check-desktop-runtime-errors` to verify build artifacts for
  more reliable builds. Ensure tests fail and succeed reliably.
- Update `.gitignore` and configure ESLint to use it to define and
  ignore build artifact directories from one place, remove
  `.eslintignore` that does not add anything after this change.
- Keep `"main"` field in `package.json` as `electron-vite` depends on it
  (alex8088/electron-vite#270).
- Improve documentation
2023-09-03 14:50:31 +02:00
undergroundwires
eb096d07e2 Fix memory leaks via auto-unsubscribing and DI
This commit simplifies event handling, providing a unified and robust
way to handle event lifecycling. This way, it fixes events not being
unsubscribed when state is changed.

Introduce a new function in `EventSubscriptionCollection` to remove
existing events and adding new events. This provides an easier to use
API, which leads to code that's easier to understand. It also prevents
potential bugs that may occur due to forgetting to call both functions.
It fixes `TheScriptsMenu` not unregistering events on state change.
Other improvements include:
  - Include a getter to get total amount of registered subcriptions.
    This helps in unit testing.
  - Have nullish checks to prevent potential errors further down the
    execution.
  - Use array instead of rest parameters to increase readability and
    simplify tests.

Ensure `SliderHandler` stops resizes on unmount, unsubscribing from all
events and resetting state to default.

Update `injectionKeys` to do imports as types to avoid circular
dependencies. Simplify importing `injectionKeys` to enable and strict
typings for iterating injection keys.

Add tests covering new behavior.
2023-09-01 18:14:25 +02:00
undergroundwires
19e42c9c52 Refactor and improve external URL checks
- Move external URL checks to its own module under `tests/`. This
  separates them from integration test, addressing long runs and
  frequent failures that led to ignoring test results.
- Move `check-desktop-runtime-errors` to `tests/checks` to keep all
  test-related checks into one directory.
- Replace `ts-node` with `vite` for running
  `check-desktop-runtime-errors` to maintain a consistent execution
  environment across checks.
- Implement a timeout for each fetch call.
- Be nice to external sources, wait 5 seconds before sending another
  request to an URL under same domain. This solves rate-limiting issues.
- Instead of running test on every push/pull request, run them only
  weekly.
- Do not run tests on each commit/PR but only scheduled (weekly) to
  minimize noise.
- Fix URLs are not captured correctly inside backticks or parenthesis.
2023-09-01 00:18:47 +02:00
undergroundwires
f4d86fccfd Fix Windows artifact naming in desktop packaging
- Fix the naming convention in Electron output to align with previous
  artifact naming to not break external/internal URLs.
- In desktop execution tests, make artifact locator logic stricter to
  test regression.
2023-08-30 13:34:30 +02:00
undergroundwires
ad0576a752 Improve desktop runtime execution tests
Test improvements:

- Capture titles for all macOS windows, not just the frontmost.
- Incorporate missing application log files.
- Improve log clarity with enriched context.
- Improve application termination on macOS by reducing grace period.
- Ensure complete application termination on macOS.
- Validate Vue application loading through an initial log.
- Support ignoring environment-specific `stderr` errors.
- Do not fail the test if working directory cannot be deleted.
- Use retry pattern when installing dependencies due to network errors.

Refactorings:

- Migrate the test code to TypeScript.
- Replace deprecated `rmdir` with `rm` for error-resistant directory
  removal.
- Improve sanity checking by shifting from App.vue to Vue bootstrapper.
- Centralize environment variable management with `EnvironmentVariables`
  construct.
- Rename infrastructure/Environment to RuntimeEnvironment for clarity.
- Isolate WindowVariables and SystemOperations from RuntimeEnvironment.
- Inject logging via preloader.
- Correct mislabeled RuntimeSanity tests.

Configuration:

- Introduce `npm run check:desktop` for simplified execution.
- Omit `console.log` override due to `nodeIntegration` restrictions and
  reveal logging functionality using context-bridging.
2023-08-29 16:30:00 +02:00
tromcho
35be05df20 win: fix typo in Defender retention script #213 2023-08-28 13:15:41 +02:00
undergroundwires
dae6d114da linux: use user.js over prefs.js for Firefox #232
Manage Firefox preferences through `user.js` instead of `prefs.js`.
Because of Mozilla's recommendation against direct `prefs.js` edits to
avoid potential profile corruption. Instead, the `user.js` file, if
present, overrides the settings in `prefs.js` at application startup.

Change AddFirefoxPrefs function to update `user.js` and manage
creation/deletion of this file:

1. Handle file creation if `user.js` does not exist.
2. Deletes file if `user.js` becomes empty after reverting settings.

Other changes:

- Improve log messages
- Minimal refactorings
2023-08-27 20:14:10 +02:00
undergroundwires-bot
ecce47fdcd ⬆️ bump everywhere to 0.12.2 2023-08-25 12:46:25 +00:00
undergroundwires
e9e0001ef8 Improve desktop security by isolating Electron
Enable `contextIsolation` in Electron to securely expose a limited set
of Node.js APIs to the renderer process. It:

1. Isolates renderer and main process contexts. It ensures that the
   powerful main process functions aren't directly accessible from
   renderer process(es), adding a security boundary.
2. Mitigates remote exploitation risks. By isolating contexts, potential
   malicious code injections in the renderer can't directly reach and
   compromise the main process.
3. Reduces attack surface.
4. Protect against prototype pollution: It prevents tampering of
   JavaScript object prototypes in one context from affecting another
   context, improving app reliability and security.

Supporting changes include:

- Extract environment and system operations classes to the infrastructure
  layer. This removes node dependencies from core domain and application
  code.
- Introduce `ISystemOperations` to encapsulate OS interactions. Use it
  from `CodeRunner` to isolate node API usage.
- Add a preloader script to inject validated environment variables into
  renderer context. This keeps Electron integration details
  encapsulated.
- Add new sanity check to fail fast on issues with preloader injected
  variables.
- Improve test coverage of runtime sanity checks and environment
  components. Move validation logic into separate classes for Single
  Responsibility.
- Improve absent value test case generation.
2023-08-25 14:31:30 +02:00
undergroundwires
62f8bfac2f Fix searching/filtering bugs #235
- Fix a bug (introduced in 1b9be8fe) preventing the tree view from being
  visible during a search.
- Fix a minor bug where the scripts view does not render based on the
  initial filter.
- Add Vue component tests for `TheScriptView` to prevent regressions.
- Refactor `isSearching` in `TheScriptView` to simplify its logic.
2023-08-25 00:32:01 +02:00
undergroundwires
75c9b51bf2 Migrate to electron-vite and electron-builder
- Switch from deprecated Vue CLI plugin to `electron-vite` (see
  nklayman/vue-cli-plugin-electron-builder#1982)
- Update main/preload scripts to use `index.cjs` filenames to support
  `"type": "module"`, resolving crash issue (#233). This crash was
  related to Electron not supporting ESM (see electron/asar#249,
  electron/electron#21457).
- This commit completes migration to Vite from Vue CLI (#230).

Structure changes:

- Introduce separate folders for Electron's main and preload processes.
- Move TypeHelpers to `src/` to mark tit as accessible by the rest of
  the code.

Config changes:

- Make `vite.config.ts` reusable by Electron configuration.
- On electron-builder, use `--publish` flag instead of `-p` for clarity.

Tests:

- Add log for preload script loading verification.
- Implement runtime environment sanity checks.
- Enhance logging in `check-desktop-runtime-errors`.
2023-08-24 20:01:53 +02:00
undergroundwires
ec98d8417f Migrate Cypress (E2E) tests to Vite and TypeScript
This commit progresses the migration from Vue CLI to Vite (#230).

TypeScript migration:

- Convert JavaScript Cypress tests and configurations to TypeScript.
- Introduce `tsconfig.json` for Cypress, following official
  recommendation.

Test execution:

- Use Cypress CLI to run the tests.
- Rename Cypress commands to reflect official naming conventions.
- Start Vue server prior to Cypress execution, using
  `start-server-and-test` package based on official documentation.
- Remove dependency on Vue CLI plugin ((`@vue/cli-plugin-e2e-cypress`).

Configuration standardization (based on Cypress docs):

- Delete unused `plugins/` directory.
- Move test (spec) files to to the root directory.
- Add official ESLint plugin (`eslint-plugin-cypress`).

Changes for importing `vite.config.ts` into `cypress.config.ts`:

- Add TypeScript import assertations to files importing JSON files.
- Use ESM friendly way instead of `__dirname` to solve `ReferenceError:
  __dirname is not defined in ES module scrope`.

Other changes:

- Simplify comments in placeholder files.
- Create Cypress specific `.gitignore` for enhanced maintainability,
  clarity and scalability.
- Remove redundant `vue.config.cjs`.
2023-08-24 13:45:34 +02:00
undergroundwires
736590558b Migrate web builds from Vue CLI to Vite
This commit changes the web application's build, transpilation and
minification process from Vue CLI to Vite. This shift paves the way for
a full migration to Vite as the primary build tool (#230).

Configuration changes:

- `.vscode/extensions.json`: Update recommended plugins, replacing
  unmaintained ones with official recommendations.
- Legacy browser support:
  - Use `@vitejs/plugin-legacy` to transpile for older browsers.
  - Remove `core-js` dependency and `babel.config.cjs` configuration as
    they're now handled by the legacy plugin.
  - Delete `@babel/preset-typescript` and `@babel/preset-typescript`
    dependencies as legacy plugin handles babel dependencies by default.
  - Add `terser` dependency that's used by the legacy plugin for
    minification, as per Vite's official documentation.
- `tsconfig.json`:
  - Remove obsolete `webpack-env` types.
  - Add `"resolveJsonModule": true` to be able to read JSON files in
    right way.
  - Use correct casing as configuration values.
  - Simplify `lib` to align with Vite and Vue starter configuration.
  - Add `"skipLibCheck": true` as `npm run build` now runs `tsc` which
    fails on inconsistent typings inside `node_modules` due to npm's
    weak dependency resoultion.
- PostCSS:
  - Add `autoprefixer` as dependency, no longer installed by Vue CLI.
  - Epxlicitly added `postcss` as dependency to anticipate potential
    peer dependency changes.
- Remove related `@vue/cli` dependencies.
- Remove `sass-loader` as Vite has native CSS preprocessing support.
- Run integration tests with `jsdom` environment so `window` object can
  be used.

Client-side changes:

- Abstract build tool specific environment variable population.
  Environment variables were previously populated by Vue CLI and now by
  Vite but not having an abstraction caused issues. This abstraction
  solves build errors and allows easier future migrations and testing.
- Change Vue CLI-specific `~@` aliases to `@` to be able to compile with
  Vite.
- Update types in LiquorTree to satisfy `tsc`.
- Remove Vue CLI-specific workaround from `src/presentation/main.ts`.

Restructuring:

- Move `public/` to `presentation/` to align with the layered structure,
  which was not possible with Vue CLI.
- Move `index.html` to web root instead of having it inside `public/` to
  align with official recommended structure.
- Move logic shared by both integration and unit tests to
  `tests/shared`.
- Move logo creation script to `scripts/` and its npm command to include
  `build` to align with rest of the structure.
2023-08-23 23:12:56 +02:00
undergroundwires
6e40edd3f8 Remove Vue ESLint plugin for Vite compatibility
The Vue ESLint plugin is not compatible with Vite and isn't provided in
Vite's default template. By removing it, the codebase progresses toward
the migration to Vue 3.0 and Vite (#230).

Changes:

- Directly execute `eslint` in the `npm run lint:eslint` command.
- Fix previously undetected linting issues that weren't covered by Vue
  CLI's default configuration.
- Updated various configuration files, reflecting the removal and lint
  fixes.
- Remove unused `eslint-plugin-import` dependency that is already
  imported by `@vue/eslint-config-airbnb-with-typescript`.

In `.eslintrc.cjs`:

- Add `es2022` as environment in to simplify setting parser options and
  align with Vite starter configuration.
- Remove useless tests override.
- Move tests override in root `.eslintrc.cjs` to `tests/` for clarity,
  better organization, scalability and separation of concerns.
2023-08-23 09:47:44 +02:00
undergroundwires
5f11c8d98f Migrate unit/integration tests to Vitest with Vite
As part of transition to Vue 3.0 and Vite (#230), this commit
facilitates the shift towards building rest of the application using
Vite. By doing so, it eliminates reliance on outdated Electron building
system that offered limited control, blocking desktop builds (#233).

Changes include:

- Introduce Vite with Vue 2.0 plugin for test execution.
- Remove `mocha`, `chai` and other related dependencies.
- Adjust test to Vitest syntax.
- Revise and update `tests.md` to document the changes.
- Add `@modyfi/vite-plugin-yaml` plugin to be able to use yaml file
  depended logic on test files, replacing previous webpack behavior.
- Fix failing tests that are revealed by Vitest due to unhandled errors
  and lack of assertments.
- Remove the test that depends on Vue CLI populating `process.env`.
- Use `jsdom` for unit test environment, adding it to dependency to
  `package.json` as project now depends on it and it was not specified
  even though `package-lock.json` included it.
2023-08-22 14:02:35 +02:00
SNOWZ
08737698c2 win: fix automatic updates revert #234
Close registry key string
2023-08-21 12:15:08 +02:00
undergroundwires
04b3133500 Add automated checks for desktop app runtime #233
- Add automation script for building, packaging, installing, executing
  and verifying Electron distrubtions across macOS, Ubuntu and Windows.
- Add GitHub workflow to run the script to test distributions using the
  script.
- Update README with new workflow status badge.
- Add application initialization log to desktop applications to be able
  to test against crashes before application initialization.
2023-08-21 01:35:19 +02:00
undergroundwires-bot
0d15992d56 ⬆️ bump everywhere to 0.12.1 2023-08-18 15:53:16 +00:00
undergroundwires
a14929a13c Migrate to ES6 modules
Configure project to use ES6 modules to enable top-level await
capabilities. This change helps project to align well with modern JS
standards.

- Set `type` to `module` in `package.json`.
- Use import/export syntax in Cypress configuration files.
- Rename configurations files that do not support modules to use
  the `.cjs` extension:
  - `vue.config.js` to `vue.config.cjs` (vuejs/vue-cli#4477).
  - `babel.config.js` to `babel.config.cjs (babel/babel-loader#894)
  - `.eslintrc.js` to `.eslintrc.cjs` (eslint/eslint#13440,
    eslint/eslint#14137)
  - `postcss.config.js` to `postcss.config.cjs` (postcss/postcss#1771)
- Provide a workaround for Vue CLI & Mocha ES6 modules conflict in
  Vue configuration file (vuejs/vue-cli#7417).
2023-08-17 18:50:14 +02:00
undergroundwires
6a20d804dc Refactor filter (search query) event handling
Refactor filter event handling to a unified event with visitor pattern
to simplify the code, avoid future bugs and provide better test
coverage.

This commit shifts from using separate `filtered` and `filterRemoved`
events to a singular, more expressive `filterChanged` event. The new
approach emits a detailed payload that explicitly indicates the filter
action and the associated filter data. The event object unifies the way
the presentation layer reacts to the events.

Benefits with this approach include:

- Simplifying event listeners by reducing the number of events to
  handle.
- Increasing code clarity and reduces potential for oversight by
  providing explicit action details in the event payload.
- Offering extensibility for future actions without introducing new
  events.
- Providing visitor pattern to handle different kind of events in easy
  and robust manner without code repetition.

Other changes:

- Refactor components handling of events to follow DRY and KISS
  principles better.
- Refactor `UserFilter.spec.ts` to:
  - Make it easier to add new tests.
  - Increase code coverage by running all event-based tests on the
    current property.
2023-08-16 15:09:26 +02:00
undergroundwires
ae75059cc1 Increase testability through dependency injection
- Remove existing integration tests for hooks as they're redundant after
  this change.
- Document the pattern in relevant documentation.
- Introduce `useEnvironment` to increase testability.
- Update components to inject dependencies rather than importing hooks
  directly.
2023-08-15 18:11:30 +02:00
undergroundwires
39e650cf11 Fix revert toggle partial rendering
This commits fixes an issue where the `REVERT` label on revert toggle
might render as `REVER` or in a similarly clipped manner due to its
fixed width. The problem is visible when certain fonts fail to load or
browser engines render content non-standardly.

Changes:
- Refactor UI component to have its own separate Vue component with unit
  tests.
- Rework component design to utilize flexbox, enhancing its adaptability
  and simplifying the structure.
- Remove obselete `webkit` directives.
- Refactor SCSS for clearer structure and better SCSS best-practices.
- Use `em` when possible instead of `px` for improved responsiveness.
2023-08-14 15:28:15 +02:00
undergroundwires
bc91237d7c Refactor usage of tooltips for flexibility
This commit introduces a new Vue component to handle tooltips. It acts
as a wrapper for the `v-tooltip`. It enhances the maintainability,
readability and portability of tooltips by enabling the direct inclusion
of inline HTML in the tooltip components. It solves issues such as
absence of linting or editor support and cumbersome string
concatenation.

It also provides an abstraction layer that simplifies the switching
between different tooltip implementations, which would allow a smooth
migration to Vue 3 (see #230).
2023-08-12 16:53:58 +02:00
undergroundwires
9e5491fdbf Implement custom lightweight modal #230
Introduce a brand new lightweight and efficient modal component. It is
designed to be visually similar to the previous one to not introduce a
change in feel of the application in a patch release, but behind the
scenes it features:

- Enhanced application speed and reduced bundle size.
- New flexbox-driven layout, eliminating JS calculations.
- Composition API ready for Vue 3.0 #230.

Other changes:

- Adopt idiomatic Vue via `v-modal` binding.
- Add unit tests for both the modal and dialog.
- Remove `vue-js-modal` dependency in favor of the new implementation.
- Adjust modal shadow color to better match theme.
- Add `@vue/test-utils` for unit testing.
2023-08-11 19:35:26 +02:00
undergroundwires
986ba078a6 Fix failing tests due to failed error logging
Unit and integration tests have been failing due to failed logging of
`Error` objects. These were creating an issue where `mocha` was not
properly returning right exit codes, leading to test pipelines
incorrectly passing despite test failures.

- Fix runtime behavior of failing to retrieve error stacks.
- Add tests for error handling.
- Add more robust custom error handling.

Related issues: babel/babel#14273, vuejs/vue-cli#6994.
2023-08-10 19:49:08 +02:00
he3als
061afad967 Improve Defender scripts #201
Reliably disable Defender services (by always using `reg` with
TrustedInstaller`), and put Firewall services in Windows Firewall
section, so that people do not accidently disable Windows Firewall
services and break Microsoft Store.

Co-authored-by: undergroundwires <git@undergroundwires.dev>
2023-08-09 15:16:31 +02:00
undergroundwires
3bc8da4cbf Fix failing security tests
This commit changes the behavior of auditing to audit only production
dependencies.

Security checks have been failing for months due to Vue CLI dependencies
and lack of resolution from the developers. This commit makes auditing
ignore development dependencies.

The reasons include:

- Vulnerabilities in developer dependencies cause pipelines to fail
  on every run.
- This is caused by dependencies such that lack resolution from the
  developers. Vue developers consider `npm audit` broken design and do
  not prioritize solutions. Discussions: vuejs/vue-cli#6637,
  vuejs/vue-cli#6621, vuejs/vue-cli#6555, vuejs/vue-cli#6553,
  vuejs/vue-cli#6523, vuejs/vue-cli#6486, vuejs/vue-cli#6632.
- Development packages are not relevant for the production payload.
- False positives create behavior of ignoring them completely instead of
  taking action, which creates a security vulnerability itself.
- Failed tests are shown in a badge on README file, giving wrong picture
  of security posture of users.

`npm audit --omit=dev` is used instead of `npm audit --production` which
is deprecated as of npm v8.7.0 npm/cli#4744.

This commit also removes exiting with output of `npm audit` command to
fix exiting with textual output, leading to failures.
2023-08-08 20:02:09 +02:00
undergroundwires
1b9be8fe2d Refactor Vue components using Composition API #230
- Migrate `StatefulVue`:
  - Introduce `UseCollectionState` that replaces its behavior and acts
    as a shared state store.
  - Add more encapsulated, granular functions based on read or write
    access to state in CollectionState.
- Some linting rules get activates due to new code-base compability to
  modern parses, fix linting errors.
  - Rename Dialog to ModalDialog as after refactoring,
    eslintvue/no-reserved-component-names does not allow name Dialog.
  - To comply with `vue/multi-word-component-names`, rename:
    - `Code`          -> `CodeInstruction`
    - `Handle`        -> `SliderHandle`
    - `Documentable`  -> `DocumentableNode`
    - `Node`          -> `NodeContent`
    - `INode`         -> `INodeContent`
    - `Responsive`    -> `SizeObserver`
- Remove `vue-property-decorator` and `vue-class-component`
  dependencies.
- Refactor `watch` with computed properties when possible for cleaner
  code.
  - Introduce `UseApplication` to reduce repeated code in new components
    that use `computed` more heavily than before.
- Change TypeScript target to `es2017` to allow top level async calls
  for getting application context/state/instance to simplify the code by
  removing async calls. However, mocha (unit and integration) tests do
  not run with top level awaits, so a workaround is used.
2023-08-07 13:16:39 +02:00
undergroundwires
3a594ac7fd Improve user privacy with secure outbound links
All outbound links now include `rel="noopener noreferrer"` attribute.
This security improvement prevents the new page from being able to
access the `window.opener` property and ensures it runs in a separate
process.

`rel="noopener"`:

   When a new page is opened using `target="_blank"`, the new page runs
   on the same process as the originating page, and has a reference to
   the originating page `window.opener`. By implementing
   `rel="noopener"`, the new page is prevented to use `window.opener`
   property.
   It's security issue because the newly opened website could
   potentially redirect the page to a malicious URL. Even though
   privacy.sexy doesn't have any sensitive information to protect, this
   can still be a vector for phishing attacks.

`rel="noreferrer"`:

  It implies features of `noopener`, and also prevents `Referer` header
  from being sent to the new page. Referer headers may include
  sensitive data, because they tell the new page the URL of the page
  the request is coming from.
2023-08-06 02:09:11 +02:00
619 changed files with 40850 additions and 32044 deletions

View File

@@ -5,3 +5,7 @@ end_of_line = lf
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
max_line_length = 100 max_line_length = 100
[{Dockerfile}]
indent_style = space
indent_size = 4

View File

@@ -1 +0,0 @@
dist/

View File

@@ -6,10 +6,10 @@ module.exports = {
root: true, root: true,
env: { env: {
node: true, node: true,
es2022: true, // add globals and sets parserOptions.ecmaVersion to 2022
}, },
extends: [ extends: [
// Vue specific rules, eslint-plugin-vue // Vue specific rules, eslint-plugin-vue
// Added by Vue CLI
'plugin:vue/essential', 'plugin:vue/essential',
// Extends eslint-config-airbnb // Extends eslint-config-airbnb
@@ -17,42 +17,14 @@ module.exports = {
// Extends @typescript-eslint/recommended // Extends @typescript-eslint/recommended
// Uses the recommended rules from the @typescript-eslint/eslint-plugin // Uses the recommended rules from the @typescript-eslint/eslint-plugin
// Added by Vue CLI
'@vue/typescript/recommended', '@vue/typescript/recommended',
], ],
parserOptions: {
ecmaVersion: 12, // ECMA 2021
/*
Having 'latest' leads to:
```
Parsing error: ecmaVersion must be a number. Received value of type string instead
```
For .js files in the project
*/
},
rules: { rules: {
...getOwnRules(), ...getOwnRules(),
...getTurnedOffBrokenRules(), ...getTurnedOffBrokenRules(),
...getOpinionatedRuleOverrides(), ...getOpinionatedRuleOverrides(),
...getTodoRules(), ...getTodoRules(),
}, },
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)',
],
env: {
mocha: true,
},
},
{
files: ['**/tests/**/*.{j,t}s?(x)'],
rules: {
'no-console': 'off',
},
},
],
}; };
function getOwnRules() { function getOwnRules() {

View File

@@ -0,0 +1,12 @@
inputs:
working-directory:
required: false
default: '.'
runs:
using: composite
steps:
-
name: Run `npm ci` with retries
shell: bash
run: npm run install-deps -- --ci
working-directory: ${{ inputs.working-directory }}

View File

@@ -3,6 +3,6 @@ runs:
steps: steps:
- -
name: Setup node name: Setup node
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: 16.x node-version: 16.x

View File

@@ -1,4 +1,4 @@
name: build-checks name: checks.build
on: on:
push: push:
@@ -9,67 +9,92 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ macos, ubuntu, windows ] os: [ macos, ubuntu, windows ]
mode: [ development, test, production ] mode: [
# Vite mode: https://vitejs.dev/guide/env-and-mode.html
development, # Used by `dev` command
production, # Used by `build` command
# Vitest mode: https://vitest.dev/guide/cli.html
test, # Used by Vitest
]
fail-fast: false # Allows to see results from other combinations fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- -
name: Install dependencies name: Install dependencies
run: npm ci uses: ./.github/actions/npm-install-dependencies
- -
name: Build name: Build web
run: npm run build -- --mode ${{ matrix.mode }} run: npm run build -- --mode ${{ matrix.mode }}
-
name: Verify web build artifacts
run: npm run check:verify-build-artifacts -- --web
# A new job is used due to environments/modes different from Vue CLI, https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1626
build-desktop: build-desktop:
strategy: strategy:
matrix: matrix:
os: [ macos, ubuntu, windows ] os: [ macos, ubuntu, windows ]
mode: [ development, production ] # "test" is not supported https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1627 mode: [
# electron-vite modes: https://electron-vite.org/guide/env-and-mode.html#global-env-variables
development, # Used by `dev` command
production, # Used by `build` and `preview` commands
]
fail-fast: false # Allows to see results from other combinations fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- -
name: Install dependencies name: Install dependencies
run: npm ci uses: ./.github/actions/npm-install-dependencies
- -
name: Install cross-env name: Prebuild desktop
# Used to set NODE_ENV due to https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1626 run: npm run electron:prebuild -- --mode ${{ matrix.mode }}
run: npm install --global cross-env
- -
name: Build name: Verify unbundled desktop build artifacts
run: |- run: npm run check:verify-build-artifacts -- --electron-unbundled
cross-env-shell NODE_ENV=${{ matrix.mode }} -
npm run electron:build -- --publish never --mode ${{ matrix.mode }} name: Build (bundle and package) desktop application
run: npm run electron:build -- --publish never
-
name: Verify bundled desktop build artifacts
run: npm run check:verify-build-artifacts -- --electron-bundled
create-icons: build-docker:
strategy: strategy:
matrix: matrix:
os: [ macos, ubuntu, windows ] os: [ macos, ubuntu ] # Windows runners do not support Linux containers
fail-fast: false # Allows to see results from other combinations fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Install Docker on macOS
uses: ./.github/actions/setup-node if: matrix.os == 'macos' # macOS runner is missing Docker
run: |-
# Install Docker
brew install docker
# Docker on macOS misses daemon due to licensing, so install colima as runtime
brew install colima
# Start the daemon
colima start
- -
name: Install dependencies name: Build Docker image
run: npm ci run: docker build -t undergroundwires/privacy.sexy:latest .
- -
name: Create icons name: Run Docker image on port 8080
run: npm run create-icons run: docker run -d -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest
-
name: Check server is up and returns HTTP 200
run: node ./scripts/verify-web-server-status.js --url http://localhost:8080

View File

@@ -0,0 +1,72 @@
name: checks.desktop-runtime-errors
# Verifies desktop builds for Electron applications across multiple OS platforms (macOS ,Ubuntu, and Windows).
on:
push:
pull_request:
jobs:
run-check:
strategy:
matrix:
os: [ macos, ubuntu, windows ]
fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Setup node
uses: ./.github/actions/setup-node
-
name: Install dependencies
uses: ./.github/actions/npm-install-dependencies
-
name: Configure Ubuntu
if: matrix.os == 'ubuntu'
shell: bash
run: |-
sudo apt update
# Configure AppImage dependencies
sudo apt install -y libfuse2
# Configure DBUS (fixes `Failed to connect to the bus: Could not parse server address: Unknown address type`)
if ! command -v 'dbus-launch' &> /dev/null; then
echo 'DBUS does not exist, installing...'
sudo apt install -y dbus-x11 # Gives both dbus and dbus-launch utility
fi
sudo systemctl start dbus
DBUS_LAUNCH_OUTPUT=$(dbus-launch)
if [ $? -eq 0 ]; then
echo "${DBUS_LAUNCH_OUTPUT}" >> $GITHUB_ENV
else
echo 'Error: dbus-launch command did not execute successfully. Exiting.' >&2
echo "${DBUS_LAUNCH_OUTPUT}" >&2
exit 1
fi
# Configure fake (virtual) display
sudo apt install -y xvfb
sudo Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
echo "DISPLAY=:99" >> $GITHUB_ENV
# Install ImageMagick for screenshots
sudo apt install -y imagemagick
# Install xdotool and xprop (from x11-utils) for window title capturing
sudo apt install -y xdotool x11-utils
-
name: Test
shell: bash
run: |-
export SCREENSHOT=true
npm run check:desktop
-
name: Upload screenshot
if: always() # Run even if previous step fails
uses: actions/upload-artifact@v3
with:
name: screenshot-${{ matrix.os }}
path: screenshot.png

View 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@v4
-
name: Setup node
uses: ./.github/actions/setup-node
-
name: Install dependencies
uses: ./.github/actions/npm-install-dependencies
-
name: Test
run: npm run check:external-urls

View File

@@ -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 -
uses: actions/checkout@v2 name: Checkout
- name: Setup node uses: actions/checkout@v4
-
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
View 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@v4
-
name: Setup node
uses: ./.github/actions/setup-node
-
name: Install dependencies
uses: ./.github/actions/npm-install-dependencies
-
name: Create icons
run: npm run icons:build
install-deps:
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
install-deps-before: [true, false]
install-command:
- npm run install-deps
- npm run install-deps -- --no-errors
- npm run install-deps -- --ci
- npm run install-deps -- --fresh --non-deterministic
- npm run install-deps -- --fresh
- npm run install-deps -- --non-deterministic
os: [ macos, ubuntu, windows ]
fail-fast: false # Still interested to see results from other combinations
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Setup node
uses: ./.github/actions/setup-node
-
name: Install dependencies
if: matrix.install-deps-before == true
uses: ./.github/actions/npm-install-dependencies
-
name: Run install-deps
run: ${{ matrix.install-command }}

View File

@@ -1,4 +1,4 @@
name: security-checks name: checks.security.dependencies
on: on:
push: push:
@@ -13,10 +13,10 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- -
name: NPM audit name: NPM audit
run: exit "$(npm audit)" # Since node 15.x, it does not fail with error if we don't explicitly exit run: npm audit --omit=dev

View File

@@ -0,0 +1,42 @@
name: checks.security.sast
on:
push:
pull_request:
schedule:
- cron: '0 0 * * 0' # at 00:00 on every Sunday
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [
javascript # analyzes code written in JavaScript, TypeScript and both.
]
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
-
name: Autobuild
uses: github/codeql-action/autobuild@v2
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"

View File

@@ -13,20 +13,29 @@ jobs:
fail-fast: false # So publish runs for other OSes if one fails fail-fast: false # So publish runs for other OSes if one fails
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- uses: actions/checkout@v2 -
uses: actions/checkout@v4
with: with:
ref: master # otherwise it defaults to the version tag missing bump commit ref: master # otherwise it defaults to the version tag missing bump commit
fetch-depth: 0 # fetch all history fetch-depth: 0 # fetch all history
- name: Checkout to bump commit -
name: Checkout to bump commit
run: git checkout "$(git rev-list "${{ github.event.release.tag_name }}"..master | tail -1)" run: git checkout "$(git rev-list "${{ github.event.release.tag_name }}"..master | tail -1)"
- name: Setup node -
name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- name: Install dependencies -
run: npm ci name: Install dependencies
- name: Run unit tests uses: ./.github/actions/npm-install-dependencies
-
name: Run unit tests
run: npm run test:unit run: npm run test:unit
- name: Publish desktop app -
run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github name: Prebuild
run: npm run electron:prebuild
-
name: Build and publish
run: npm run electron:build -- --publish always
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EP_GH_IGNORE_TIME: true # Otherwise publishing fails if GitHub release is more than 2 hours old https://github.com/electron-userland/electron-builder/issues/2074 EP_GH_IGNORE_TIME: true # Otherwise publishing fails if GitHub release is more than 2 hours old https://github.com/electron-userland/electron-builder/issues/2074

View File

@@ -10,7 +10,7 @@ jobs:
steps: steps:
- -
name: "Infrastructure: Checkout" name: "Infrastructure: Checkout"
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: aws path: aws
repository: undergroundwires/aws-static-site-with-cd repository: undergroundwires/aws-static-site-with-cd
@@ -75,7 +75,7 @@ jobs:
working-directory: aws working-directory: aws
- -
name: "App: Checkout" name: "App: Checkout"
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: app path: app
ref: master # otherwise we don't get version bump commit ref: master # otherwise we don't get version bump commit
@@ -84,8 +84,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"
run: >- shell: bash
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}} \

View File

@@ -14,13 +14,13 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- -
name: Install dependencies name: Install dependencies
run: npm ci uses: ./.github/actions/npm-install-dependencies
- -
name: Run e2e tests name: Run e2e tests
run: npm run test:e2e -- --headless run: npm run test:cy:run

View File

@@ -16,13 +16,13 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- -
name: Install dependencies name: Install dependencies
run: npm ci uses: ./.github/actions/npm-install-dependencies
- -
name: Run integration tests name: Run integration tests
run: npm run test:integration run: npm run test:integration

View File

@@ -14,13 +14,13 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Set-up node name: Set-up node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
- -
name: Install dependencies name: Install dependencies
run: npm ci uses: ./.github/actions/npm-install-dependencies
- -
name: Run unit tests name: Run unit tests
run: npm run test:unit run: npm run test:unit

7
.gitignore vendored
View File

@@ -1,10 +1,5 @@
node_modules node_modules
dist/ /dist-*/
.vs .vs
.vscode/**/* .vscode/**/*
!.vscode/extensions.json !.vscode/extensions.json
#Electron-builder output
/dist_electron
# Cypress
/tests/e2e/screenshots
/tests/e2e/videos

View File

@@ -11,8 +11,8 @@
"dbaeumer.vscode-eslint", // Lints JavaScript/TypeScript. "dbaeumer.vscode-eslint", // Lints JavaScript/TypeScript.
"pmneo.tsimporter", // Provides better auto-complete for TypeScripts imports. "pmneo.tsimporter", // Provides better auto-complete for TypeScripts imports.
// Vue // Vue
"jcbuisson.vue", // Highlights syntax. "Vue.volar", // Official Vue extensions
"octref.vetur", // Adds Vetur, Vue tooling support. "Vue.vscode-typescript-vue-plugin", // Official TypeScript Vue Plugin
// Scripting // Scripting
"timonwong.shellcheck", // Lints bash files. "timonwong.shellcheck", // Lints bash files.
"ms-vscode.powershell", // Lints PowerShell files. "ms-vscode.powershell", // Lints PowerShell files.

View File

@@ -1,5 +1,92 @@
# Changelog # Changelog
## 0.12.5 (2023-10-13)
* Fix Docker build and improve checks #220 | [7669985](https://github.com/undergroundwires/privacy.sexy/commit/7669985f8e1446e726a95626ecf35b3ce6b60a16)
* Add SAST security checks with SECURITY.md #178 | [3e5239f](https://github.com/undergroundwires/privacy.sexy/commit/3e5239f7d35e57749c01adf3dbbcd365aebb39c8)
* Add Scoop download instructions #174 | [cf55ca9](https://github.com/undergroundwires/privacy.sexy/commit/cf55ca9e28b064fa7a516077a9da23e3a8e3f534)
* win: fix and improve temp dir cleanup #176, #89 | [d457504](https://github.com/undergroundwires/privacy.sexy/commit/d45750428cca010daf2721b33a8ae3a01b28813b)
* win, linux: improve VSCode setting robustness #196 | [e8a52f7](https://github.com/undergroundwires/privacy.sexy/commit/e8a52f717dc799b34ceeb1c27c2b8219391dff6a)
* linux: fix obsolete Firefox DPI script #239 | [e5f6edf](https://github.com/undergroundwires/privacy.sexy/commit/e5f6edf405bcec7c29ea4d7932d1910620fa15f8)
* win: add removal of Edge assocations #64 | [888c916](https://github.com/undergroundwires/privacy.sexy/commit/888c9166fc66a2094137fa8be739cc21bafef5f6)
* win: improve Edge & OneDrive shortcut removal #73 | [8501495](https://github.com/undergroundwires/privacy.sexy/commit/8501495c170af61913288a63dbd369db5bbc5003)
* win: relocate and document SecHealthUI #190 | [2862951](https://github.com/undergroundwires/privacy.sexy/commit/286295128d0179358e0c6b7b6415d752175a1aed)
* Add developer toolkit UI component | [2147eae](https://github.com/undergroundwires/privacy.sexy/commit/2147eae687b82d05bc43bb4605d9068f148bb92a)
* win: fix and improve network data usage reset #265 | [5e359c2](https://github.com/undergroundwires/privacy.sexy/commit/5e359c2fb82a08e6acf7159b70ca86a8234b359b)
* win: improve app reversion and docs #260 | [a3f11df](https://github.com/undergroundwires/privacy.sexy/commit/a3f11dff187c821a00910c20dac05e285cda9073)
* Fix working directory in CI/CD web release | [698b570](https://github.com/undergroundwires/privacy.sexy/commit/698b570ee6e300d6703015464f4345b5e706f1cb)
* Implement new UI component for icons #230 | [48730bc](https://github.com/undergroundwires/privacy.sexy/commit/48730bca0506120bca4bf3a23545d59f2b1a9009)
* win: fix and improve AppCompat disabling #255 | [bab6316](https://github.com/undergroundwires/privacy.sexy/commit/bab6316e7625230cf4a4cf67c3aca417347db75c)
* win, linux, mac: fix typos and improve naming | [67c3677](https://github.com/undergroundwires/privacy.sexy/commit/67c3677621b201525a813e8a26f07d607176e89b)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.4...0.12.5)
## 0.12.4 (2023-09-25)
* win: fix Windows spotlight revert, docs, recommend | [659fea7](https://github.com/undergroundwires/privacy.sexy/commit/659fea7afcabcd0ea273cfdcc8c4bae190c126f3)
* win: fix Edge telemetry disabling for v116+ #242 | [6d301f9](https://github.com/undergroundwires/privacy.sexy/commit/6d301f99616ed49975876803d0098eafe4d3cb2e)
* win: fix, improve disabling automatic updates #252 | [6e9b65d](https://github.com/undergroundwires/privacy.sexy/commit/6e9b65d8b1b481c1471dde90876c37838b4ac4e5)
* win: refactor `update.mode` key for VSCode #215 | [c27172c](https://github.com/undergroundwires/privacy.sexy/commit/c27172c32e7c316b7cb0f44cab611eed89ca034e)
* Fix wrong action path in website CI deployment | [a1f2497](https://github.com/undergroundwires/privacy.sexy/commit/a1f24973813ccbdd7e1f06c64e1912a991a6bb64)
* Fix compiler bug with nested optional arguments | [53222fd](https://github.com/undergroundwires/privacy.sexy/commit/53222fd83c2846089746a217482195806f960d18)
* Fix no spacing after lists in documentation text | [f810ed0](https://github.com/undergroundwires/privacy.sexy/commit/f810ed0c147c2a46cae3b70b635ed81128646fff)
* Rewrite tooltip UI for efficiency and Vue 3.0 #230 | [8b930fc](https://github.com/undergroundwires/privacy.sexy/commit/8b930fc57c8ee6691ed6165bcb27d97e64a1a0c0)
* win: fix uninstallation of newer Edge #236 | [60dde11](https://github.com/undergroundwires/privacy.sexy/commit/60dde11311a2409537f5965f370b0daaaec53339)
* win: fix delivery optimization side-effects #173 | [203daeb](https://github.com/undergroundwires/privacy.sexy/commit/203daeb4a2fca0a0295cbc2a736394f9f87725e6)
* win: fix Defender scan artifacts removal #246 | [cb21a97](https://github.com/undergroundwires/privacy.sexy/commit/cb21a970b6b867e1476a5eb8a72b9a7fdd53a744)
* Fix outdated and broken links in README #161 | [0303ef2](https://github.com/undergroundwires/privacy.sexy/commit/0303ef2fd98b36306523e2a0c5f5ae812a4c6c99)
* Fix loss of tree node state when switching views | [8f188ac](https://github.com/undergroundwires/privacy.sexy/commit/8f188acd3c2d93e40c89569c74bc5cff992f0052)
* Fix slow appearance of nodes on tree view | [bd2082e](https://github.com/undergroundwires/privacy.sexy/commit/bd2082e8c574db065bb4462f30ea3ace2cb028cb)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.3...0.12.4)
## 0.12.3 (2023-09-09)
* linux: use user.js over prefs.js for Firefox #232 | [dae6d11](https://github.com/undergroundwires/privacy.sexy/commit/dae6d114daab6857d773071211eb57619b136281)
* win: fix typo in Defender retention script #213 | [35be05d](https://github.com/undergroundwires/privacy.sexy/commit/35be05df2094ea8bba4ee4725e6fa4956a79493d)
* Improve desktop runtime execution tests | [ad0576a](https://github.com/undergroundwires/privacy.sexy/commit/ad0576a752f8fd6ea2f917a59173fe61f9951246)
* Fix Windows artifact naming in desktop packaging | [f4d86fc](https://github.com/undergroundwires/privacy.sexy/commit/f4d86fccfd0e73e94c8c6e400a33514900bc5abe)
* Refactor and improve external URL checks | [19e42c9](https://github.com/undergroundwires/privacy.sexy/commit/19e42c9c52a18c813ded4265e687e01032cdd4c8)
* Fix memory leaks via auto-unsubscribing and DI | [eb096d0](https://github.com/undergroundwires/privacy.sexy/commit/eb096d07e276e1b4c8040220c47f186d02841e14)
* Refactor build configs and improve CI/CD checks | [0a2a1a0](https://github.com/undergroundwires/privacy.sexy/commit/0a2a1a026b0efb29624be82b06536c518c1ea439)
* Introduce retry mechanism for npm install in CI/CD | [4beb1bb](https://github.com/undergroundwires/privacy.sexy/commit/4beb1bb5748a60886210187ca3cdc7f4b41067c0)
* win: fix disable recent apps revert #211, #248 | [4ce327e](https://github.com/undergroundwires/privacy.sexy/commit/4ce327eb6af542ed2916d649553e5e1ba5833882)
* Change license to AGPLv3 | [821cc62](https://github.com/undergroundwires/privacy.sexy/commit/821cc62c4c8347cb76d041f82f574754e4d948c5)
* Introduce new TreeView UI component | [65f121c](https://github.com/undergroundwires/privacy.sexy/commit/65f121c451af87315e1c91df4198562e0445b2c2)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.2...0.12.3)
## 0.12.2 (2023-08-25)
* Add automated checks for desktop app runtime #233 | [04b3133](https://github.com/undergroundwires/privacy.sexy/commit/04b3133500485d0d278a81a177a1677134131405)
* win: fix automatic updates revert #234 | [0873769](https://github.com/undergroundwires/privacy.sexy/commit/08737698c2283bdf535d1611a730031ebfc7c0df)
* Migrate unit/integration tests to Vitest with Vite | [5f11c8d](https://github.com/undergroundwires/privacy.sexy/commit/5f11c8d98f782dd7c77f27649a1685fb7bd06e13)
* Remove Vue ESLint plugin for Vite compatibility | [6e40edd](https://github.com/undergroundwires/privacy.sexy/commit/6e40edd3f8a063c1b7482c27d8368e14c2fbcfbf)
* Migrate web builds from Vue CLI to Vite | [7365905](https://github.com/undergroundwires/privacy.sexy/commit/736590558be51a09435bb87e78b6655e8533bc2e)
* Migrate Cypress (E2E) tests to Vite and TypeScript | [ec98d84](https://github.com/undergroundwires/privacy.sexy/commit/ec98d8417f779fa818ccdda6bb90f521e1738002)
* Migrate to `electron-vite` and `electron-builder` | [75c9b51](https://github.com/undergroundwires/privacy.sexy/commit/75c9b51bf2d1dc7269adfd7b5ed71acfb5031299)
* Fix searching/filtering bugs #235 | [62f8bfa](https://github.com/undergroundwires/privacy.sexy/commit/62f8bfac2f481c93598fe19a51594769f522d684)
* Improve desktop security by isolating Electron | [e9e0001](https://github.com/undergroundwires/privacy.sexy/commit/e9e0001ef845fa6935c59a4e20a89aac9e71756a)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.1...0.12.2)
## 0.12.1 (2023-08-17)
* Transition to eslint-config-airbnb-with-typescript | [ff84f56](https://github.com/undergroundwires/privacy.sexy/commit/ff84f5676e496dd7ec5b3599e34ec9627d181ea2)
* Improve user privacy with secure outbound links | [3a594ac](https://github.com/undergroundwires/privacy.sexy/commit/3a594ac7fd708dc1e98155ffb9b21acd4e1fcf2d)
* Refactor Vue components using Composition API #230 | [1b9be8f](https://github.com/undergroundwires/privacy.sexy/commit/1b9be8fe2d72d8fb5cf1fed6dcc0b9777171aa98)
* Fix failing security tests | [3bc8da4](https://github.com/undergroundwires/privacy.sexy/commit/3bc8da4cbf1e2bd758dc3fffe4b1e62dc3beb7b3)
* Improve Defender scripts #201 | [061afad](https://github.com/undergroundwires/privacy.sexy/commit/061afad9673a41454c2421c318898f2b4f4cf504)
* Fix failing tests due to failed error logging | [986ba07](https://github.com/undergroundwires/privacy.sexy/commit/986ba078a643de6acbee50fff9cf77494ca7ea7f)
* Implement custom lightweight modal #230 | [9e5491f](https://github.com/undergroundwires/privacy.sexy/commit/9e5491fdbf2d9d40d974f5ad0e879a6d5c6d1e55)
* Refactor usage of tooltips for flexibility | [bc91237](https://github.com/undergroundwires/privacy.sexy/commit/bc91237d7c54bdcd15c5c39a55def50d172bb659)
* Fix revert toggle partial rendering | [39e650c](https://github.com/undergroundwires/privacy.sexy/commit/39e650cf110bee6b1b21d9b2902b36b0e2568d54)
* Increase testability through dependency injection | [ae75059](https://github.com/undergroundwires/privacy.sexy/commit/ae75059cc14db41f55dd2056f528442c7d319dd2)
* Refactor filter (search query) event handling | [6a20d80](https://github.com/undergroundwires/privacy.sexy/commit/6a20d804dc365d22c1248d787f9912271f508eeb)
* Migrate to ES6 modules | [a14929a](https://github.com/undergroundwires/privacy.sexy/commit/a14929a13cc6260b514692d9b4f1cdf5fb85d8b2)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.0...0.12.1)
## 0.12.0 (2023-08-03) ## 0.12.0 (2023-08-03)
* Improve script/category name validation | [b210aad](https://github.com/undergroundwires/privacy.sexy/commit/b210aaddf26629179f77fe19f62f65d8a0ca2b87) * Improve script/category name validation | [b210aad](https://github.com/undergroundwires/privacy.sexy/commit/b210aaddf26629179f77fe19f62f65d8a0ca2b87)

View File

@@ -43,6 +43,7 @@ You have two alternatives:
1. [Create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) and ask for someone else to add the script for you. 1. [Create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) and ask for someone else to add the script for you.
2. Or send a PR yourself. This would make it faster to get your code into the project. You need to add scripts to related OS in [collections](src/application/collections/) folder. Then you'd sent a pull request, see [pull request process](#pull-request-process). 2. Or send a PR yourself. This would make it faster to get your code into the project. You need to add scripts to related OS in [collections](src/application/collections/) folder. Then you'd sent a pull request, see [pull request process](#pull-request-process).
- 💡 You should use existing shared functions for most of the operations, like `DisableService` for disabling services, to maintain code consistency and efficiency.
- 📖 If you're unsure about the syntax, check [collection-files.md](docs/collection-files.md). - 📖 If you're unsure about the syntax, check [collection-files.md](docs/collection-files.md).
- 📖 If you wish to use templates, use [templating.md](./docs/templating.md). - 📖 If you wish to use templates, use [templating.md](./docs/templating.md).

View File

@@ -1,13 +1,16 @@
# Build # Build
FROM node:lts-alpine as build-stage FROM node:lts-alpine AS build-stage
WORKDIR /app WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . . COPY . .
RUN npm run build RUN npm run install-deps
RUN npm run build \
&& npm run check:verify-build-artifacts -- --web
RUN mkdir /dist \
&& dist_directory=$(node 'scripts/print-dist-dir.js' --web) \
&& cp -a "${dist_directory}/." '/dist'
# Production stage # Production stage
FROM nginx:stable-alpine as production-stage FROM nginx:stable-alpine AS production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html COPY --from=build-stage /dist /usr/share/nginx/html
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]

141
LICENSE
View File

@@ -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>.

View File

@@ -4,27 +4,19 @@
<!-- markdownlint-disable MD033 --> <!-- markdownlint-disable MD033 -->
<p align="center"> <p align="center">
<a href="https://undergroundwires.dev/donate?project=privacy.sexy"> <a href="https://undergroundwires.dev/donate?project=privacy.sexy" target="_blank" rel="noopener noreferrer">
<img <img
alt="donation badge" alt="donation badge"
src="https://undergroundwires.dev/img/badges/donate/flat.svg" src="https://undergroundwires.dev/img/badges/donate/flat.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md"> <a href="https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">
<img <img
alt="contributions are welcome" alt="contributions are welcome"
src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat" src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"
/> />
</a> </a>
<!-- Code quality --> <a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability" target="_blank" rel="noopener noreferrer">
<br />
<a href="https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript">
<img
alt="Language grade: JavaScript/TypeScript"
src="https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18"
/>
</a>
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability">
<img <img
alt="Maintainability" alt="Maintainability"
src="https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability" src="https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability"
@@ -32,59 +24,85 @@
</a> </a>
<!-- Tests --> <!-- Tests -->
<br /> <br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.unit.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.unit.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Unit tests status" alt="Unit tests status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/unit-tests/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/unit-tests/badge.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.integration.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.integration.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Integration tests status" alt="Integration tests status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/integration-tests/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/integration-tests/badge.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.e2e.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.e2e.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="E2E tests status" alt="E2E tests status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg"
/> />
</a> </a>
<!-- Security checks -->
<br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.sast.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Status of dependency security checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.sast/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.dependencies.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Status of Static Analysis Security Testing (SAST)"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.dependencies/badge.svg"
/>
</a>
<!-- Checks --> <!-- Checks -->
<br /> <br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Quality checks status" alt="Quality checks status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Security checks status" alt="Status of build checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/security-checks/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.build/badge.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.desktop-runtime-errors.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Build checks status" alt="Status of runtime error checks for the desktop application"
src="https://github.com/undergroundwires/privacy.sexy/workflows/build-checks/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.desktop-runtime-errors/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.scripts.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Status of script checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.scripts/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.external-urls.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Status of external URL checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.external-urls/badge.svg"
/> />
</a> </a>
<!-- Release --> <!-- Release -->
<br /> <br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Git release status" alt="Git release status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-git/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/release-git/badge.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.site.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.site.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Site release status" alt="Site release status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-site/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/release-site/badge.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.desktop.yaml"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.desktop.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Desktop application release status" alt="Desktop application release status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-desktop/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/release-desktop/badge.svg"
@@ -92,7 +110,7 @@
</a> </a>
<!-- Others --> <!-- Others -->
<br /> <br />
<a href="https://github.com/undergroundwires/bump-everywhere"> <a href="https://github.com/undergroundwires/bump-everywhere" target="_blank" rel="noopener noreferrer">
<img <img
alt="Auto-versioned by bump-everywhere" alt="Auto-versioned by bump-everywhere"
src="https://github.com/undergroundwires/bump-everywhere/blob/master/badge.svg?raw=true" src="https://github.com/undergroundwires/bump-everywhere/blob/master/badge.svg?raw=true"
@@ -104,7 +122,7 @@
## Get started ## Get started
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy). - 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
- 🖥️ **Offline**: Check [releases page](https://github.com/undergroundwires/privacy.sexy/releases), or download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-Setup-0.11.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.dmg), [Linux](https://github.com/undergroundwires/pr.vacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.AppImage). - 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.5/privacy.sexy-Setup-0.12.5.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.5/privacy.sexy-0.12.5.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.5/privacy.sexy-0.12.5.AppImage). For more options, see [here](#additional-install-options).
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly. Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
@@ -132,6 +150,16 @@ Online version does not require to run any software on your computer. Offline ve
**Contribute 👷**. Contributions of any type are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) as the starting point. It includes useful information like [how to add new scripts](./CONTRIBUTING.md#extend-scripts). **Contribute 👷**. Contributions of any type are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) as the starting point. It includes useful information like [how to add new scripts](./CONTRIBUTING.md#extend-scripts).
## Additional Install Options
- Check the [releases page](https://github.com/undergroundwires/privacy.sexy/releases) for all available versions.
- Using [Scoop](https://scoop.sh/#/apps?q=privacy.sexy&s=2&d=1&o=true) package manager on Windows:
```powershell
scoop bucket add extras
scoop install privacy.sexy
```
## Development ## Development
Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment. Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment.
@@ -139,3 +167,7 @@ Refer to [development.md](./docs/development.md) for Docker usage and reading mo
Check [architecture.md](./docs/architecture.md) for an overview of design and how different parts and layers work together. You can refer to [application.md](./docs/application.md) for a closer look at application layer codebase and [presentation.md](./docs/presentation.md) for code related to GUI layer. [collection-files.md](./docs/collection-files.md) explains the YAML files that are the core of the application and [templating.md](./docs/templating.md) documents how to use templating language in those files. In [ci-cd.md](./docs/ci-cd.md), you can read more about the pipelines that automates maintenance tasks and ensures you get what see. Check [architecture.md](./docs/architecture.md) for an overview of design and how different parts and layers work together. You can refer to [application.md](./docs/application.md) for a closer look at application layer codebase and [presentation.md](./docs/presentation.md) for code related to GUI layer. [collection-files.md](./docs/collection-files.md) explains the YAML files that are the core of the application and [templating.md](./docs/templating.md) documents how to use templating language in those files. In [ci-cd.md](./docs/ci-cd.md), you can read more about the pipelines that automates maintenance tasks and ensures you get what see.
[docs/](./docs/) folder includes all other documentation. [docs/](./docs/) folder includes all other documentation.
## Security
Security is a top priority at privacy.sexy. An extensive commitment to security verification ensures this priority. For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).

31
SECURITY.md Normal file
View File

@@ -0,0 +1,31 @@
# Security Policy
privacy.sexy takes security seriously. Commitment is made to address all security issues with urgency. Responsible reporting of any discovered vulnerabilities in the project is highly encouraged.
## Reporting a Vulnerability
Efforts to responsibly disclose findings are greatly appreciated. To report a security vulnerability, follow these steps:
- For general vulnerabilities, [open an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) using the bug report template.
- For sensitive matters, [contact the developer directly](https://undergroundwires.dev).
## Security Report Handling
Upon receipt of a security report, the following actions will be taken:
- The report will be confirmed, identifying the affected components.
- The impact and severity of the issue will be assessed.
- Work on a fix and plan a release to address the vulnerability will be initiated.
- The reporter will be kept updated about the progress.
## Testing
Regular and extensive testing is conducted to ensure robust security in the project. Information about testing practices can be found in the [Testing Documentation](./docs/tests.md).
## Support
For additional assistance or any unanswered questions, [submit a GitHub issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose). Security concerns are a priority, and necessary support to address them is assured.
---
Active contribution to the safety and security of privacy.sexy is thanked. This collaborative effort keeps the project resilient and trustworthy for all.

View File

@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
};

View File

@@ -1,14 +1,18 @@
import { defineConfig } from 'cypress' import { defineConfig } from 'cypress';
import ViteConfig from './vite.config';
const CYPRESS_BASE_DIR = 'tests/e2e/';
export default defineConfig({ export default defineConfig({
fixturesFolder: 'tests/e2e/fixtures', fixturesFolder: `${CYPRESS_BASE_DIR}/fixtures`,
screenshotsFolder: 'tests/e2e/screenshots', screenshotsFolder: `${CYPRESS_BASE_DIR}/screenshots`,
videosFolder: 'tests/e2e/videos',
video: true,
videosFolder: `${CYPRESS_BASE_DIR}/videos`,
e2e: { e2e: {
setupNodeEvents(on, config) { baseUrl: `http://localhost:${ViteConfig.server.port}/`,
return require('./tests/e2e/plugins/index.js')(on, config) specPattern: `${CYPRESS_BASE_DIR}/**/*.cy.{js,jsx,ts,tsx}`, // Default: cypress/e2e/**/*.cy.{js,jsx,ts,tsx}
}, supportFile: `${CYPRESS_BASE_DIR}/support/e2e.ts`,
specPattern: 'tests/e2e/specs/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'tests/e2e/support/index.js',
}, },
}); });

5
dist-dirs.json Normal file
View File

@@ -0,0 +1,5 @@
{
"electronUnbundled": "dist-electron-unbundled",
"electronBundled": "dist-electron-bundled",
"web": "dist-web"
}

View File

@@ -35,7 +35,7 @@ Application layer enables [data-driven programming](https://en.wikipedia.org/wik
Application layer parses the application data to compile the domain object [`Application.ts`](./../src/domain/Application.ts). Application layer parses the application data to compile the domain object [`Application.ts`](./../src/domain/Application.ts).
A webpack loader loads (or injects) application data ([collection yaml files](./../src/application/collections/)) into the application layer in compile time. Application layer ([`ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts)) parses and compiles this data in runtime. The build tool loads (or injects) application data ([collection yaml files](./../src/application/collections/)) into the application layer in compile time. Application layer ([`ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts)) parses and compiles this data in runtime.
Application layer compiles templating syntax during parsing to create the end scripts. You can read more about templating syntax in [templating.md](./templating.md) and how application data uses them through functions in [collection-files.md | Function](./collection-files.md#function). Application layer compiles templating syntax during parsing to create the end scripts. You can read more about templating syntax in [templating.md](./templating.md) and how application data uses them through functions in [collection-files.md | Function](./collection-files.md#function).

View File

@@ -15,11 +15,23 @@ Application is
Application uses highly decoupled models & services in different DDD layers: Application uses highly decoupled models & services in different DDD layers:
- presentation layer (see [presentation.md](./presentation.md)), **Application layer** (see [application.md](./application.md)):
- application layer (see [application.md](./application.md)),
- and domain layer.
Application layer depends on and consumes domain layer. [Presentation layer](./presentation.md) consumes and depends on application layer along with domain layer. Application and presentation layers can communicate through domain model. - Coordinates application activities and consumes the domain layer.
**Presentation layer** (see [presentation.md](./presentation.md)):
- Handles UI/UX, consumes both the application and domain layers.
- May communicate directly with the infrastructure layer for technical needs, but avoids domain logic.
**Domain layer**:
- Serves as the system's core and central truth.
- Facilitates communication between the application and presentation layers through the domain model.
**Infrastructure layer**:
- Manages technical implementations without dependencies on other layers or domain knowledge.
![DDD + vue.js](./../img/architecture/app-ddd.png) ![DDD + vue.js](./../img/architecture/app-ddd.png)
@@ -27,6 +39,8 @@ Application layer depends on and consumes domain layer. [Presentation layer](./p
State handling uses an event-driven subscription model to signal state changes and special functions to register changes. It does not depend on third party packages. State handling uses an event-driven subscription model to signal state changes and special functions to register changes. It does not depend on third party packages.
The presentation layer can read and modify state through the context. State changes trigger events that components can subscribe to for reactivity.
Each layer treat application layer differently. Each layer treat application layer differently.
![State](./../img/architecture/app-state.png) ![State](./../img/architecture/app-state.png)
@@ -45,7 +59,7 @@ Each layer treat application layer differently.
- So state is mutable, and fires related events when mutated. - So state is mutable, and fires related events when mutated.
- 📖 Read more: [application.md | Application state](./application.md#application-state). - 📖 Read more: [application.md | Application state](./application.md#application-state).
It's comparable with flux ([`redux`](https://redux.js.org/)) or flux-like ([`vuex`](https://vuex.vuejs.org/)) patterns. Flux component "view" is [presentation layer](./presentation.md) in Vue. Flux functions "dispatcher", "store" and "action creation" functions lie in the [application layer](./application.md). A difference is that application state in privacy.sexy is mutable and lies in single flux "store" that holds app state and logic. The "actions" mutate the state directly which in turns act as dispatcher to notify its own event subscriptions (callbacks). It's comparable with `flux`, `vuex`, and `pinia`. A difference is that mutable application layer state in privacy.sexy is mutable and lies in single "store" that holds app state and logic. The "actions" mutate the state directly which in turns act as dispatcher to notify its own event subscriptions (callbacks).
## AWS infrastructure ## AWS infrastructure

View File

@@ -174,3 +174,19 @@
- `endCode:` *`string`* (**required**) - `endCode:` *`string`* (**required**)
- Code that'll be inserted at the end of user created script. - Code that'll be inserted at the end of user created script.
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!` - Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
## Naming guidelines
- Prioritize consistency throughout all names.
- Use an instruction format like "do this, do that" for clear, direct guidance. This approach reduces potential confusion and offers easy-to-follow steps. It provides specific, unambiguous instructions.
- Ensure brand names adhere to their official casing.
- Choose clear and uncomplicated language.
- Favor the terms:
- "Disable" over "Turn off"
- "Configure" over "Set up"
- "Clear" over "Erase" or "Clean"
- "Minimize" over "Limit" or "Reduce" (when it enhances clarity)
- "Remove" over "Uninstall"
- Structure your phrases for clarity.
- For instance, "Disable XX telemetry" or "Clear XX data" are preferred over "Clear data from XX", "Disable telemetry in XX", or "Clear data of XX".
- Use sentence case rather than Title Case.

View File

@@ -5,22 +5,28 @@ Before your commit, a good practice is to:
1. [Run unit tests](#testing) 1. [Run unit tests](#testing)
2. [Lint your code](#linting) 2. [Lint your code](#linting)
You could run other types of tests as well, but they may take longer time and overkill for your changes. Automated actions executes the tests for a pull request or change in the main branch. See [ci-cd.md](./ci-cd.md) for more information. You could run other types of tests as well, but they may take longer time and overkill for your changes.
Automated actions are set up to execute these tests as necessary.
See [ci-cd.md](./ci-cd.md) for more information.
## Commands ## Commands
### Prerequisites ### Prerequisites
- Install node >15.x. - Install Node >16.x.
- Install dependencies using `npm install`. - Install dependencies using `npm install` (or [`npm run install-deps`](#utility-scripts) for more options).
### Testing ### Testing
- Run unit tests: `npm run test:unit` - Run unit tests: `npm run test:unit`
- Run integration tests: `npm run test:integration` - Run integration tests: `npm run test:integration`
- Run e2e (end-to-end) tests - Run end-to-end (e2e) tests:
- Interactive mode with GUI: `npm run test:e2e` - `npm run test:cy:open`: Run tests interactively using the development server with hot-reloading.
- Headless mode without GUI: `npm run test:e2e -- --headless` - `npm run test:cy:run`: Run tests on the production build in a headless mode.
- Run checks:
- `npm run check:desktop`: Run runtime checks for packaged desktop applications ([README.md](./../tests/checks/desktop-runtime-errors/check-desktop-runtime-errors/README.md)).
- You can set environment variables active its flags such as `BUILD=true SCREENSHOT=true npm run check:desktop`
- `npm run check:external-urls`: Test whether external URLs used in applications are alive.
📖 Read more about testing in [tests](./tests.md). 📖 Read more about testing in [tests](./tests.md).
@@ -35,11 +41,26 @@ You could run other types of tests as well, but they may take longer time and ov
### Running ### Running
- Run in local server: `npm run serve` **Web:**
- Run in local server: `npm run dev`
- 💡 Meant for local development with features such as hot-reloading. - 💡 Meant for local development with features such as hot-reloading.
- Run using Docker: - Preview production build: `npm run preview`
1. Build: `docker build -t undergroundwires/privacy.sexy:latest .` - Start a local web server that serves the built solution from `./dist`.
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest` - 💡 Run `npm run build` before `npm run preview`.
**Desktop apps:**
- `npm run electron:dev`: The command will build the main process and preload scripts source code, and start a dev server for the renderer, and start the Electron app.
- `npm run electron:preview`: The command will build the main process, preload scripts and renderer source code, and start the Electron app to preview.
- `npm run electron:prebuild`: The command will build the main process, preload scripts and renderer source code. Usually before packaging the Electron application, you need to execute this command.
- `npm run electron:build`: Prebuilds the Electron application, packages and publishes it through `electron-builder`.
**Docker:**
1. Build: `docker build -t undergroundwires/privacy.sexy:latest .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest`
3. Application should be available at [`http://localhost:8080`](http://localhost:8080)
### Building ### Building
@@ -47,13 +68,27 @@ 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.
- [**`npm run check:verify-build-artifacts [-- <options>]`**](../scripts/verify-build-artifacts.js):
- Verifies the existence and content of build artifacts. Useful for ensuring that the build process is generating the expected output.
- [**`node scripts/verify-web-server-status.js --url [URL]`**](../scripts/verify-web-server-status.js):
- Checks if a specified server is up with retries and returns an HTTP 200 status code.
## Recommended extensions ## Recommended extensions
You should use EditorConfig to follow project style. You should use EditorConfig to follow project style.

View File

@@ -1,30 +1,33 @@
# Presentation layer # Presentation layer
Presentation layer consists of UI-related code. It uses Vue.js as JavaScript framework and includes Vue.js components. It also includes [Electron](https://www.electronjs.org/) to provide functionality to desktop application. The presentation layer handles UI concerns using Vue as JavaScript framework and Electron to provide desktop functionality.
It's designed event-driven from bottom to top. It listens user events (from top) and state events (from bottom) to update state or the GUI. It reflects the [application state](./application.md#application-state) and allows user interactions to modify it. Components manage their own local UI state.
The presentation layer uses an event-driven architecture for bidirectional reactivity between the application state and UI. State change events flow bottom-up to trigger UI updates, while user events flow top-down through components, some ultimately modifying the application state.
📖 Refer to [architecture.md (Layered Application)](./architecture.md#layered-application) to read more about the layered architecture. 📖 Refer to [architecture.md (Layered Application)](./architecture.md#layered-application) to read more about the layered architecture.
## Structure ## Structure
- [`/src/` **`presentation/`**](./../src/presentation/): Contains all presentation related code including Vue and Electron configurations - [`/src/` **`presentation/`**](./../src/presentation/): Contains Vue and Electron code.
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue global objects including components and plugins. - [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue components and plugins.
- [**`components/`**](./../src/presentation/components/): Contains all Vue components and their helper classes. - [**`components/`**](./../src/presentation/components/): Contains Vue components and helpers.
- [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that other components share. - [**`Shared/`**](./../src/presentation/components/Shared): Contains shared Vue components and helpers.
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets that webpack will process. - [**`Hooks`**](../src/presentation/components/Shared/Hooks): Hooks used by components through [dependency injection](#dependency-injections).
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts - [**`/public/`**](../src/presentation/public/): Contains static assets.
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles used throughout different components. - [**`assets/`**](./../src/presentation/assets/styles/): Contains assets processed by Vite.
- [**`components/`**](./../src/presentation/assets/styles/components): Contains reusable styles coupled to a Vue/HTML component. - [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
- [**`vendors-extensions/`**](./../src/presentation/assets/styles/third-party-extensions): Contains styles that override third-party components used. - [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Primary Sass file, passes along all other styles, should be the single file used from other components. - [**`components/`**](./../src/presentation/assets/styles/components): Contains styles coupled to Vue components.
- [**`main.ts`**](./../src/presentation/main.ts): Application entry point that mounts and starts Vue application. - [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint.
- [**`electron/`**](./../src/presentation/electron/): Electron configuration for the desktop application. - [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
- [**`main.ts`**](./../src/presentation/main.ts): Main process of Electron, started as first thing when app starts. - [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
- [**`/public/`**](./../public/): Contains static assets that are directly copied and do not go through webpack. - [`/main/` **`index.ts`**](./../src/presentation/main.ts): Main entry for Electron, managing application windows and lifecycle events.
- [**`/vue.config.js`**](./../vue.config.js): Global Vue CLI configurations loaded by `@vue/cli-service`. - [`/preload/` **`index.ts`**](./../src/presentation/main.ts): Script executed before the renderer, securing Node.js features for renderer use.
- [**`/postcss.config.js`**](./../postcss.config.js): PostCSS configurations used by Vue CLI internally. - [**`/vite.config.ts`**](./../vite.config.ts): Contains Vite configurations for building web application.
- [**`/babel.config.js`**](./../babel.config.js): Babel configurations for polyfills used by `@vue/cli-plugin-babel`. - [**`/electron.vite.config.ts`**](./../electron.vite.config.ts): Contains Vite configurations for building desktop applications.
- [**`/postcss.config.cjs`**](./../postcss.config.cjs): Contains PostCSS configurations for Vite.
## Visual design best-practices ## Visual design best-practices
@@ -32,7 +35,7 @@ Add visual clues for clickable items. It should be as clear as possible that the
## Application data ## Application data
Components (should) use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain to avoid [parsing and compiling](./application.md#parsing-and-compiling) the application again. Components (should) use [`UseApplication`](./../src/presentation/components/Shared/Hooks/UseApplication.ts) to reach the application domain to avoid [parsing and compiling](./application.md#parsing-and-compiling) the application again.
[Application.ts](../src/domain/Application.ts) is an immutable domain model that represents application state. It includes: [Application.ts](../src/domain/Application.ts) is an immutable domain model that represents application state. It includes:
@@ -43,34 +46,58 @@ You can read more about how application layer provides application data to he pr
## Application state ## Application state
Inheritance of a Vue components marks whether it uses application state . Components that does not handle application state extends `Vue`. Stateful components mutate or/and react to state changes (such as user selection or search queries) in [ApplicationContext](./../src/application/Context/ApplicationContext.ts) extend [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts) class to access the context / state. This project uses a singleton instance of the application state, making it available to all Vue components.
[`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts) functions include: The decision to not use third-party state management libraries like [`vuex`](https://web.archive.org/web/20230801191617/https://vuex.vuejs.org/) or [`pinia`](https://web.archive.org/web/20230801191743/https://pinia.vuejs.org/) was made to promote code independence and enhance portability.
- Creating a singleton of the state and makes it available to presentation layer as single source of truth. Stateful components can mutate and/or react to state changes (e.g., user selection, search queries) in the [ApplicationContext](./../src/application/Context/ApplicationContext.ts). Vue components import [`CollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) to access both the application context and the state.
- Providing virtual abstract `handleCollectionState` callback that it calls when
- the Vue loads the component,
- and also every time when state changes.
- Providing `events` member to make lifecycling of state subscriptions events easier because it ensures that components unsubscribe from listening to state events when
- the component is no longer used (destroyed),
- an if [ApplicationContext](./../src/application/Context/ApplicationContext.ts) changes the active [collection](./collection-files.md) to a different one.
📖 Refer to [architecture.md | Application State](./architecture.md#application-state) to get an overview of event handling and [application.md | Application State](./presentation.md#application-state) for deeper look into how the application layer manages state. [`UseCollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) provides several functionalities including:
## Modals - **Singleton State Instance**: It creates a singleton instance of the state, which is shared across the presentation layer. The singleton instance ensures that there's a single source of truth for the application's state.
- **State Change Callback and Lifecycle Management**: It offers a mechanism to register callbacks, which will be invoked when the state initializes or mutates. It ensures that components unsubscribe from state events when they are no longer in use or when [ApplicationContext](./../src/application/Context/ApplicationContext.ts) switches the active [collection](./collection-files.md).
- **State Access and Modification**: It provides functions to read and mutate for accessing and modifying the state, encapsulating the details of these operations.
- **Event Subscription Lifecycle Management**: Includes an `events` member that simplifies state subscription lifecycle events. This ensures that components unsubscribe from state events when they are no longer in use, or when [ApplicationContext](./../src/application/Context/ApplicationContext.ts) switches the active [collection](./collection-files.md).
[Dialog.vue](./../src/presentation/components/Shared/Dialog.vue) is a shared component that other components used to show modal windows. 📖 Refer to [architecture.md | Application State](./architecture.md#application-state) for an overview of event handling and [application.md | Application State](./presentation.md#application-state) for an in-depth understanding of state management in the application layer.
You can use it by wrapping the content inside of its `slot` and call `.show()` function on its reference. For example: ## Dependency injections
```html The presentation layer uses Vue's native dependency injection system to increase testability and decouple components.
<Dialog ref="testDialog">
<div>Hello world</div>
</Dialog>
<div @click="$refs.testDialog.show()">Show dialog</div>
```
## Sass naming convention To add a new dependency:
1. **Define its symbol**: Define an associated symbol for every dependency in [`injectionSymbols.ts`](./../src/presentation/injectionSymbols.ts). Symbols are grouped into:
- **Singletons**: Shared across components, instantiated once.
- **Transients**: Factories yielding a new instance on every access.
2. **Provide the dependency**: Modify the [`provideDependencies`](./../src/presentation/bootstrapping/DependencyProvider.ts) function to include the new dependency. [`App.vue`](./../src/presentation/components/App.vue) calls this function within its `setup()` hook to register the dependencies.
3. **Inject the dependency**: Use Vue's `inject` method alongside the defined symbol to incorporate the dependency into components.
- For singletons, invoke the factory method: `inject(symbolKey)()`.
- For transients, directly inject: `inject(symbolKey)`.
## Shared UI components
Shared UI components promote consistency and simplifies the creation of the front-end.
In order to maintain portability and easy maintainability, the preference is towards using homegrown components over third-party ones or comprehensive UI frameworks like Quasar.
Shared components include:
- [ModalDialog.vue](./../src/presentation/components/Shared/Modal/ModalDialog.vue) is utilized for rendering modal windows.
- [TooltipWrapper.vue](./../src/presentation/components/Shared/TooltipWrapper.vue) acts as a wrapper for rendering tooltips.
## Desktop builds
Desktop builds uses `electron-vite` to bundle the code, and `electron-builder` to build and publish the packages.
## Styles
### Style location
- **Global styles**: The [`assets/styles/`](#structure) directory is reserved for styles that have a broader scope, affecting multiple components or entire layouts. They are generic and should not be tightly coupled to a specific component's functionality.
- **Component-specific styles**: Styles closely tied to a particular component's functionality or appearance should reside near the component they are used by. This makes it easier to locate and modify styles when working on a specific component.
### Sass naming convention
- Use lowercase for variables/functions/mixins, e.g.: - Use lowercase for variables/functions/mixins, e.g.:
- Variable: `$variable: value;` - Variable: `$variable: value;`

View File

@@ -2,79 +2,142 @@
## Benefits of templating ## Benefits of templating
- Generating scripts by sharing code to increase best-practice usage and maintainability. - **Code sharing:** Share code across scripts for consistent practices and easier maintenance.
- Creating self-contained scripts without cross-dependencies. - **Script independence:** Generate self-contained scripts, eliminating the need for external code.
- Use of pipes for writing cleaner code and letting pipes do dirty work. - **Cleaner code:** Use pipes for complex operations, resulting in more readable and streamlined code.
## Expressions ## Expressions
- Expressions start and end with mustaches (double brackets, `{{` and `}}`). **Syntax:**
- E.g. `Hello {{ $name }} !`
- Syntax is close to [Go Templates ❤️](https://pkg.go.dev/text/template) but not the same.
- Functions enables usage of expressions.
- In script definition parts of a function, see [`Function`](./collection-files.md#Function).
- When doing a call as argument values, see [`FunctionCall`](./collection-files.md#Function).
- Expressions inside expressions (nested templates) are supported.
- An expression can output another expression that will also be compiled.
- E.g. following would compile first [with expression](#with), and then [parameter substitution](#parameter-substitution) in its output.
```go Expressions are enclosed within `{{` and `}}`.
{{ with $condition }} Example: `Hello {{ $name }}!`.
echo {{ $text }} They are a core component of templating, enhancing scripts with dynamic capabilities and functionality.
{{ end }}
``` **Syntax similarity:**
The syntax shares similarities with [Go Templates ❤️](https://pkg.go.dev/text/template), but with some differences:
**Function definitions:**
You can use expressions in function definition.
Refer to [Function](./collection-files.md#function) for more details.
Example usage:
```yaml
name: GreetFunction
parameters:
- name: name
code: Hello {{ $name }}!
```
If you assign `name` the value `world`, invoking `GreetFunction` would result in `Hello world!`.
**Function arguments:**
You can also use expressions in arguments in nested function calls.
Refer to [`Function | collection-files.md`](./collection-files.md#functioncall) for more details.
Example with nested function calls:
```yaml
-
name: PrintMessageFunction
parameters:
- name: message
code: echo "{{ $message }}"
-
name: GreetUserFunction
parameters:
- name: userName
call:
name: PrintMessageFunction
parameters:
argument: 'Hello, {{ $userName }}!'
```
Here, if `userName` is `Alice`, invoking `GreetUserFunction` would execute `echo "Hello, Alice!"`.
**Nested templates:**
You can nest expressions inside expressions (also called "nested templates").
This means that an expression can output another expression where compiler will compile both.
For example, following would compile first [with expression](#with), and then [parameter substitution](#parameter-substitution) in its output:
```go
{{ with $condition }}
echo {{ $text }}
{{ end }}
```
### Parameter substitution ### Parameter substitution
A simple function example: Parameter substitution dynamically replaces variable references with their corresponding values in the script.
**Example function:**
```yaml ```yaml
function: EchoArgument name: DisplayTextFunction
parameters: parameters:
- name: 'argument' - name: 'text'
code: Hello {{ $argument }} ! code: echo {{ $text }}
``` ```
It would print "Hello world" if it's called in a [script](./collection-files.md#script) as following: Invoking `DisplayTextFunction` with `text` set to `"Hello, world!"` would result in `echo "Hello, World!"`.
```yaml
script: Echo script
call:
function: EchoArgument
parameters:
argument: World
```
A function can call other functions such as:
```yaml
-
function: CallerFunction
parameters:
- name: 'value'
call:
function: EchoArgument
parameters:
argument: {{ $value }}
-
function: EchoArgument
parameters:
- name: 'argument'
code: Hello {{ $argument }} !
```
### with ### with
Skips its "block" if the variable is absent or empty. Its "block" is between `with` start (`{{ with .. }}`) and end (`{{ end }`}) expressions. The `with` expression enables conditional rendering and provides a context variable for simpler code.
E.g. `{{ with $parameterName }} Hi, I'm a block! {{ end }}` would only output `Hi, I'm a block!` if `parameterName` has any value..
It binds its context (value of the provided parameter value) as arbitrary `.` value. It allows you to use the argument value of the given parameter when it is provided and not empty such as: **Optional block rendering:**
If the provided variable is falsy (`false`, `null`, or empty), the compiler skips the enclosed block of code.
A "block" lies between the with start (`{{ with .. }}`) and end (`{{ end }}`) expressions, defining its boundaries.
Example:
```go
{{ with $optionalVariable }}
Hello
{{ end }}
```
This would display `Hello` if `$optionalVariable` is truthy.
**Parameter declaration:**
You should set `optional: true` for the argument if you use it like `{{ with $argument }} .. {{ end }}`.
Declare parameters used for `with` condition as optional such as:
```yaml
name: ConditionalOutputFunction
parameters:
- name: 'data'
optional: true
code: |-
{{ with $data }}
Data is: {{ . }}
{{ end }}
```
**Context variable:**
`with` statement binds its context (value of the provided parameter value) as arbitrary `.` value.
`{{ . }}` syntax gives you access to the context variable.
This is optional to use, and not required to use `with` expressions.
For example:
```go ```go
{{ with $parameterName }}Parameter value is {{ . }} here {{ end }} {{ with $parameterName }}Parameter value is {{ . }} here {{ end }}
``` ```
It supports multiline text inside the block. You can have something like: **Multiline text:**
It supports multiline text inside the block. You can write something like:
```go ```go
{{ with $argument }} {{ with $argument }}
@@ -83,7 +146,9 @@ It supports multiline text inside the block. You can have something like:
{{ end }} {{ end }}
``` ```
You can also use other expressions inside its block, such as [parameter substitution](#parameter-substitution): **Inner expressions:**
You can also embed other expressions inside its block, such as [parameter substitution](#parameter-substitution):
```go ```go
{{ with $condition }} {{ with $condition }}
@@ -91,32 +156,44 @@ You can also use other expressions inside its block, such as [parameter substitu
{{ end }} {{ end }}
``` ```
💡 Declare parameters used for `with` condition as optional. Set `optional: true` for the argument if you use it like `{{ with $argument }} .. {{ end }}`. This also includes nesting `with` statements:
Example: ```go
{{ with $condition1 }}
```yaml Value of $condition1: {{ . }}
function: FunctionThatOutputsConditionally {{ with $condition2 }}
parameters: Value of $condition2: {{ . }}
- name: 'argument'
optional: true
code: |-
{{ with $argument }}
Value is: {{ . }}
{{ end }} {{ end }}
{{ end }}
``` ```
### Pipes ### Pipes
- Pipes are functions available for handling text. Pipes are functions designed for text manipulation.
- Allows stacking actions one after another also known as "chaining". They allow for a sequential application of operations resembling [Unix pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)), also known as "chaining".
- Like [Unix pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)), the concept is simple: each pipeline's output becomes the input of the following pipe. Each pipeline's output becomes the input of the following pipe.
- You cannot create pipes. [A dedicated compiler](./application.md#parsing-and-compiling) provides pre-defined pipes to consume in collection files.
- You can combine pipes with other expressions such as [parameter substitution](#parameter-substitution) and [with](#with) syntax. **Pre-defined**:
- ❗ Pipe names must be camelCase without any space or special characters.
- **Existing pipes** Pipes are pre-defined by the system.
- `inlinePowerShell`: Converts a multi-lined PowerShell script to a single line. You cannot create pipes in [collection files](./collection-files.md).
- `escapeDoubleQuotes`: Escapes `"` characters, allows you to use them inside double quotes (`"`). [A dedicated compiler](./application.md#parsing-and-compiling) provides pre-defined pipes to consume in collection files.
- **Example usages**
- `{{ with $code }} echo "{{ . | inlinePowerShell }}" {{ end }}` **Compatibility:**
- `{{ with $code }} echo "{{ . | inlinePowerShell | escapeDoubleQuotes }}" {{ end }}`
You can combine pipes with other expressions such as [parameter substitution](#parameter-substitution) and [with](#with) syntax.
For example:
```go
{{ with $script }} echo "{{ . | inlinePowerShell | escapeDoubleQuotes }}" {{ end }}
```
**Naming:**
❗ Pipe names must be camelCase without any space or special characters.
**Available pipes:**
- `inlinePowerShell`: Converts a multi-lined PowerShell script to a single line.
- `escapeDoubleQuotes`: Escapes `"` characters for batch command execution, allows you to use them inside double quotes (`"`).

View File

@@ -5,77 +5,84 @@ There are different types of tests executed:
1. [Unit tests](#unit-tests) 1. [Unit tests](#unit-tests)
2. [Integration tests](#integration-tests) 2. [Integration tests](#integration-tests)
3. [End-to-end (E2E) tests](#e2e-tests) 3. [End-to-end (E2E) tests](#e2e-tests)
4. [Automated checks](#automated-checks)
Common aspects for all tests: ## Unit and integration tests
- They use [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/). - They utilize [Vitest](https://vitest.dev/).
- Their files end with `.spec.{ts|js}` suffix. - Test files are suffixed with `.spec.ts`.
💡 You can use path/module alias `@/tests` in import statements.
## Unit tests
- Unit tests test each component in isolation.
- All unit tests goes under [`./tests/unit`](./../tests/unit).
- They rely on [stubs](./../tests/unit/shared/Stubs) for isolation.
### Unit tests structure
- [`./src/`](./../src/)
- Includes source code that unit tests will test.
- [`./tests/unit/`](./../tests/unit/)
- Includes test code.
- Tests follow same folder structure as [`./src/`](./../src).
- E.g. if system under test lies in [`./src/application/ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts) then its tests would be in test would be at [`./tests/unit/application/ApplicationFactory.spec.ts`](./../tests/unit/application/ApplicationFactory.spec.ts).
- [`shared/`](./../tests/unit/shared/)
- Includes common functionality that's shared across unit tests.
- [`Assertions/`](./../tests/unit/shared/Assertions):
- Common assertions that extend [Chai Assertion Library](https://www.chaijs.com/).
- Asserting functions should start with `expect` prefix.
- [`TestCases/`](./../tests/unit/shared/TestCases/)
- Shared test cases.
- Functions that calls `it()` from [Mocha test framework](https://mochajs.org/) should have `it` prefix.
- E.g. `itEachAbsentCollectionValue()`.
- [`Stubs/`](./../tests/unit/shared/Stubs)
- Includes stubs to be able to test components in isolation.
- Stubs have minimal and dummy behavior to be functional, they may also have spying or mocking functions.
### Unit tests naming
- Each test suite first describe the system under test.
- E.g. tests for class `Application.ts` are all inside `Application.spec.ts`.
- `describe` blocks tests for same function (if applicable).
- E.g. test for `run()` are inside `describe('run', () => ..)`.
### Act, arrange, assert ### Act, arrange, assert
- Tests use act, arrange and assert (AAA) pattern when applicable. - Tests implement the act, arrange, and assert (AAA) pattern.
- **Arrange** - **Arrange**
- Sets up the test case. - Sets up the test scenario and environment.
- Starts with comment line `// arrange`. - Begins with comment line `// arrange`.
- **Act** - **Act**
- Executes the actual test. - Executes the actual test.
- Starts with comment line `// act`. - Begins with comment line `// act`.
- **Assert** - **Assert**
- Elicit some sort of expectation. - Sets an expectation for the test's outcome.
- Starts with comment line `// assert`. - Begins with comment line `// assert`.
## Integration tests ### Unit tests
- Tests functionality of a component in combination with others (not isolated). - Evaluate individual components in isolation.
- Ensure dependencies to third parties work as expected. - Located in [`./tests/unit`](./../tests/unit).
- Defined in [./tests/integration](./../tests/integration). - Achieve isolation using [stubs](./../tests/unit/shared/Stubs).
- Include Vue component tests, enabled by `@vue/test-utils`.
#### Unit tests naming
- Test suites start with a description of the component or system under test.
- E.g., tests for `Application.ts` are contained in `Application.spec.ts`.
- Whenever possible, `describe` blocks group tests of the same function.
- E.g., tests for `run()` are inside `describe('run', () => ...)`.
### Integration tests
- Assess the combined functionality of components.
- They verify that third-party dependencies function as anticipated.
## E2E tests ## E2E tests
- Test the functionality and performance of a running application. - Examine the live web application's functionality and performance.
- Vue CLI plugin [`e2e-cypress`](https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-plugin-e2e-cypress#readme) configures E2E tests. - Uses Cypress to run the tests.
- Test names and folders have logical structure based on tests executed.
- The structure is following: ## Automated checks
- [`cypress.config.ts`](./../cypress.config.ts): Cypress configuration file.
- [`./tests/e2e/`](./../tests/e2e/): Base Cypress folder. These checks validate various qualities like runtime execution, building process, security testing, etc.
- [`/specs/`](./../tests/e2e/specs/): Test files named with `.spec.js` extension.
- [`/plugins/index.js`](./../tests/e2e/plugins/index.js): Plugin file executed before loading project. - Use [various tools](./../package.json) and [scripts](./../scripts).
- [`/support/index.js`](./../tests/e2e/support/index.js): Support file, runs before every single spec file. - Are automatically executed as [GitHub workflows](./../.github/workflows).
- *(Ignored)* `/videos`: Asset folder for videos taken during tests.
- *(Ignored)* `/screenshots`: Asset folder for Screenshots taken during tests. ### Security checks
- [`checks.security.sast`](./../.github/workflows/checks.security.sast.yaml): Utilizes CodeQL to conduct Static Analysis Security Testing (SAST) to ensure the secure integrity of the codebase.
- [`checks.security.dependencies`](./../.github/workflows/checks.security.dependencies.yaml): Performs audits on third-party dependencies to identify and mitigate potential vulnerabilities, safeguarding the project from exploitable weaknesses.
## Tests structure
- [`package.json`](./../package.json): Defines test commands and includes tools used in tests.
- [`vite.config.ts`](./../vite.config.ts): Configures `vitest` for unit and integration tests.
- [`./src/`](./../src/): Contains the code subject to testing.
- [`./tests/shared/`](./../tests/shared/): Contains code shared by different test categories.
- [`bootstrap/setup.ts`](./../tests/shared/bootstrap/setup.ts): Initializes unit and integration tests.
- [`./tests/unit/`](./../tests/unit/)
- Stores unit test code.
- The directory structure mirrors [`./src/`](./../src).
- E.g., tests for [`./src/application/ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts) reside in [`./tests/unit/application/ApplicationFactory.spec.ts`](./../tests/unit/application/ApplicationFactory.spec.ts).
- [`shared/`](./../tests/unit/shared/)
- Contains shared unit test functionalities.
- [`Assertions/`](./../tests/unit/shared/Assertions): Contains common assertion functions, prefixed with `expect`.
- [`TestCases/`](./../tests/unit/shared/TestCases/)
- Shared test cases.
- Functions that calls `it()` from [Vitest](https://vitest.dev/) should have `it` prefix.
- [`Stubs/`](./../tests/unit/shared/Stubs): Maintains stubs for component isolation, equipped with basic functionalities and, when necessary, spying or mocking capabilities.
- [`./tests/integration/`](./../tests/integration/): Contains integration test files.
- [`cypress.config.ts`](./../cypress.config.ts): Cypress (E2E tests) configuration file.
- [`./tests/e2e/`](./../tests/e2e/): Base Cypress folder, includes tests with `.cy.ts` extension.
- [`/support/e2e.ts`](./../tests/e2e/support/e2e.ts): Support file, runs before every single spec file.
- [`/tsconfig.json`]: TypeScript configuration for file Cypress code, improves IDE support, recommended to have by official documentation.
- *(git ignored)* `/videos`: Asset folder for videos taken during tests.
- *(git ignored)* `/screenshots`: Asset folder for Screenshots taken during tests.

43
electron-builder.cjs Normal file
View 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}',
},
};

80
electron.vite.config.ts Normal file
View File

@@ -0,0 +1,80 @@
import { resolve } from 'path';
import { mergeConfig, UserConfig } from 'vite';
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
import { getAliasesFromTsConfig, getClientEnvironmentVariables } from './vite-config-helper';
import { createVueConfig } from './vite.config';
import distDirs from './dist-dirs.json' assert { type: 'json' };
const MAIN_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/main/index.ts');
const PRELOAD_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/preload/index.ts');
const WEB_INDEX_HTML_PATH = resolvePathFromProjectRoot('src/presentation/index.html');
const ELECTRON_DIST_SUBDIRECTORIES = {
main: resolveElectronDistSubdirectory('main'),
preload: resolveElectronDistSubdirectory('preload'),
renderer: resolveElectronDistSubdirectory('renderer'),
};
process.env.ELECTRON_ENTRY = resolve(ELECTRON_DIST_SUBDIRECTORIES.main, 'index.cjs');
export default defineConfig({
main: getSharedElectronConfig({
distDirSubfolder: ELECTRON_DIST_SUBDIRECTORIES.main,
entryFilePath: MAIN_ENTRY_FILE,
}),
preload: getSharedElectronConfig({
distDirSubfolder: ELECTRON_DIST_SUBDIRECTORIES.preload,
entryFilePath: PRELOAD_ENTRY_FILE,
}),
renderer: mergeConfig(
createVueConfig({
supportLegacyBrowsers: false,
}),
{
build: {
outDir: ELECTRON_DIST_SUBDIRECTORIES.renderer,
rollupOptions: {
input: {
index: WEB_INDEX_HTML_PATH,
},
},
},
},
),
});
function getSharedElectronConfig(options: {
readonly distDirSubfolder: string;
readonly entryFilePath: string;
}): UserConfig {
return {
build: {
outDir: options.distDirSubfolder,
lib: {
entry: options.entryFilePath,
},
rollupOptions: {
output: {
entryFileNames: '[name].cjs', // This is needed so `type="module"` works
},
},
},
plugins: [externalizeDepsPlugin()],
define: {
...getClientEnvironmentVariables(),
},
resolve: {
alias: {
...getAliasesFromTsConfig(),
},
},
};
}
function resolvePathFromProjectRoot(pathSegment: string): string {
return resolve(__dirname, pathSegment);
}
function resolveElectronDistSubdirectory(subDirectory: string): string {
const electronDistDir = resolvePathFromProjectRoot(distDirs.electronUnbundled);
return resolve(electronDistDir, subDirectory);
}

View File

@@ -4,9 +4,6 @@ This folder contains image files and other resources related to images.
## logo.svg ## logo.svg
[logo.svg](./logo.svg) is the master logo from which all other icons or images are created from. [`logo.svg`](./logo.svg) serves as the primary logo from which all other icons and images are derived.
It should be the only file that will be changed manually. Only modify this file manually.
After making changes, execute `npm run build:icons` to regenerate logo files in various formats.
[`logo-update.mjs`](./logo-update.mjs) script in this folder updates all the logo files.
It should be executed everytime the logo is changed.
It automates recreation of logo files in different formats.

34792
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,112 +1,107 @@
{ {
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.12.0", "version": "0.12.5",
"private": true, "private": true,
"slogan": "Now you have the choice", "slogan": "Now you have the choice",
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆", "description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
"author": "undergroundwires", "author": "undergroundwires",
"type": "module",
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "dev": "vite",
"build": "vue-cli-service build", "build": "vue-tsc --noEmit && vite build",
"test:unit": "vue-cli-service test:unit", "preview": "vite preview",
"test:e2e": "vue-cli-service test:e2e", "test:unit": "vitest run --dir tests/unit",
"test:integration": "vitest run --dir tests/integration",
"test:cy:run": "start-server-and-test \"vite build && vite preview --port 7070\" http://localhost:7070 \"cypress run --config baseUrl=http://localhost:7070\"",
"test:cy:open": "start-server-and-test \"vite --port 7070 --mode production\" http://localhost:7070 \"cypress open --config baseUrl=http://localhost:7070\"",
"lint": "npm run lint:md && npm run lint:md:consistency && npm run lint:md:relative-urls && npm run lint:eslint && npm run lint:yaml", "lint": "npm run lint:md && npm run lint:md:consistency && npm run lint:md:relative-urls && npm run lint:eslint && npm run lint:yaml",
"create-icons": "node img/logo-update.mjs", "install-deps": "node scripts/npm-install.js",
"electron:build": "vue-cli-service electron:build", "icons:build": "node scripts/logo-update.js",
"electron:serve": "vue-cli-service electron:serve", "check:desktop": "vitest run --dir tests/checks/desktop-runtime-errors --environment node",
"lint:eslint": "vue-cli-service lint --no-fix --mode production", "check:external-urls": "vitest run --dir tests/checks/external-urls --environment node",
"check:verify-build-artifacts": "node scripts/verify-build-artifacts",
"electron:dev": "electron-vite dev",
"electron:preview": "electron-vite preview",
"electron:prebuild": "electron-vite build",
"electron:build": "electron-builder",
"lint:eslint": "eslint . --ignore-path .gitignore",
"lint:md": "markdownlint **/*.md --ignore node_modules", "lint:md": "markdownlint **/*.md --ignore node_modules",
"lint:md:consistency": "remark . --frail --use remark-preset-lint-consistent", "lint:md:consistency": "remark . --frail --use remark-preset-lint-consistent",
"lint:md:relative-urls": "remark . --frail --use remark-validate-links", "lint:md:relative-urls": "remark . --frail --use remark-validate-links",
"lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml", "lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps"
"test:integration": "vue-cli-service test:unit \"tests/integration/**/*.spec.ts\""
}, },
"main": "index.js",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0", "@floating-ui/vue": "^1.0.2",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^2.0.9",
"@juggle/resize-observer": "^3.4.0", "@juggle/resize-observer": "^3.4.0",
"ace-builds": "^1.23.4", "ace-builds": "^1.30.0",
"core-js": "^3.32.0",
"cross-fetch": "^4.0.0", "cross-fetch": "^4.0.0",
"electron-log": "^4.4.8",
"electron-progressbar": "^2.1.0", "electron-progressbar": "^2.1.0",
"electron-updater": "^6.1.4",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"install": "^0.13.0", "markdown-it": "^13.0.2",
"liquor-tree": "^0.2.70", "vue": "^3.3.7"
"markdown-it": "^13.0.1",
"npm": "^9.8.1",
"v-tooltip": "2.1.3",
"vue": "^2.7.14",
"vue-class-component": "^7.2.6",
"vue-js-modal": "^2.0.1",
"vue-property-decorator": "^9.1.2"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.3.2", "@modyfi/vite-plugin-yaml": "^1.0.4",
"@types/ace": "^0.0.48", "@rushstack/eslint-patch": "^1.5.1",
"@types/chai": "^4.3.5", "@types/ace": "^0.0.49",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@types/mocha": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/parser": "^5.62.0",
"@vue/cli-plugin-babel": "~5.0.8", "@vitejs/plugin-legacy": "^4.1.1",
"@vue/cli-plugin-e2e-cypress": "~5.0.8", "@vitejs/plugin-vue": "^4.4.0",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-plugin-typescript": "~5.0.8",
"@vue/cli-plugin-unit-mocha": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"@vue/eslint-config-airbnb-with-typescript": "^7.0.0", "@vue/eslint-config-airbnb-with-typescript": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.3", "@vue/eslint-config-typescript": "^11.0.3",
"chai": "^4.3.7", "@vue/test-utils": "^2.4.1",
"cypress": "^12.17.2", "autoprefixer": "^10.4.16",
"electron": "^25.3.2", "cypress": "^13.3.1",
"electron-builder": "^24.6.3", "electron": "^27.0.0",
"electron-builder": "^24.6.4",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-icon-builder": "^2.0.1", "electron-icon-builder": "^2.0.1",
"electron-log": "^4.4.8", "electron-vite": "^1.0.28",
"electron-updater": "^6.1.4", "eslint": "^8.51.0",
"eslint": "^8.46.0", "eslint-plugin-cypress": "^2.15.1",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-vue": "^9.17.0",
"eslint-plugin-vue": "^9.6.0", "eslint-plugin-vuejs-accessibility": "^2.2.0",
"eslint-plugin-vuejs-accessibility": "^1.2.0", "icon-gen": "^4.0.0",
"icon-gen": "^3.0.1", "jsdom": "^22.1.0",
"js-yaml-loader": "^1.2.2", "markdownlint-cli": "^0.37.0",
"markdownlint-cli": "^0.35.0", "postcss": "^8.4.31",
"remark-cli": "^11.0.0", "remark-cli": "^12.0.0",
"remark-lint-no-dead-urls": "^1.1.0", "remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^5.1.2", "remark-preset-lint-consistent": "^5.1.2",
"remark-validate-links": "^12.1.1", "remark-validate-links": "^13.0.0",
"sass": "^1.64.1", "sass": "^1.69.3",
"sass-loader": "^13.3.2", "start-server-and-test": "^2.0.1",
"svgexport": "^0.4.2", "svgexport": "^0.4.2",
"ts-loader": "^9.4.4", "terser": "^5.21.0",
"typescript": "~4.6.2", "tslib": "^2.6.2",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4", "typescript": "^5.2.2",
"yaml-lint": "^1.7.0", "vite": "^4.4.11",
"tslib": "~2.4.0" "vitest": "^0.34.6",
}, "vue-tsc": "^1.8.19",
"overrides": { "yaml-lint": "^1.7.0"
"vue-cli-plugin-electron-builder": {
"electron-builder": "^24.6.3"
}
}, },
"//devDependencies": { "//devDependencies": {
"typescript": [ "terser": "Used by @vitejs/plugin-legacy for minification",
"Cannot upgrade to 5.X.X due to unmaintained @vue/cli-plugin-typescript, https://github.com/vuejs/vue-cli/issues/7401", "@rushstack/eslint-patch": "Needed by @vue/eslint-config-typescript",
"Cannot upgrade to > 4.6.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252" "@vue/eslint-config-typescript": "Cannot upgrade to 12.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
], "@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
"tslib": "Cannot upgrade to > 2.4.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252", "@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58"
"@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-typescript, https://github.com/vuejs/eslint-config-typescript/pull/60",
"@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-typescript, https://github.com/vuejs/eslint-config-typescript/pull/60"
}, },
"homepage": "https://privacy.sexy", "homepage": "https://privacy.sexy",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/undergroundwires/privacy.sexy.git" "url": "https://github.com/undergroundwires/privacy.sexy.git"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
},
"//optionalDependencies": {
"dmg-license": "Required by `electron-builder` for DMG builds on macOS, https://github.com/electron-userland/electron-builder/issues/6489, https://github.com/electron-userland/electron-builder/issues/6520"
} }
} }

9
postcss.config.cjs Normal file
View File

@@ -0,0 +1,9 @@
const autoprefixer = require('autoprefixer');
module.exports = () => {
return {
plugins: [
autoprefixer(),
],
};
};

View File

@@ -1,5 +0,0 @@
module.exports = {
plugins: {
autoprefixer: {},
},
};

View File

@@ -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"

View File

@@ -8,7 +8,7 @@ class Paths {
constructor(selfDirectory) { constructor(selfDirectory) {
const projectRoot = resolve(selfDirectory, '../'); const projectRoot = resolve(selfDirectory, '../');
this.sourceImage = join(projectRoot, 'img/logo.svg'); this.sourceImage = join(projectRoot, 'img/logo.svg');
this.publicDirectory = join(projectRoot, 'public'); this.publicDirectory = join(projectRoot, 'src/presentation/public');
this.electronBuildDirectory = join(projectRoot, 'build'); this.electronBuildDirectory = join(projectRoot, 'build');
} }
@@ -61,7 +61,7 @@ async function updateDesktopIcons(sourceImage, electronIconsDir) {
await ensureFolderExists(electronIconsDir); await ensureFolderExists(electronIconsDir);
const temporaryDir = await mkdtemp('icon-'); const temporaryDir = await mkdtemp('icon-');
const temporaryPngFile = join(temporaryDir, 'icon.png'); const temporaryPngFile = join(temporaryDir, 'icon.png');
console.log(`Converting from SVG (${sourceImage}) to PNG: ${temporaryPngFile}`); // required by icon-builder console.log(`Converting from SVG (${sourceImage}) to PNG: ${temporaryPngFile}`); // required by `icon-builder`
await runCommand( await runCommand(
'npx', 'npx',
'svgexport', 'svgexport',

199
scripts/npm-install.js Normal file
View 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
View 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();

View 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();

View File

@@ -0,0 +1,62 @@
/**
* Description:
* This script checks if a server, provided as a CLI argument, is up
* and returns an HTTP 200 status code.
* It is designed to provide easy verification of server availability
* and will retry a specified number of times.
*
* Usage:
* node ./scripts/verify-web-server-status.js --url [URL]
*
* Options:
* --url URL of the server to check
*/
import { get } from 'http';
const MAX_RETRIES = 30;
const RETRY_DELAY_IN_SECONDS = 3;
const URL_PARAMETER_NAME = '--url';
function checkServer(currentRetryCount = 1) {
const serverUrl = getServerUrl();
console.log(`Requesting ${serverUrl}...`);
get(serverUrl, (res) => {
if (res.statusCode === 200) {
console.log('🎊 Success: The server is up and returned HTTP 200.');
process.exit(0);
} else {
console.log(`Server returned HTTP status code ${res.statusCode}.`);
retry(currentRetryCount);
}
}).on('error', (err) => {
console.error('Error making the request:', err);
retry(currentRetryCount);
});
}
function retry(currentRetryCount) {
console.log(`Attempt ${currentRetryCount}/${MAX_RETRIES}:`);
console.log(`Retrying in ${RETRY_DELAY_IN_SECONDS} seconds.`);
const remainingTime = (MAX_RETRIES - currentRetryCount) * RETRY_DELAY_IN_SECONDS;
console.log(`Time remaining before timeout: ${remainingTime}s`);
if (currentRetryCount < MAX_RETRIES) {
setTimeout(() => checkServer(currentRetryCount + 1), RETRY_DELAY_IN_SECONDS * 1000);
} else {
console.log('Failure: The server at did not return HTTP 200 within the allocated time. Exiting.');
process.exit(1);
}
}
function getServerUrl() {
const urlIndex = process.argv.indexOf(URL_PARAMETER_NAME);
if (urlIndex === -1 || urlIndex === process.argv.length - 1) {
console.error(`Parameter "${URL_PARAMETER_NAME}" is not provided.`);
process.exit(1);
}
return process.argv[urlIndex + 1];
}
checkServer();

16
src/TypeHelpers.ts Normal file
View File

@@ -0,0 +1,16 @@
export type Constructible<T, TArgs extends unknown[] = never> = {
prototype: T;
apply: (this: unknown, args: TArgs) => void;
readonly name: string;
};
export type PropertyKeys<T> = {
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? never : K;
}[keyof T];
export type ConstructorArguments<T> =
T extends new (...args: infer U) => unknown ? U : never;
export type FunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never;
}[keyof T];

View File

@@ -0,0 +1,50 @@
/*
Provides a unified and resilient way to extend errors across platforms.
Rationale:
- Babel:
> "Built-in classes cannot be properly subclassed due to limitations in ES5"
> https://web.archive.org/web/20230810014108/https://babeljs.io/docs/caveats#classes
- TypeScript:
> "Extending built-ins like Error, Array, and Map may no longer work"
> https://web.archive.org/web/20230810014143/https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
*/
export abstract class CustomError extends Error {
constructor(message?: string, options?: ErrorOptions) {
super(message, options);
fixPrototype(this, new.target.prototype);
ensureStackTrace(this);
this.name = this.constructor.name;
}
}
export const Environment = {
getSetPrototypeOf: () => Object.setPrototypeOf,
getCaptureStackTrace: () => Error.captureStackTrace,
};
function fixPrototype(target: Error, prototype: CustomError) {
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
const setPrototypeOf = Environment.getSetPrototypeOf();
if (!functionExists(setPrototypeOf)) {
return;
}
setPrototypeOf(target, prototype);
}
function ensureStackTrace(target: Error) {
const captureStackTrace = Environment.getCaptureStackTrace();
if (!functionExists(captureStackTrace)) {
// captureStackTrace is only available on V8, if it's not available
// modern JS engines will usually generate a stack trace on error objects when they're thrown.
return;
}
captureStackTrace(target, target.constructor);
}
function functionExists(func: unknown): boolean {
// Not doing truthy/falsy check i.e. if(func) as most values are truthy in JS for robustness
return typeof func === 'function';
}

View File

@@ -1,25 +1,23 @@
import { IApplicationContext } from '@/application/Context/IApplicationContext'; import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
import { Environment } from '../Environment/Environment'; import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
import { IEnvironment } from '../Environment/IEnvironment';
import { IApplicationFactory } from '../IApplicationFactory'; import { IApplicationFactory } from '../IApplicationFactory';
import { ApplicationFactory } from '../ApplicationFactory'; import { ApplicationFactory } from '../ApplicationFactory';
import { ApplicationContext } from './ApplicationContext'; import { ApplicationContext } from './ApplicationContext';
export async function buildContext( export async function buildContext(
factory: IApplicationFactory = ApplicationFactory.Current, factory: IApplicationFactory = ApplicationFactory.Current,
environment = Environment.CurrentEnvironment, environment = RuntimeEnvironment.CurrentEnvironment,
): Promise<IApplicationContext> { ): Promise<IApplicationContext> {
if (!factory) { throw new Error('missing factory'); } if (!factory) { throw new Error('missing factory'); }
if (!environment) { throw new Error('missing environment'); } if (!environment) { throw new Error('missing environment'); }
const app = await factory.getApp(); const app = await factory.getApp();
const os = getInitialOs(app, environment); const os = getInitialOs(app, environment.os);
return new ApplicationContext(app, os); return new ApplicationContext(app, os);
} }
function getInitialOs(app: IApplication, environment: IEnvironment): OperatingSystem { function getInitialOs(app: IApplication, currentOs: OperatingSystem): OperatingSystem {
const currentOs = environment.os;
const supportedOsList = app.getSupportedOsList(); const supportedOsList = app.getSupportedOsList();
if (supportedOsList.includes(currentOs)) { if (supportedOsList.includes(currentOs)) {
return currentOs; return currentOs;

View File

@@ -0,0 +1,4 @@
export enum FilterActionType {
Apply,
Clear,
}

View File

@@ -0,0 +1,37 @@
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { FilterActionType } from './FilterActionType';
import { IFilterChangeDetails, IFilterChangeDetailsVisitor } from './IFilterChangeDetails';
export class FilterChange implements IFilterChangeDetails {
public static forApply(filter: IFilterResult) {
if (!filter) {
throw new Error('missing filter');
}
return new FilterChange(FilterActionType.Apply, filter);
}
public static forClear() {
return new FilterChange(FilterActionType.Clear);
}
private constructor(
public readonly actionType: FilterActionType,
public readonly filter?: IFilterResult,
) { }
public visit(visitor: IFilterChangeDetailsVisitor): void {
if (!visitor) {
throw new Error('missing visitor');
}
switch (this.actionType) {
case FilterActionType.Apply:
visitor.onApply(this.filter);
break;
case FilterActionType.Clear:
visitor.onClear();
break;
default:
throw new Error(`Unknown action type: ${this.actionType}`);
}
}
}

View File

@@ -0,0 +1,14 @@
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { FilterActionType } from './FilterActionType';
export interface IFilterChangeDetails {
readonly actionType: FilterActionType;
readonly filter?: IFilterResult;
visit(visitor: IFilterChangeDetailsVisitor): void;
}
export interface IFilterChangeDetailsVisitor {
onClear(): void;
onApply(filter: IFilterResult): void;
}

View File

@@ -1,13 +1,13 @@
import { IEventSource } from '@/infrastructure/Events/IEventSource'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
import { IFilterResult } from './IFilterResult'; import { IFilterResult } from './IFilterResult';
import { IFilterChangeDetails } from './Event/IFilterChangeDetails';
export interface IReadOnlyUserFilter { export interface IReadOnlyUserFilter {
readonly currentFilter: IFilterResult | undefined; readonly currentFilter: IFilterResult | undefined;
readonly filtered: IEventSource<IFilterResult>; readonly filterChanged: IEventSource<IFilterChangeDetails>;
readonly filterRemoved: IEventSource<void>;
} }
export interface IUserFilter extends IReadOnlyUserFilter { export interface IUserFilter extends IReadOnlyUserFilter {
setFilter(filter: string): void; applyFilter(filter: string): void;
removeFilter(): void; clearFilter(): void;
} }

View File

@@ -4,11 +4,11 @@ import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { FilterResult } from './FilterResult'; import { FilterResult } from './FilterResult';
import { IFilterResult } from './IFilterResult'; import { IFilterResult } from './IFilterResult';
import { IUserFilter } from './IUserFilter'; import { IUserFilter } from './IUserFilter';
import { IFilterChangeDetails } from './Event/IFilterChangeDetails';
import { FilterChange } from './Event/FilterChange';
export class UserFilter implements IUserFilter { export class UserFilter implements IUserFilter {
public readonly filtered = new EventSource<IFilterResult>(); public readonly filterChanged = new EventSource<IFilterChangeDetails>();
public readonly filterRemoved = new EventSource<void>();
public currentFilter: IFilterResult | undefined; public currentFilter: IFilterResult | undefined;
@@ -16,9 +16,9 @@ export class UserFilter implements IUserFilter {
} }
public setFilter(filter: string): void { public applyFilter(filter: string): void {
if (!filter) { if (!filter) {
throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter'); throw new Error('Filter must be defined and not empty. Use clearFilter() to remove the filter');
} }
const filterLowercase = filter.toLocaleLowerCase(); const filterLowercase = filter.toLocaleLowerCase();
const filteredScripts = this.collection.getAllScripts().filter( const filteredScripts = this.collection.getAllScripts().filter(
@@ -33,12 +33,12 @@ export class UserFilter implements IUserFilter {
filter, filter,
); );
this.currentFilter = matches; this.currentFilter = matches;
this.filtered.notify(matches); this.filterChanged.notify(FilterChange.forApply(this.currentFilter));
} }
public removeFilter(): void { public clearFilter(): void {
this.currentFilter = undefined; this.currentFilter = undefined;
this.filterRemoved.notify(); this.filterChanged.notify(FilterChange.forClear());
} }
} }

View File

@@ -1,89 +0,0 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { BrowserOsDetector } from './BrowserOs/BrowserOsDetector';
import { IBrowserOsDetector } from './BrowserOs/IBrowserOsDetector';
import { IEnvironment } from './IEnvironment';
export interface IEnvironmentVariables {
readonly window: Window & typeof globalThis;
readonly process: NodeJS.Process;
readonly navigator: Navigator;
}
export class Environment implements IEnvironment {
public static readonly CurrentEnvironment: IEnvironment = new Environment({
window,
process: typeof process !== 'undefined' ? process /* electron only */ : undefined,
navigator,
});
public readonly isDesktop: boolean;
public readonly os: OperatingSystem;
protected constructor(
variables: IEnvironmentVariables,
browserOsDetector: IBrowserOsDetector = new BrowserOsDetector(),
) {
if (!variables) {
throw new Error('variables is null or empty');
}
this.isDesktop = isDesktop(variables);
if (this.isDesktop) {
this.os = getDesktopOsType(getProcessPlatform(variables));
} else {
const userAgent = getUserAgent(variables);
this.os = !userAgent ? undefined : browserOsDetector.detect(userAgent);
}
}
}
function getUserAgent(variables: IEnvironmentVariables): string {
if (!variables.window || !variables.window.navigator) {
return undefined;
}
return variables.window.navigator.userAgent;
}
function getProcessPlatform(variables: IEnvironmentVariables): string {
if (!variables.process || !variables.process.platform) {
return undefined;
}
return variables.process.platform;
}
function getDesktopOsType(processPlatform: string): OperatingSystem | undefined {
// https://nodejs.org/api/process.html#process_process_platform
switch (processPlatform) {
case 'darwin':
return OperatingSystem.macOS;
case 'win32':
return OperatingSystem.Windows;
case 'linux':
return OperatingSystem.Linux;
default:
return undefined;
}
}
function isDesktop(variables: IEnvironmentVariables): boolean {
// More: https://github.com/electron/electron/issues/2288
// Renderer process
if (variables.window
&& variables.window.process
&& variables.window.process.type === 'renderer') {
return true;
}
// Main process
if (variables.process
&& variables.process.versions
&& Boolean(variables.process.versions.electron)) {
return true;
}
// Detect the user agent when the `nodeIntegration` option is set to true
if (variables.navigator
&& variables.navigator.userAgent
&& variables.navigator.userAgent.includes('Electron')) {
return true;
}
return false;
}

View File

@@ -1,6 +0,0 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface IEnvironment {
readonly isDesktop: boolean;
readonly os: OperatingSystem;
}

View File

@@ -7,16 +7,19 @@ import MacOsData from '@/application/collections/macos.yaml';
import LinuxData from '@/application/collections/linux.yaml'; import LinuxData from '@/application/collections/linux.yaml';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser'; import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { Application } from '@/domain/Application'; import { Application } from '@/domain/Application';
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
import { parseCategoryCollection } from './CategoryCollectionParser'; import { parseCategoryCollection } from './CategoryCollectionParser';
export function parseApplication( export function parseApplication(
parser = CategoryCollectionParser, categoryParser = parseCategoryCollection,
processEnv: NodeJS.ProcessEnv = process.env, informationParser = parseProjectInformation,
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
collectionsData = PreParsedCollections, collectionsData = PreParsedCollections,
): IApplication { ): IApplication {
validateCollectionsData(collectionsData); validateCollectionsData(collectionsData);
const information = parseProjectInformation(processEnv); const information = informationParser(metadata);
const collections = collectionsData.map((collection) => parser(collection, information)); const collections = collectionsData.map((collection) => categoryParser(collection, information));
const app = new Application(information, collections); const app = new Application(information, collections);
return app; return app;
} }
@@ -24,16 +27,12 @@ export function parseApplication(
export type CategoryCollectionParserType export type CategoryCollectionParserType
= (file: CollectionData, info: IProjectInformation) => ICategoryCollection; = (file: CollectionData, info: IProjectInformation) => ICategoryCollection;
const CategoryCollectionParser: CategoryCollectionParserType = (file, info) => {
return parseCategoryCollection(file, info);
};
const PreParsedCollections: readonly CollectionData [] = [ const PreParsedCollections: readonly CollectionData [] = [
WindowsData, MacOsData, LinuxData, WindowsData, MacOsData, LinuxData,
]; ];
function validateCollectionsData(collections: readonly CollectionData[]) { function validateCollectionsData(collections: readonly CollectionData[]) {
if (!collections || !collections.length) { if (!collections?.length) {
throw new Error('missing collections'); throw new Error('missing collections');
} }
if (collections.some((collection) => !collection)) { if (collections.some((collection) => !collection)) {

View File

@@ -1,11 +1,10 @@
import { CustomError } from '@/application/Common/CustomError';
import { NodeType } from './NodeType'; import { NodeType } from './NodeType';
import { NodeData } from './NodeData'; import { NodeData } from './NodeData';
export class NodeDataError extends Error { export class NodeDataError extends CustomError {
constructor(message: string, public readonly context: INodeDataErrorContext) { constructor(message: string, public readonly context: INodeDataErrorContext) {
super(createMessage(message, context)); super(createMessage(message, context));
Object.setPrototypeOf(this, new.target.prototype); // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
this.name = new.target.name;
} }
} }

View File

@@ -1,28 +1,29 @@
import { IProjectInformation } from '@/domain/IProjectInformation'; import { IProjectInformation } from '@/domain/IProjectInformation';
import { ProjectInformation } from '@/domain/ProjectInformation'; import { ProjectInformation } from '@/domain/ProjectInformation';
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
import { Version } from '@/domain/Version'; import { Version } from '@/domain/Version';
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
import { ConstructorArguments } from '@/TypeHelpers';
export function parseProjectInformation( export function
environment: NodeJS.ProcessEnv | VueAppEnvironment, parseProjectInformation(
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
createProjectInformation: ProjectInformationFactory = (
...args
) => new ProjectInformation(...args),
): IProjectInformation { ): IProjectInformation {
const version = new Version(environment[VueAppEnvironmentKeys.VUE_APP_VERSION]); const version = new Version(
return new ProjectInformation( metadata.version,
environment[VueAppEnvironmentKeys.VUE_APP_NAME], );
return createProjectInformation(
metadata.name,
version, version,
environment[VueAppEnvironmentKeys.VUE_APP_SLOGAN], metadata.slogan,
environment[VueAppEnvironmentKeys.VUE_APP_REPOSITORY_URL], metadata.repositoryUrl,
environment[VueAppEnvironmentKeys.VUE_APP_HOMEPAGE_URL], metadata.homepageUrl,
); );
} }
export const VueAppEnvironmentKeys = { export type ProjectInformationFactory = (
VUE_APP_VERSION: 'VUE_APP_VERSION', ...args: ConstructorArguments<typeof ProjectInformation>
VUE_APP_NAME: 'VUE_APP_NAME', ) => IProjectInformation;
VUE_APP_SLOGAN: 'VUE_APP_SLOGAN',
VUE_APP_REPOSITORY_URL: 'VUE_APP_REPOSITORY_URL',
VUE_APP_HOMEPAGE_URL: 'VUE_APP_HOMEPAGE_URL',
} as const;
export type VueAppEnvironment = {
[K in keyof typeof VueAppEnvironmentKeys]: string;
};

View File

@@ -14,45 +14,44 @@ export class ExpressionRegexBuilder {
.addRawRegex('\\s+'); .addRawRegex('\\s+');
} }
public matchPipeline() { public captureOptionalPipeline() {
return this return this
.expectZeroOrMoreWhitespaces() .addRawRegex('((?:\\|\\s*\\b[a-zA-Z]+\\b\\s*)*)');
.addRawRegex('(\\|\\s*.+?)?');
} }
public matchUntilFirstWhitespace() { public captureUntilWhitespaceOrPipe() {
return this return this
.addRawRegex('([^|\\s]+)'); .addRawRegex('([^|\\s]+)');
} }
public matchMultilineAnythingExceptSurroundingWhitespaces() { public captureMultilineAnythingExceptSurroundingWhitespaces() {
return this return this
.expectZeroOrMoreWhitespaces() .expectOptionalWhitespaces()
.addRawRegex('([\\S\\s]+?)') .addRawRegex('([\\s\\S]*\\S)')
.expectZeroOrMoreWhitespaces(); .expectOptionalWhitespaces();
} }
public expectExpressionStart() { public expectExpressionStart() {
return this return this
.expectCharacters('{{') .expectCharacters('{{')
.expectZeroOrMoreWhitespaces(); .expectOptionalWhitespaces();
} }
public expectExpressionEnd() { public expectExpressionEnd() {
return this return this
.expectZeroOrMoreWhitespaces() .expectOptionalWhitespaces()
.expectCharacters('}}'); .expectCharacters('}}');
} }
public expectOptionalWhitespaces() {
return this
.addRawRegex('\\s*');
}
public buildRegExp(): RegExp { public buildRegExp(): RegExp {
return new RegExp(this.parts.join(''), 'g'); return new RegExp(this.parts.join(''), 'g');
} }
private expectZeroOrMoreWhitespaces() {
return this
.addRawRegex('\\s*');
}
private addRawRegex(regex: string) { private addRawRegex(regex: string) {
this.parts.push(regex); this.parts.push(regex);
return this; return this;

View File

@@ -6,8 +6,9 @@ export class ParameterSubstitutionParser extends RegexParser {
protected readonly regex = new ExpressionRegexBuilder() protected readonly regex = new ExpressionRegexBuilder()
.expectExpressionStart() .expectExpressionStart()
.expectCharacters('$') .expectCharacters('$')
.matchUntilFirstWhitespace() // First match: Parameter name .captureUntilWhitespaceOrPipe() // First capture: Parameter name
.matchPipeline() // Second match: Pipeline .expectOptionalWhitespaces()
.captureOptionalPipeline() // Second capture: Pipeline
.expectExpressionEnd() .expectExpressionEnd()
.buildRegExp(); .buildRegExp();

View File

@@ -1,59 +1,222 @@
// eslint-disable-next-line max-classes-per-file
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter'; import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
import { RegexParser, IPrimitiveExpression } from '../Parser/Regex/RegexParser'; import { IExpression } from '../Expression/IExpression';
import { ExpressionPosition } from '../Expression/ExpressionPosition';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder'; import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
export class WithParser extends RegexParser { export class WithParser implements IExpressionParser {
protected readonly regex = new ExpressionRegexBuilder() public findExpressions(code: string): IExpression[] {
// {{ with $parameterName }} if (!code) {
.expectExpressionStart() throw new Error('missing code');
.expectCharacters('with') }
.expectOneOrMoreWhitespaces() return parseWithExpressions(code);
.expectCharacters('$') }
.matchUntilFirstWhitespace() // First match: parameter name }
.expectExpressionEnd()
// ...
.matchMultilineAnythingExceptSurroundingWhitespaces() // Second match: Scope text
// {{ end }}
.expectExpressionStart()
.expectCharacters('end')
.expectExpressionEnd()
.buildRegExp();
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression { enum WithStatementType {
const parameterName = match[1]; Start,
const scopeText = match[2]; End,
ContextVariable,
}
type WithStatement = {
readonly type: WithStatementType.Start;
readonly parameterName: string;
readonly position: ExpressionPosition;
} | {
readonly type: WithStatementType.End;
readonly position: ExpressionPosition;
} | {
readonly type: WithStatementType.ContextVariable;
readonly position: ExpressionPosition;
readonly pipeline: string | undefined;
};
function parseAllWithExpressions(
input: string,
): WithStatement[] {
const expressions = new Array<WithStatement>();
for (const match of input.matchAll(WithStatementStartRegEx)) {
expressions.push({
type: WithStatementType.Start,
parameterName: match[1],
position: createPosition(match),
});
}
for (const match of input.matchAll(WithStatementEndRegEx)) {
expressions.push({
type: WithStatementType.End,
position: createPosition(match),
});
}
for (const match of input.matchAll(ContextVariableWithPipelineRegEx)) {
expressions.push({
type: WithStatementType.ContextVariable,
position: createPosition(match),
pipeline: match[1],
});
}
return expressions;
}
function createPosition(match: RegExpMatchArray): ExpressionPosition {
const startPos = match.index;
const endPos = startPos + match[0].length;
return new ExpressionPosition(startPos, endPos);
}
class WithStatementBuilder {
private readonly contextVariables = new Array<{
readonly positionInScope: ExpressionPosition;
readonly pipeline: string | undefined;
}>();
public addContextVariable(
absolutePosition: ExpressionPosition,
pipeline: string | undefined,
): void {
const positionInScope = new ExpressionPosition(
absolutePosition.start - this.startExpressionPosition.end,
absolutePosition.end - this.startExpressionPosition.end,
);
this.contextVariables.push({
positionInScope,
pipeline,
});
}
public buildExpression(endExpressionPosition: ExpressionPosition, input: string): IExpression {
const parameters = new FunctionParameterCollection();
parameters.addParameter(new FunctionParameter(this.parameterName, true));
const position = new ExpressionPosition(
this.startExpressionPosition.start,
endExpressionPosition.end,
);
const scope = input.substring(this.startExpressionPosition.end, endExpressionPosition.start);
return { return {
parameters: [new FunctionParameter(parameterName, true)], parameters,
evaluator: (context) => { position,
const argumentValue = context.args.hasArgument(parameterName) evaluate: (context) => {
? context.args.getArgument(parameterName).argumentValue const argumentValue = context.args.hasArgument(this.parameterName)
? context.args.getArgument(this.parameterName).argumentValue
: undefined; : undefined;
if (!argumentValue) { if (!argumentValue) {
return ''; return '';
} }
return replaceEachScopeSubstitution(scopeText, (pipeline) => { const substitutedScope = this.substituteContextVariables(scope, (pipeline) => {
if (!pipeline) { if (!pipeline) {
return argumentValue; return argumentValue;
} }
return context.pipelineCompiler.compile(argumentValue, pipeline); return context.pipelineCompiler.compile(argumentValue, pipeline);
}); });
return substitutedScope;
}, },
}; };
} }
constructor(
private readonly startExpressionPosition: ExpressionPosition,
private readonly parameterName: string,
) {
}
private substituteContextVariables(
scope: string,
substituter: (pipeline: string) => string,
): string {
if (!this.contextVariables.length) {
return scope;
}
let substitutedScope = '';
let scopeSubstrIndex = 0;
for (const contextVariable of this.contextVariables) {
substitutedScope += scope.substring(scopeSubstrIndex, contextVariable.positionInScope.start);
substitutedScope += substituter(contextVariable.pipeline);
scopeSubstrIndex = contextVariable.positionInScope.end;
}
substitutedScope += scope.substring(scopeSubstrIndex, scope.length);
return substitutedScope;
}
} }
const ScopeSubstitutionRegEx = new ExpressionRegexBuilder() function buildErrorContext(code: string, statements: readonly WithStatement[]): string {
const formattedStatements = statements.map((s) => `- [${s.position.start}, ${s.position.end}] ${WithStatementType[s.type]}`).join('\n');
return [
'Code:', '---', code, '---',
'nStatements:', '---', formattedStatements, '---',
].join('\n');
}
function parseWithExpressions(input: string): IExpression[] {
const allStatements = parseAllWithExpressions(input);
const sortedStatements = allStatements
.slice()
.sort((a, b) => b.position.start - a.position.start);
const expressions = new Array<IExpression>();
const builders = new Array<WithStatementBuilder>();
const throwWithContext = (message: string) => {
throw new Error(`${message}\n${buildErrorContext(input, allStatements)}}`);
};
while (sortedStatements.length > 0) {
const statement = sortedStatements.pop();
if (!statement) {
break;
}
switch (statement.type) { // eslint-disable-line default-case
case WithStatementType.Start:
builders.push(new WithStatementBuilder(
statement.position,
statement.parameterName,
));
break;
case WithStatementType.ContextVariable:
if (builders.length === 0) {
throwWithContext('Context variable before `with` statement.');
}
builders[builders.length - 1].addContextVariable(statement.position, statement.pipeline);
break;
case WithStatementType.End:
if (builders.length === 0) {
throwWithContext('Redundant `end` statement, missing `with`?');
}
expressions.push(builders.pop().buildExpression(statement.position, input));
break;
}
}
if (builders.length > 0) {
throwWithContext('Missing `end` statement, forgot `{{ end }}?');
}
return expressions;
}
const ContextVariableWithPipelineRegEx = new ExpressionRegexBuilder()
// {{ . | pipeName }} // {{ . | pipeName }}
.expectExpressionStart() .expectExpressionStart()
.expectCharacters('.') .expectCharacters('.')
.matchPipeline() // First match: pipeline .expectOptionalWhitespaces()
.captureOptionalPipeline() // First capture: pipeline
.expectExpressionEnd() .expectExpressionEnd()
.buildRegExp(); .buildRegExp();
function replaceEachScopeSubstitution(scopeText: string, replacer: (pipeline: string) => string) { const WithStatementStartRegEx = new ExpressionRegexBuilder()
// Not using /{{\s*.\s*(?:(\|\s*[^{}]*?)\s*)?}}/g for not matching brackets, // {{ with $parameterName }}
// but instead letting the pipeline compiler to fail on those. .expectExpressionStart()
return scopeText.replaceAll(ScopeSubstitutionRegEx, (_$, match1) => { .expectCharacters('with')
return replacer(match1); .expectOneOrMoreWhitespaces()
}); .expectCharacters('$')
} .captureUntilWhitespaceOrPipe() // First capture: parameter name
.expectExpressionEnd()
.expectOptionalWhitespaces()
.buildRegExp();
const WithStatementEndRegEx = new ExpressionRegexBuilder()
// {{ end }}
.expectOptionalWhitespaces()
.expectExpressionStart()
.expectCharacters('end')
.expectOptionalWhitespaces()
.expectExpressionEnd()
.buildRegExp();

View File

@@ -0,0 +1,5 @@
import { CompiledCode } from '../CompiledCode';
export interface CodeSegmentMerger {
mergeCodeParts(codeSegments: readonly CompiledCode[]): CompiledCode;
}

View File

@@ -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');
}

View File

@@ -1,4 +1,4 @@
export interface ICompiledCode { export interface CompiledCode {
readonly code: string; readonly code: string;
readonly revertCode?: string; readonly revertCode?: string;
} }

View File

@@ -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;
}

View File

@@ -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'),
);
} }

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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'),
);
}

View File

@@ -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[];
}

View File

@@ -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[],
}

View File

@@ -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;
}

View File

@@ -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());
}

View File

@@ -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),
},
];
}
}

View File

@@ -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();
}
}

View File

@@ -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');
}
}
} }

View File

@@ -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(

View File

@@ -1,6 +0,0 @@
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
export interface IFunctionCall {
readonly functionName: string;
readonly args: IReadOnlyFunctionCallArgumentCollection;
}

View File

@@ -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');
}
}
}

View File

@@ -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 {

View File

@@ -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[];
} }

View File

@@ -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,
}; };
} }
} }

View File

@@ -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);
} }

View File

@@ -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()]),
); );

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
# Structure documented in "docs/collection-files.md" # Structure is documented in "docs/collection-files.md"
os: macos os: macos
scripting: scripting:
language: shellscript language: shellscript
@@ -48,18 +48,18 @@ actions:
# on main HDD # on main HDD
sudo rm -rfv ~/.Trash/* &>/dev/null sudo rm -rfv ~/.Trash/* &>/dev/null
- -
name: Clear system cache files name: Clear system cache
recommend: strict recommend: strict
code: |- code: |-
sudo rm -rfv /Library/Caches/* &>/dev/null sudo rm -rfv /Library/Caches/* &>/dev/null
sudo rm -rfv /System/Library/Caches/* &>/dev/null sudo rm -rfv /System/Library/Caches/* &>/dev/null
sudo rm -rfv ~/Library/Caches/* &>/dev/null sudo rm -rfv ~/Library/Caches/* &>/dev/null
- -
category: Clear OS logs category: Clear operating system logs
recommend: strict recommend: strict
children: children:
- -
category: Clear unified logs (diagnostics) category: Clear unified diagnostic logs
docs: https://developer.apple.com/documentation/os/logging docs: https://developer.apple.com/documentation/os/logging
children: children:
- -
@@ -69,7 +69,7 @@ actions:
sudo rm -rfv /private/var/db/diagnostics/* sudo rm -rfv /private/var/db/diagnostics/*
sudo rm -rfv /var/db/diagnostics/* sudo rm -rfv /var/db/diagnostics/*
- -
name: Clear shared-cache strings data name: Clear shared cache strings data
docs: docs:
- https://eclecticlight.co/2017/09/23/sierras-unified-log-evolves-more-persistent-and-a-valuable-log-log/ - https://eclecticlight.co/2017/09/23/sierras-unified-log-evolves-more-persistent-and-a-valuable-log-log/
- https://github.com/privacysexy-forks/dtformats/blob/main/documentation/Apple%20Unified%20Logging%20and%20Activity%20Tracing%20formats.asciidoc - https://github.com/privacysexy-forks/dtformats/blob/main/documentation/Apple%20Unified%20Logging%20and%20Activity%20Tracing%20formats.asciidoc
@@ -77,7 +77,7 @@ actions:
sudo rm -rfv /private/var/db/uuidtext/ sudo rm -rfv /private/var/db/uuidtext/
sudo rm -rfv /var/db/uuidtext/ sudo rm -rfv /var/db/uuidtext/
- -
category: Clear system logs (/var/log/) category: Clear system logs
children: children:
- -
name: Clear Apple System Logs (ASL) name: Clear Apple System Logs (ASL)
@@ -94,7 +94,7 @@ actions:
docs: https://discussions.apple.com/thread/1829842 docs: https://discussions.apple.com/thread/1829842
code: sudo rm -fv /var/log/install.log code: sudo rm -fv /var/log/install.log
- -
name: Clear all system logs name: Clear all system logs in `/var/log/` directory
docs: https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/ docs: https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
code: sudo rm -rfv /var/log/* # Clears including /var/log/system.log code: sudo rm -rfv /var/log/* # Clears including /var/log/system.log
- -
@@ -105,7 +105,7 @@ actions:
name: Clear Mail logs name: Clear Mail logs
code: rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/* code: rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/*
- -
name: Clear audit logs (login, logout, authentication and other user activity) name: Clear user activity audit logs (login, logout, authentication, etc.)
docs: docs:
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf - https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
- http://macadmins.psu.edu/wp-content/uploads/sites/24696/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf - http://macadmins.psu.edu/wp-content/uploads/sites/24696/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
@@ -113,7 +113,7 @@ actions:
sudo rm -rfv /var/audit/* sudo rm -rfv /var/audit/*
sudo rm -rfv /private/var/audit/* sudo rm -rfv /private/var/audit/*
- -
name: Clear user logs (user reports) name: Clear user report logs
docs: docs:
- https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/ - https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
- https://apple.stackexchange.com/questions/272929/is-it-safe-to-delete-the-content-of-library-logs - https://apple.stackexchange.com/questions/272929/is-it-safe-to-delete-the-content-of-library-logs
@@ -134,15 +134,15 @@ actions:
category: Clear browser history category: Clear browser history
children: children:
- -
category: Clear Google Chrome history category: Clear Chrome history
children: children:
- -
name: Clear Google Chrome browsing history name: Clear Chrome browsing history
code: |- code: |-
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History &>/dev/null rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History &>/dev/null
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History-journal &>/dev/null rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History-journal &>/dev/null
- -
name: Google Chrome Cache Files name: Clear Chrome cache
code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null
- -
category: Clear Safari history category: Clear Safari history
@@ -165,7 +165,7 @@ actions:
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/Downloads.plist code: rm -f ~/Library/Safari/Downloads.plist
- -
name: Clear Safari top sites name: Clear Safari frequently visited sites
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/ docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
code: rm -f ~/Library/Safari/TopSites.plist code: rm -f ~/Library/Safari/TopSites.plist
- -
@@ -182,7 +182,7 @@ actions:
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/ docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
code: rm -f ~/Library/Caches/com.apple.Safari/Cache.db code: rm -f ~/Library/Caches/com.apple.Safari/Cache.db
- -
name: Clear Safari web page icons displayed on URL bar name: Clear Safari URL bar web page icons
docs: docs:
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/ - https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
- https://lifehacker.com/safaris-private-browsing-mode-saves-urls-in-an-easily-a-1691944343 - https://lifehacker.com/safaris-private-browsing-mode-saves-urls-in-an-easily-a-1691944343
@@ -194,11 +194,11 @@ actions:
- https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/ - https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/
code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews
- -
name: Clear copy of the Safari history name: Clear Safari history copy
docs: https://forensicsfromthesausagefactory.blogspot.com/2010/06/safari-history-spotlight-webhistory.html docs: https://forensicsfromthesausagefactory.blogspot.com/2010/06/safari-history-spotlight-webhistory.html
code: rm -rfv ~/Library/Caches/Metadata/Safari/History code: rm -rfv ~/Library/Caches/Metadata/Safari/History
- -
name: Clear search history embedded in Safari preferences name: Clear search term history embedded in Safari preferences
docs: https://krypted.com/tag/recentsearchstrings/ docs: https://krypted.com/tag/recentsearchstrings/
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )' code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
- -
@@ -215,11 +215,11 @@ actions:
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plist code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plist
- -
name: Clear URLs that are allowed to display notifications in Safari name: Clear allowed URLs for Safari notifications
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
- -
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays name: Clear Safari preferences for downloads, geolocation, pop-ups, and autoplay per site
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/PerSitePreferences.db code: rm -f ~/Library/Safari/PerSitePreferences.db
- -
@@ -231,15 +231,15 @@ actions:
sudo rm -rf ~/Library/Caches/Mozilla/ sudo rm -rf ~/Library/Caches/Mozilla/
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite
- -
name: Delete Firefox form history name: Clear Firefox form history
code: |- code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.sqlite rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.dat rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.dat
- -
name: Delete Firefox site preferences name: Clear Firefox site preferences
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/content-prefs.sqlite code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/content-prefs.sqlite
- -
name: Delete Firefox session restore data (loads after the browser closes or crashes) name: Clear Firefox session restore data (loads after the browser closes or crashes)
code: |- code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionCheckpoints.json rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionCheckpoints.json
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore*.js* rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore*.js*
@@ -250,7 +250,7 @@ actions:
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.bak* rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.bak*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20* rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
- -
name: Delete Firefox passwords name: Clear Firefox passwords
docs: https://web.archive.org/web/20210425202923/http://kb.mozillazine.org/Password_Manager docs: https://web.archive.org/web/20210425202923/http://kb.mozillazine.org/Password_Manager
code: |- code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
@@ -259,20 +259,20 @@ actions:
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json
- -
name: Delete Firefox HTML5 cookies name: Clear Firefox HTML5 cookies
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite
- -
name: Delete Firefox crash reports name: Clear Firefox crash reports
code: |- code: |-
rm -rfv ~/Library/Application\ Support/Firefox/Crash\ Reports/ rm -rfv ~/Library/Application\ Support/Firefox/Crash\ Reports/
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/minidumps/*.dmp rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/minidumps/*.dmp
- -
name: Delete Firefox backup files name: Clear Firefox backup files
code: |- code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.json rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.json
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.jsonlz4 rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.jsonlz4
- -
name: Delete Firefox cookies name: Clear Firefox cookies
code: |- code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite
@@ -280,7 +280,7 @@ actions:
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-wal rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-wal
rm -rfv ~/Library/Application\ Support/Firefox/Profiles/*/storage/default/http* rm -rfv ~/Library/Application\ Support/Firefox/Profiles/*/storage/default/http*
- -
category: Clear third party application data category: Clear third-party application data
children: children:
- -
name: Clear Adobe cache name: Clear Adobe cache
@@ -290,18 +290,18 @@ actions:
name: Clear Gradle cache name: Clear Gradle cache
recommend: strict recommend: strict
code: |- code: |-
if [ -d "/Users/${HOST}/.gradle/caches" ]; then if [ -d "~/.gradle/caches" ]; then
rm -rfv ~/.gradle/caches/ &> /dev/null rm -rfv ~/.gradle/caches/ &> /dev/null
fi fi
- -
name: Clear Dropbox cache name: Clear Dropbox cache
recommend: standard recommend: standard
code: |- code: |-
if [ -d "/Users/${HOST}/Dropbox" ]; then if [ -d "~/Dropbox/.dropbox.cache" ]; then
sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null
fi fi
- -
name: Clear Google Drive file stream cache name: Clear Google Drive File Stream cache
recommend: standard recommend: standard
code: |- code: |-
killall "Google Drive File Stream" killall "Google Drive File Stream"
@@ -323,21 +323,54 @@ actions:
brew tap --repair &>/dev/null brew tap --repair &>/dev/null
fi fi
- -
name: Clear any old versions of Ruby gems name: Clear old Ruby gem versions
recommend: strict recommend: strict
code: |- code: |-
if type "gem" &> /dev/null; then if type "gem" &> /dev/null; then
gem cleanup &>/dev/null gem cleanup &>/dev/null
fi fi
- -
name: Clear Docker name: Clear unused Docker data
recommend: strict recommend: strict
docs: |-
This script frees up disk space, but also improves user privacy by:
1. **Removal of stopped containers**: Containers often run applications or services that might process sensitive
or personal data. Even if a container is stopped, its filesystem remains intact, and potentially sensitive data inside
it can be accessed. By removing stopped containers, we eliminate this potential privacy risk.
2. **Deletion of unused images**: Images can sometimes contain sensitive information, especially if they were built
from `Dockerfile`s that copied local files or were used in scenarios where sensitive data was processed. Deleting unused
images ensures that any inadvertent sensitive information embedded in those images is eradicated.
3. **Cleanup of network configurations**: Networks, especially custom ones, can contain configurations that reveal details
about system architecture, inter-container communication, or even hardcoded secrets. Removing unused networks mitigates
risks associated with lingering, outdated, or insecure configurations.
4. **Elimination of build cache**: The Docker build process uses a cache to speed up image creation. This cache can contain
remnants of previous builds, including potentially sensitive data or files. Pruning the build cache ensures that these remnants
are deleted, further safeguarding privacy.
5. **Footprint reduction**: By consistently pruning unused Docker objects, the overall footprint of Docker on the system is
reduced. This makes it harder for malicious actors to exploit any lingering or overlooked vulnerabilities in the system or Docker
itself.
This script runs `docker system prune -af` command to clean up unused Docker data [1].
Specifically, the command will [1]:
- Remove all stopped containers.
- Remove all networks not used by at least one container.
- Remove all images not used by any container.
- Remove all build cache.
[1]: https://web.archive.org/web/20230810171526/https://docs.docker.com/engine/reference/commandline/system_prune/ "docker system prune | Docker Documentation"
code: |- code: |-
if type "docker" &> /dev/null; then if type "docker" &> /dev/null; then
docker system prune -af docker system prune -af
fi fi
- -
name: Clear Pyenv-VirtualEnv cache name: Clear Pyenv-Virtualenv cache
recommend: strict recommend: strict
code: |- code: |-
if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then
@@ -359,22 +392,22 @@ actions:
yarn cache clean --force yarn cache clean --force
fi fi
- -
category: iOS Cleanup category: Clear iOS usage data
children: children:
- -
name: Clear iOS applications name: Clear iOS app copies from iTunes
recommend: strict recommend: strict
code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null
- -
name: Clear iOS photo caches name: Clear iOS photo cache
recommend: standard recommend: standard
code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/* code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/*
- -
name: Remove iOS Device Backups name: Clear iOS Device Backups
recommend: strict recommend: strict
code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null
- -
name: Clear iOS Simulators name: Clear iOS simulators
recommend: strict recommend: strict
code: |- code: |-
if type "xcrun" &>/dev/null; then if type "xcrun" &>/dev/null; then
@@ -385,7 +418,7 @@ actions:
xcrun simctl erase all xcrun simctl erase all
fi fi
- -
name: Clear the list of iOS devices connected name: Clear list of connected iOS devices
recommend: strict recommend: strict
code: |- code: |-
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect" sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
@@ -394,7 +427,7 @@ actions:
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
sudo rm -rfv /var/db/lockdown/* sudo rm -rfv /var/db/lockdown/*
- -
name: Clear XCode Derived Data and Archives name: Clear Xcode's derived data and archives
recommend: strict recommend: strict
code: |- code: |-
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null
@@ -407,50 +440,50 @@ actions:
sudo dscacheutil -flushcache sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder sudo killall -HUP mDNSResponder
- -
name: Purge inactive memory name: Clear inactive memory
recommend: standard recommend: standard
code: sudo purge code: sudo purge
- -
category: Reset privacy permissions for all applications category: Clear all privacy permissions for applications
children: children:
- -
name: Reset camera permissions name: Clear "camera" permissions
code: tccutil reset Camera code: tccutil reset Camera
- -
name: Reset microphone permissions name: Clear "microphone" permissions
code: tccutil reset Microphone code: tccutil reset Microphone
- -
name: Reset accessibility permissions name: Clear "accessibility" permissions
code: tccutil reset Accessibility code: tccutil reset Accessibility
- -
name: Reset screen capture permissions name: Clear "screen capture" permissions
code: tccutil reset ScreenCapture code: tccutil reset ScreenCapture
- -
name: Reset reminders permissions name: Clear "reminders" permissions
code: tccutil reset Reminders code: tccutil reset Reminders
- -
name: Reset photos permissions name: Clear "photos" permissions
code: tccutil reset Photos code: tccutil reset Photos
- -
name: Reset calendar permissions name: Clear "calendar" permissions
code: tccutil reset Calendar code: tccutil reset Calendar
- -
name: Reset full disk access permissions name: Clear "full disk access" permissions
code: tccutil reset SystemPolicyAllFiles code: tccutil reset SystemPolicyAllFiles
- -
name: Reset contacts permissions name: Clear "contacts" permissions
code: tccutil reset SystemPolicyAllFiles code: tccutil reset SystemPolicyAllFiles
- -
name: Reset desktop folder permissions name: Clear "desktop folder" permissions
code: tccutil reset SystemPolicyDesktopFolder code: tccutil reset SystemPolicyDesktopFolder
- -
name: Reset documents folder permissions name: Clear "documents folder" permissions
code: tccutil reset SystemPolicyDocumentsFolder code: tccutil reset SystemPolicyDocumentsFolder
- -
name: Reset downloads permissions name: Clear "downloads" permissions
code: tccutil reset SystemPolicyDownloadsFolder code: tccutil reset SystemPolicyDownloadsFolder
- -
name: Reset all app permissions name: Clear all app permissions
code: tccutil reset All code: tccutil reset All
- -
category: Configure programs category: Configure programs
@@ -468,20 +501,20 @@ actions:
sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled
sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry
- -
name: Disable Microsoft Office diagnostics data sending name: Disable Microsoft Office telemetry
recommend: standard recommend: standard
code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData
revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference
- -
name: Uninstall Google update name: Remove Google Software Update service
recommend: strict recommend: strict
code: |- code: |-
googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall
if [ -f "$googleUpdateFile" ]; then if [ -f "$googleUpdateFile" ]; then
$googleUpdateFile --nuke $googleUpdateFile --nuke
echo Uninstalled google update echo 'Uninstalled Google update'
else else
echo Google update file does not exist echo 'Google update file does not exist'
fi fi
- -
name: Disable Homebrew user behavior analytics name: Disable Homebrew user behavior analytics
@@ -514,12 +547,12 @@ actions:
docs: |- docs: |-
Parallels Desktop for Mac is software providing hardware virtualization for macOS [1]. Parallels Desktop for Mac is software providing hardware virtualization for macOS [1].
When you use it, it collects and share your personal data to third parties [2]. Personal When you use it, it collects and shares your personal data to third parties [2]. Personal
data include IP address of your device, your broad geographical location (country, state data include IP address of your device, your broad geographical location (country, state
(if applicable), and city) and used product [2]. (if applicable), and city) and used product [2].
It includes third-party ads [3] and automatic check for updates [4] by default. Both of these It includes third-party advertisements [3] and automatic check for updates [4] by default.
behaviors communicate with online services that reveal data about you. Both of these behaviors communicate with online services that reveal personal data about you.
[1]: https://web.archive.org/web/20221012155943/https://en.wikipedia.org/wiki/Parallels_Desktop_for_Mac "Parallels Desktop for Mac - Wikipedia | en.wikipedia.org" [1]: https://web.archive.org/web/20221012155943/https://en.wikipedia.org/wiki/Parallels_Desktop_for_Mac "Parallels Desktop for Mac - Wikipedia | en.wikipedia.org"
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com" [2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
@@ -527,7 +560,7 @@ actions:
[4]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com" [4]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
children: children:
- -
name: Turn off ads in Parallels Desktop name: Disable Parallels Desktop advertisements
recommend: standard recommend: standard
docs: |- docs: |-
Parallels Desktop in-product notifications to show ads from Parallels or other third Parallels Desktop in-product notifications to show ads from Parallels or other third
@@ -544,7 +577,7 @@ actions:
default). It's undocumented but still kept disabled by this script. default). It's undocumented but still kept disabled by this script.
[1]: https://web.archive.org/save/https://forum.parallels.com/threads/unable-to-process-the-upgrade-request.345603/ "Unable to process the upgrade request | Parallels Forums | forum.parallels.com" [1]: https://web.archive.org/save/https://forum.parallels.com/threads/unable-to-process-the-upgrade-request.345603/ "Unable to process the upgrade request | Parallels Forums | forum.parallels.com"
[2]: https://web.archive.org/web/20221012151800/https://kb.parallels.com/114422 "How do I turn off notifications in Parallels Desktop and Parallels Access? | Knowledge Base | parallels.com" [2]: https://web.archive.org/web/20221012151800/https://kb.parallels.com/114422 "How do I turn off notifications in Parallels Desktop and Parallels Access? | Knowledge Base | parallels.com"
code: |- code: |-
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool yes defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool yes
defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes
@@ -552,16 +585,16 @@ actions:
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool no defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool no
defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes
- -
category: Disable Parallels Desktop auto-updates category: Disable Parallels Desktop automatic updates
docs: |- docs: |-
Parallels Desktop by default checks for updates frequently and automatically downloads them [1]. Parallels Desktop by default checks for updates frequently and automatically downloads them [1].
This reveal personal data about [2] you without your control. This reveal personal data about you [2] without your control.
[1]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com" [1]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com" [2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
children: children:
- -
name: Disable automatically downloading Parallels Desktop updates name: Disable automatic downloads for Parallels Desktop updates
docs: |- docs: |-
Automatic downloads are enabled by default, and this script disables automatic downloads. Automatic downloads are enabled by default, and this script disables automatic downloads.
@@ -570,11 +603,11 @@ actions:
- Check: `defaults read 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically'` - Check: `defaults read 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically'`
- Values: 0 - Disabled, 1 - Enabled (default) - Values: 0 - Disabled, 1 - Enabled (default)
[1]: https://web.archive.org/web/20221012153810/https://download.parallels.com/desktop/v18/docs/en_US/Parallels-Desktop-Business-Edition-Administrators-Guide/37744.htm "Parallels Desktop Business Edition Administrator's Guide v18 - Configuring individual Macs | download.parallels.com" [1]: https://web.archive.org/web/20221012153810/https://download.parallels.com/desktop/v18/docs/en_US/Parallels-Desktop-Business-Edition-Administrators-Guide/37744.htm "Parallels Desktop Business Edition Administrator's Guide v18 - Configuring individual Macs | download.parallels.com"
code: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool no code: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool no
revertCode: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool yes revertCode: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool yes
- -
name: Disable automatically checking for Parallels Desktop updates name: Disable automatic checks for Parallels Desktop updates
docs: |- docs: |-
Automatic checks are weekly by default, and this script disables the checks completely. Automatic checks are weekly by default, and this script disables the checks completely.
@@ -593,7 +626,7 @@ actions:
category: Configure Apple Remote Desktop category: Configure Apple Remote Desktop
children: children:
- -
name: Deactivate the Remote Management Service name: Disable remote management service
recommend: strict recommend: strict
code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop
revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console
@@ -604,26 +637,26 @@ actions:
sudo rm -rf /var/db/RemoteManagement sudo rm -rf /var/db/RemoteManagement
sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist
defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist
sudo rm -r /Library/Application\ Support/Apple/Remote\ Desktop/ sudo rm -rf /Library/Application\ Support/Apple/Remote\ Desktop/
rm -r ~/Library/Application\ Support/Remote\ Desktop/ rm -r ~/Library/Application\ Support/Remote\ Desktop/
rm -r ~/Library/Containers/com.apple.RemoteDesktop rm -r ~/Library/Containers/com.apple.RemoteDesktop
- -
name: Disable Internet based spell correction name: Disable online spell correction
code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false
revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled
- -
name: Disable Remote Apple Events name: Disable remote Apple events
recommend: strict recommend: strict
code: sudo systemsetup -setremoteappleevents off code: sudo systemsetup -setremoteappleevents off
revertCode: sudo systemsetup -setremoteappleevents on revertCode: sudo systemsetup -setremoteappleevents on
- -
name: Do not store documents to iCloud Drive by default name: Disable automatic storage of documents in iCloud Drive
docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html
recommend: standard recommend: standard
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
- -
name: Do not show recent items on dock name: Disable display of recent applications on Dock
docs: https://developer.apple.com/documentation/devicemanagement/dock docs: https://developer.apple.com/documentation/devicemanagement/dock
code: defaults write com.apple.dock show-recents -bool false code: defaults write com.apple.dock show-recents -bool false
revertCode: defaults delete com.apple.dock show-recents revertCode: defaults delete com.apple.dock show-recents
@@ -636,7 +669,7 @@ actions:
category: Configure Siri category: Configure Siri
children: children:
- -
name: Opt-out from Siri data collection name: Disable participation in Siri data collection
recommend: standard recommend: standard
code: defaults write com.apple.assistant.support 'Siri Data Sharing Opt-In Status' -int 2 code: defaults write com.apple.assistant.support 'Siri Data Sharing Opt-In Status' -int 2
revertCode: defaults delete com.apple.assistant.support 'Siri Data Sharing Opt-In Status' revertCode: defaults delete com.apple.assistant.support 'Siri Data Sharing Opt-In Status'
@@ -683,7 +716,7 @@ actions:
launchctl enable "gui/$UID/com.apple.Siri.agent" launchctl enable "gui/$UID/com.apple.Siri.agent"
sudo launchctl enable 'system/com.apple.Siri.agent' sudo launchctl enable 'system/com.apple.Siri.agent'
if [ $(/usr/bin/csrutil status | awk '/status/ {print $5}' | sed 's/\.$//') = "enabled" ]; then if [ $(/usr/bin/csrutil status | awk '/status/ {print $5}' | sed 's/\.$//') = "enabled" ]; then
>&2 echo 'This script requires SIP to be disabled. Read more: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection'' >&2 echo 'This script requires SIP to be disabled. Read more: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection'
fi fi
- -
name: Disable "Do you want to enable Siri?" pop-up name: Disable "Do you want to enable Siri?" pop-up
@@ -694,15 +727,15 @@ actions:
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup' revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
- -
category: Hide Siri category: Remove Siri from user interface
children: children:
- -
name: Hide Siri from menu bar name: Remove Siri from menu bar
recommend: strict recommend: strict
code: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 0 code: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 0
revertCode: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 1 revertCode: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 1
- -
name: Hide Siri from status menu name: Remove Siri from status menu
recommend: strict recommend: strict
docs: https://derflounder.wordpress.com/2016/09/20/blocking-siri-on-macos-sierra/ docs: https://derflounder.wordpress.com/2016/09/20/blocking-siri-on-macos-sierra/
code: |- code: |-
@@ -716,7 +749,7 @@ actions:
code: sudo mdutil -i off -d / code: sudo mdutil -i off -d /
revertCode: sudo mdutil -i on / revertCode: sudo mdutil -i on /
- -
name: Disable Personalized advertisements and identifier collection name: Disable personalized advertisements and identifier tracking
recommend: standard recommend: standard
docs: |- docs: |-
This script enhances your privacy by deactivating Personalized Ads and disabling the collection This script enhances your privacy by deactivating Personalized Ads and disabling the collection
@@ -746,7 +779,7 @@ actions:
Please note: The `forceLimitAdTracking` key limits ad tracking [3] [4] and is found in CIS Please note: The `forceLimitAdTracking` key limits ad tracking [3] [4] and is found in CIS
benchmarks for macOS [4]. However, the official macOS documentation specifies that it is benchmarks for macOS [4]. However, the official macOS documentation specifies that it is
applicable only to iOS 7 and later versions, not to macOS [3]. The key does not exist on the OS applicable only to iOS 7 and newer versions, not to macOS [3]. The key does not exist on the OS
by default. by default.
[1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple" [1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple"
@@ -789,7 +822,7 @@ actions:
sudo defaults write /Library/Preferences/com.apple.alf globalstate -bool false sudo defaults write /Library/Preferences/com.apple.alf globalstate -bool false
defaults write com.apple.security.firewall EnableFirewall -bool false defaults write com.apple.security.firewall EnableFirewall -bool false
- -
name: Turn on firewall logging name: Enable firewall logging
recommend: standard recommend: standard
docs: docs:
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671 - https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
@@ -801,7 +834,7 @@ actions:
/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -bool false sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -bool false
- -
name: Turn on stealth mode name: Enable stealth mode
recommend: standard recommend: standard
docs: docs:
- https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327 - https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327
@@ -816,16 +849,16 @@ actions:
sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -bool false sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -bool false
defaults write com.apple.security.firewall EnableStealthMode -bool false defaults write com.apple.security.firewall EnableStealthMode -bool false
- -
category: Disable auto-permitting incoming traffic for apps category: Disable automatic permission for incoming traffic in applications
children: children:
- -
name: Prevent automatically allowing incoming connections to signed apps name: Disable automatic incoming connections for signed apps
docs: https://daiderd.com/nix-darwin/manual/index.html docs: https://daiderd.com/nix-darwin/manual/index.html
recommend: strict recommend: strict
code: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool false code: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool false
revertCode: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool true revertCode: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool true
- -
name: Prevent automatically allowing incoming connections to downloaded signed apps name: Disable automatic incoming connections for downloaded signed apps
docs: https://daiderd.com/nix-darwin/manual/index.html docs: https://daiderd.com/nix-darwin/manual/index.html
recommend: strict recommend: strict
code: sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsignedenabled -bool false code: sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsignedenabled -bool false
@@ -845,16 +878,16 @@ actions:
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active -bool false code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active -bool false
revertCode: sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active revertCode: sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active
- -
category: Use screen saver for protection category: Enable protective screen saver
children: children:
- -
name: Require a password to wake the computer from sleep or screen saver name: Enable password requirement for waking from sleep or screen saver
# The screen saver acts as a session lock and prevents unauthorized users from accessing the current user's account. # The screen saver acts as a session lock and prevents unauthorized users from accessing the current user's account.
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230744 docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230744
code: sudo defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true code: sudo defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword
- -
name: Initiate session lock five seconds after screen saver is started name: Enable session lock five seconds after screen saver initiation
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230745 docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230745
# An unattended system with an excessive grace period is vulnerable to a malicious user. # An unattended system with an excessive grace period is vulnerable to a malicious user.
code: sudo defaults write /Library/Preferences/com.apple.screensaver 'askForPasswordDelay' -int 5 code: sudo defaults write /Library/Preferences/com.apple.screensaver 'askForPasswordDelay' -int 5
@@ -866,29 +899,29 @@ actions:
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81615 - https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81615
children: children:
- -
name: Disables signing in as Guest from the login screen name: Disable guest sign-in from login screen
code: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool NO code: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool NO
revetCode: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool YES revertCode: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool YES
- -
name: Disables Guest access to file shares over AF name: Disable guest access to file shares over AF
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool NO code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool NO
revetCode: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool YES revertCode: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool YES
- -
name: Disables Guest access to file shares over SMB name: Disable guest access to file shares over SMB
code: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool NO code: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool NO
revetCode: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool YES revertCode: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool YES
- -
category: Prevent unauthorized connections category: Disable unauthorized connections
children: children:
- -
name: Disable remote login (incoming SSH and SFTP connections) name: Disable incoming SSH and SFTP remote logins
recommend: standard recommend: standard
docs: https://osxdaily.com/2016/08/16/enable-ssh-mac-command-line/ docs: https://osxdaily.com/2016/08/16/enable-ssh-mac-command-line/
# Check if enabled: sudo systemsetup -getremotelogin, returns "Remote Login: On" or "Off" # Check if enabled: sudo systemsetup -getremotelogin, returns "Remote Login: On" or "Off"
code: echo 'yes' | sudo systemsetup -setremotelogin off code: echo 'yes' | sudo systemsetup -setremotelogin off
revertCode: sudo systemsetup -setremotelogin on revertCode: sudo systemsetup -setremotelogin on
- -
name: Disable insecure TFTP service name: Disable the insecure TFTP service
recommend: standard recommend: standard
# If the system does not require Trivial File Transfer Protocol (TFTP), then support for # If the system does not require Trivial File Transfer Protocol (TFTP), then support for
# it is non-essential and should be disabled. The information system should be configured to # it is non-essential and should be disabled. The information system should be configured to
@@ -921,13 +954,13 @@ actions:
- https://www.cups.org/doc/security.html # Security risks - https://www.cups.org/doc/security.html # Security risks
children: children:
- -
name: Disable sharing of local printers with other computers name: Disable local printer sharing with other computers
recommend: standard recommend: standard
docs: https://www.cups.org/doc/man-cupsctl.html docs: https://www.cups.org/doc/man-cupsctl.html
code: cupsctl --no-share-printers code: cupsctl --no-share-printers
revertCode: cupsctl --share-printers revertCode: cupsctl --share-printers
- -
name: Disable printing from any address including the Internet name: Disable printing from external addresses, including the internet
recommend: standard recommend: standard
docs: https://www.cups.org/doc/man-cupsctl.html docs: https://www.cups.org/doc/man-cupsctl.html
code: cupsctl --no-remote-any code: cupsctl --no-remote-any
@@ -952,7 +985,7 @@ actions:
category: Clean File Quarantine from downloaded files category: Clean File Quarantine from downloaded files
children: children:
- -
name: Clear File Quarantine logs of all downloaded files name: Clear logs of all downloaded files from File Quarantine
recommend: strict recommend: strict
docs: docs:
- https://www.macobserver.com/tips/how-to/your-mac-remembers-everything-you-download-heres-how-to-clear-download-history/ - https://www.macobserver.com/tips/how-to/your-mac-remembers-everything-you-download-heres-how-to-clear-download-history/
@@ -969,7 +1002,7 @@ actions:
if ls -lO "$db_file" | grep --silent 'schg'; then if ls -lO "$db_file" | grep --silent 'schg'; then
sudo chflags noschg "$db_file" sudo chflags noschg "$db_file"
echo "Found and removed system immutable flag" echo "Found and removed system immutable flag"
has_sytem_immutable_flag=true has_system_immutable_flag=true
fi fi
if ls -lO "$db_file" | grep --silent 'uchg'; then if ls -lO "$db_file" | grep --silent 'uchg'; then
sudo chflags nouchg "$db_file" sudo chflags nouchg "$db_file"
@@ -978,7 +1011,7 @@ actions:
fi fi
sqlite3 "$db_file" "$db_query" sqlite3 "$db_file" "$db_query"
echo "Executed the query \"$db_query\"" echo "Executed the query \"$db_query\""
if [ "$has_sytem_immutable_flag" = true ] ; then if [ "$has_system_immutable_flag" = true ] ; then
sudo chflags schg "$db_file" sudo chflags schg "$db_file"
echo "Added system immutable flag back" echo "Added system immutable flag back"
fi fi
@@ -1012,10 +1045,10 @@ actions:
' \ ' \
{} \; {} \;
- -
category: Disable File Quarantine from tracking downloaded files category: Disable macOS File Quarantine tracking for downloaded files
children: children:
- -
name: Prevent quarantine from logging downloaded files name: Disable downloaded file logging in quarantine
docs: docs:
- https://eclecticlight.co/2019/04/25/%F0%9F%8E%97-quarantine-apps/ - https://eclecticlight.co/2019/04/25/%F0%9F%8E%97-quarantine-apps/
- https://eclecticlight.co/2017/12/11/xattr-com-apple-quarantine-the-quarantine-flag/ - https://eclecticlight.co/2017/12/11/xattr-com-apple-quarantine-the-quarantine-flag/
@@ -1038,7 +1071,7 @@ actions:
>&2 echo "Cannot revert immutability, file does not exist at\"$file_to_lock\"" >&2 echo "Cannot revert immutability, file does not exist at\"$file_to_lock\""
fi fi
- -
name: Disable using extended quarantine attribute on downloaded files (disables warning) name: Disable extended quarantine attribute for downloaded files (disables warning)
# Disables dialogs shown when opening an application for the first time # Disables dialogs shown when opening an application for the first time
# i.e. "Application Downloaded from Internet" quarantine warning. # i.e. "Application Downloaded from Internet" quarantine warning.
docs: docs:
@@ -1054,7 +1087,7 @@ actions:
# Can protect against unknown threats. # Can protect against unknown threats.
children: children:
- -
name: Prevent Gatekeeper from automatically reactivating itself name: Disable Gatekeeper's automatic reactivation
docs: docs:
- https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/ - https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/
- https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/ - https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
@@ -1071,8 +1104,8 @@ actions:
code: |- code: |-
os_major_ver=$(sw_vers -productVersion | awk -F "." '{print $1}') os_major_ver=$(sw_vers -productVersion | awk -F "." '{print $1}')
os_minor_ver=$(sw_vers -productVersion | awk -F "." '{print $2}') os_minor_ver=$(sw_vers -productVersion | awk -F "." '{print $2}')
if [[ $os_major_ver -le 10 \ if [[ $os_major_ver -le 10 \
|| ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \ || ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \
]]; then ]]; then
echo "No action needed, Gatekeeper is not available this OS version" echo "No action needed, Gatekeeper is not available this OS version"
else else
@@ -1090,8 +1123,8 @@ actions:
revertCode: |- revertCode: |-
os_major_ver=$(sw_vers -productVersion | awk -F "." '{print $1}') os_major_ver=$(sw_vers -productVersion | awk -F "." '{print $1}')
os_minor_ver=$(sw_vers -productVersion | awk -F "." '{print $2}') os_minor_ver=$(sw_vers -productVersion | awk -F "." '{print $2}')
if [[ $os_major_ver -le 10 \ if [[ $os_major_ver -le 10 \
|| ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \ || ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \
]]; then ]]; then
>&2 echo "Gatekeeper is not available in this OS version" >&2 echo "Gatekeeper is not available in this OS version"
else else
@@ -1107,7 +1140,7 @@ actions:
fi fi
fi fi
- -
name: Disable Library Validation Entitlement (checks signature of libraries) name: Disable library validation entitlement (library signature validation)
docs: docs:
- https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation - https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation
- https://www.macenhance.com/docs/general/sip-library-validation.html - https://www.macenhance.com/docs/general/sip-library-validation.html
@@ -1121,25 +1154,25 @@ actions:
- https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html - https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html
children: children:
- -
name: Disable automatically checking for updates name: Disable automatic checks for updates
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
code: |- code: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
revertCode: |- revertCode: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
- -
name: Disable automatically downloading new updates when available name: Disable automatic downloads for updates
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
code: |- code: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
revertCode: |- revertCode: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
- -
name: Disable automatically installing macOS updates name: Disable automatic installation of macOS updates
docs: docs:
# References for AutoUpdateRestartRequired # References for AutoUpdateRestartRequired
- https://kb.vmware.com/s/article/2960635 - https://kb.vmware.com/s/article/2960635
@@ -1149,48 +1182,48 @@ actions:
code: |- code: |-
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14) # For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false
# For Mojave and later (>= 10.14) # For Mojave and newer (>= 10.14)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool false sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool false
revertCode: |- revertCode: |-
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14) # For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool true sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool true
# For Mojave and later (>= 10.14) # For Mojave and newer (>= 10.14)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
- -
name: Disable automatically updating app from the App Store name: Disable automatic app updates from the App Store
docs: docs:
- https://kb.vmware.com/s/article/2960635 - https://kb.vmware.com/s/article/2960635
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/ - https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
code: |- code: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
# For Mojave and later (>= 10.14) # For Mojave and newer (>= 10.14)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool false sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool false
revertCode: |- revertCode: |-
# For OS X Yosemite and later # For OS X Yosemite and newer
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool true sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool true
# For Mojave and later (>= 10.14) # For Mojave and newer (>= 10.14)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
- -
name: Disable installation of macOS beta releases name: Disable macOS beta release installation
docs: https://support.apple.com/en-gb/HT203018 docs: https://support.apple.com/en-gb/HT203018
code: |- code: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
revertCode: |- revertCode: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
- -
name: Disable automatically installing configuration data (e.g. XProtect, Gatekeeper, MRT) name: Disable automatic installation for configuration data (e.g. XProtect, Gatekeeper, MRT)
docs: https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/ docs: https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
code: |- code: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
revertCode: |- revertCode: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
- -
name: Disable automatically installing system data files and security updates name: Disable automatic installation for system data files and security updates
docs: docs:
# References for CriticalUpdateInstall # References for CriticalUpdateInstall
- https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/ - https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/
@@ -1198,10 +1231,10 @@ actions:
# References for softwareupdate --background-critical # References for softwareupdate --background-critical
- https://managingosx.wordpress.com/2013/04/30/undocumented-options/ - https://managingosx.wordpress.com/2013/04/30/undocumented-options/
code: |- code: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
revertCode: |- revertCode: |-
# For OS X Yosemite and later (>= 10.10) # For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool true sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool true
# Trigger background check with normal scan (critical updates only) # Trigger background check with normal scan (critical updates only)
sudo softwareupdate --background-critical sudo softwareupdate --background-critical

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,37 @@
import os from 'os'; import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
import path from 'path';
import fs from 'fs';
// eslint-disable-next-line camelcase
import child_process from 'child_process';
import { Environment } from '@/application/Environment/Environment';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { getWindowInjectedSystemOperations } from './SystemOperations/WindowInjectedSystemOperations';
export class CodeRunner { export class CodeRunner {
constructor( constructor(
private readonly node = getNodeJs(), private readonly system = getWindowInjectedSystemOperations(),
private readonly environment = Environment.CurrentEnvironment, private readonly environment = RuntimeEnvironment.CurrentEnvironment,
) { ) {
if (!system) {
throw new Error('missing system operations');
}
} }
public async runCode(code: string, folderName: string, fileExtension: string): Promise<void> { public async runCode(code: string, folderName: string, fileExtension: string): Promise<void> {
const dir = this.node.path.join(this.node.os.tmpdir(), folderName); const { os } = this.environment;
await this.node.fs.promises.mkdir(dir, { recursive: true }); const dir = this.system.location.combinePaths(
const filePath = this.node.path.join(dir, `run.${fileExtension}`); this.system.operatingSystem.getTempDirectory(),
await this.node.fs.promises.writeFile(filePath, code); folderName,
await this.node.fs.promises.chmod(filePath, '755'); );
const command = getExecuteCommand(filePath, this.environment); await this.system.fileSystem.createDirectory(dir, true);
this.node.child_process.exec(command); const filePath = this.system.location.combinePaths(dir, `run.${fileExtension}`);
await this.system.fileSystem.writeToFile(filePath, code);
await this.system.fileSystem.setFilePermissions(filePath, '755');
const command = getExecuteCommand(filePath, os);
this.system.command.execute(command);
} }
} }
function getExecuteCommand(scriptPath: string, environment: Environment): string { function getExecuteCommand(
switch (environment.os) { scriptPath: string,
currentOperatingSystem: OperatingSystem,
): string {
switch (currentOperatingSystem) {
case OperatingSystem.Linux: case OperatingSystem.Linux:
return `x-terminal-emulator -e '${scriptPath}'`; return `x-terminal-emulator -e '${scriptPath}'`;
case OperatingSystem.macOS: case OperatingSystem.macOS:
@@ -36,46 +42,6 @@ function getExecuteCommand(scriptPath: string, environment: Environment): string
case OperatingSystem.Windows: case OperatingSystem.Windows:
return scriptPath; return scriptPath;
default: default:
throw Error(`unsupported os: ${OperatingSystem[environment.os]}`); throw Error(`unsupported os: ${OperatingSystem[currentOperatingSystem]}`);
} }
} }
function getNodeJs(): INodeJs {
return {
os, path, fs, child_process,
};
}
export interface INodeJs {
os: INodeOs;
path: INodePath;
fs: INodeFs;
// eslint-disable-next-line camelcase
child_process: INodeChildProcess;
}
export interface INodeOs {
tmpdir(): string;
}
export interface INodePath {
join(...paths: string[]): string;
}
export interface INodeChildProcess {
exec(command: string): void;
}
export interface INodeFs {
readonly promises: INodeFsPromises;
}
interface INodeFsPromisesMakeDirectoryOptions {
recursive?: boolean;
}
interface INodeFsPromises { // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v13/fs.d.ts
chmod(path: string, mode: string | number): Promise<void>;
mkdir(path: string, options: INodeFsPromisesMakeDirectoryOptions): Promise<string>;
writeFile(path: string, data: string): Promise<void>;
}

View File

@@ -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;

View File

@@ -0,0 +1,50 @@
import { IEnvironmentVariables } from './IEnvironmentVariables';
/* Validation is externalized to keep the environment objects simple */
export function validateEnvironmentVariables(environment: IEnvironmentVariables): void {
if (!environment) {
throw new Error('missing environment');
}
const keyValues = capturePropertyValues(environment);
if (!Object.keys(keyValues).length) {
throw new Error('Unable to capture key/value pairs');
}
const keysMissingValue = getKeysMissingValues(keyValues);
if (keysMissingValue.length > 0) {
throw new Error(`Environment keys missing: ${keysMissingValue.join(', ')}`);
}
}
function getKeysMissingValues(keyValuePairs: Record<string, unknown>): string[] {
return Object.entries(keyValuePairs)
.reduce((acc, [key, value]) => {
if (!value && typeof value !== 'boolean') {
acc.push(key);
}
return acc;
}, new Array<string>());
}
/**
* Captures values of properties and getters from the provided instance.
* Necessary because code transformations can make class getters non-enumerable during bundling.
* This ensures that even if getters are non-enumerable, their values are still captured and used.
*/
function capturePropertyValues(instance: unknown): Record<string, unknown> {
const obj: Record<string, unknown> = {};
const descriptors = Object.getOwnPropertyDescriptors(instance.constructor.prototype);
// Capture regular properties from the instance
for (const [key, value] of Object.entries(instance)) {
obj[key] = value;
}
// Capture getter properties from the instance's prototype
for (const [key, descriptor] of Object.entries(descriptors)) {
if (typeof descriptor.get === 'function') {
obj[key] = descriptor.get.call(instance);
}
}
return obj;
}

View File

@@ -0,0 +1,10 @@
/**
* Represents essential metadata about the application.
*/
export interface IAppMetadata {
readonly version: string;
readonly name: string;
readonly slogan: string;
readonly repositoryUrl: string;
readonly homepageUrl: string;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,5 @@
import { IEnvironmentVariables } from './IEnvironmentVariables';
export interface IEnvironmentVariablesFactory {
readonly instance: IEnvironmentVariables;
}

View File

@@ -0,0 +1,13 @@
// Only variables prefixed with VITE_ are exposed to Vite-processed code
export const VITE_USER_DEFINED_ENVIRONMENT_KEYS = {
VERSION: 'VITE_APP_VERSION',
NAME: 'VITE_APP_NAME',
SLOGAN: 'VITE_APP_SLOGAN',
REPOSITORY_URL: 'VITE_APP_REPOSITORY_URL',
HOMEPAGE_URL: 'VITE_APP_HOMEPAGE_URL',
} as const;
export const VITE_ENVIRONMENT_KEYS = {
...VITE_USER_DEFINED_ENVIRONMENT_KEYS,
DEV: 'DEV',
} as const;

View File

@@ -0,0 +1,33 @@
import { IEnvironmentVariables } from '../IEnvironmentVariables';
/**
* Provides the application's environment variables.
*/
export class ViteEnvironmentVariables implements IEnvironmentVariables {
// Ensure the use of import.meta.env prefix for the following properties.
// Vue will replace these statically during production builds.
public get version(): string {
return import.meta.env.VITE_APP_VERSION;
}
public get name(): string {
return import.meta.env.VITE_APP_NAME;
}
public get slogan(): string {
return import.meta.env.VITE_APP_SLOGAN;
}
public get repositoryUrl(): string {
return import.meta.env.VITE_APP_REPOSITORY_URL;
}
public get homepageUrl(): string {
return import.meta.env.VITE_APP_HOMEPAGE_URL;
}
public get isNonProduction(): boolean {
return import.meta.env.DEV;
}
}

View File

@@ -0,0 +1,11 @@
/// <reference types="vite/client" />
import { VITE_ENVIRONMENT_KEYS } from './ViteEnvironmentKeys';
export type ViteEnvironmentVariables = {
readonly [K in keyof typeof VITE_ENVIRONMENT_KEYS]: string;
};
interface ImportMeta {
readonly env: ViteEnvironmentVariables
}

View File

@@ -1,9 +1,20 @@
import { IEventSubscriptionCollection } from './IEventSubscriptionCollection';
import { IEventSubscription } from './IEventSource'; import { IEventSubscription } from './IEventSource';
export class EventSubscriptionCollection { export class EventSubscriptionCollection implements IEventSubscriptionCollection {
private readonly subscriptions = new Array<IEventSubscription>(); private readonly subscriptions = new Array<IEventSubscription>();
public register(...subscriptions: IEventSubscription[]) { public get subscriptionCount() {
return this.subscriptions.length;
}
public register(subscriptions: IEventSubscription[]) {
if (!subscriptions || subscriptions.length === 0) {
throw new Error('missing subscriptions');
}
if (subscriptions.some((subscription) => !subscription)) {
throw new Error('missing subscription in list');
}
this.subscriptions.push(...subscriptions); this.subscriptions.push(...subscriptions);
} }
@@ -11,4 +22,9 @@ export class EventSubscriptionCollection {
this.subscriptions.forEach((listener) => listener.unsubscribe()); this.subscriptions.forEach((listener) => listener.unsubscribe());
this.subscriptions.splice(0, this.subscriptions.length); this.subscriptions.splice(0, this.subscriptions.length);
} }
public unsubscribeAllAndRegister(subscriptions: IEventSubscription[]) {
this.unsubscribeAll();
this.register(subscriptions);
}
} }

Some files were not shown because too many files have changed in this diff Show More