Compare commits

..

70 Commits

Author SHA1 Message Date
undergroundwires
58f902216b Fix disabling of Microsoft Defender $170
- Change naming from Windows Defender to Microsoft Defender to match
  latest branding.
- Add more extensive documentation.
- Add more scripts extending ways to disable Defender.
- Disable "Windows Security Center Service"
- Add missing `SetMpPreference` commands
- New disabling:
  - Disabling of Windows features related to Defender.
  - Disable Antimalware Scan Interface (AMSI)

TODO: Soft delete Defender directories, like
`$env:programdata\Microsoft\Windows Defender`

TODO: Add from here: https://learn.microsoft.com/en-us/mem/intune/protect/antivirus-security-experience-windows-settings

New scripts:

- Disable "Windows Security Center" service
- Kill SmartScreen process
- Disable "Microsoft Security Core Boot" service

Improved scripts:

- Disable Intrusion Prevention System (IPS): Add CLI command to disable
  it.

TODO: These to separate commit

TODO:

- Improve disabling of `RenameSystemFile` AsTrustedInstaller and get
  back all commented out code.
2024-07-18 09:48:06 +02:00
undergroundwires
48d6dbd700 Refactor to use string IDs for executables #262
This commit unifies the concepts of executables having same ID
structure. It paves the way for more complex ID structure and using IDs
in collection files as part of new ID solution (#262). Using string IDs
also leads to more expressive test code.

This commit also refactors the rest of the code to adopt to the changes.

This commit:

- Separate concerns from entities for data access (in repositories) and
  executables. Executables use `Identifiable` meanwhile repositories use
  `RepositoryEntity`.
- Refactor unnecessary generic parameters for enttities and ids,
  enforcing string gtype everwyhere.
- Changes numeric IDs to string IDs for categories to unify the
  retrieval and construction for executables, using pseudo-ids (their
  names) just like scripts.
- Remove `BaseEntity` for simplicity.
- Simplify usage and construction of executable objects.
  Move factories responsible for creation of category/scripts to domain
  layer. Do not longer export `CollectionCategorY` and
  `CollectionScript`.
- Use named typed for string IDs for better differentation of different
  ID contexts in code.
2024-07-08 23:23:05 +02:00
undergroundwires
19ea8dbc5b Fix close button overlap by scrollbar
This commit positions the close button on modaals to remain visible on
small screens.

This fix addresses an issue where the dialog's close button was being
obscured by the vertical scrollbar on narrow screens. This problem was
particularly evident in Chromium browsers, which display a larger
scrollbar.

This change includes a new mixin to document the purpose. The mixin sets
the position of close button to `absolute` and aligns it to the top
right. This way, the button stays visible and accessible regaRdless of
The scrollbar's presence, thereby improving the user inTerfaCE usability
on deviceS with smaller screens.
2024-07-08 12:16:51 +02:00
undergroundwires
70959ccada Fix documentation button spacing on small screens
The previous layout lacked a specified gap between the node header and
the documentation toggle button in the tree view. This resulted in a
crowded appearance, making the interface look cluttered and reducing
readability, especially on smaller screens.

This commit introduces a relative gap, adjusting the spacing based on
the text size. This change enhances the visual separation and improves
user interaction by ensuring the documentation button and text do not
overlap, regardless of screen size.
2024-07-07 11:59:27 +02:00
undergroundwires
5d365f65fa win: improve service disabling as TrustedInstaller
This commit changes the mechanism to disable services using
TrustedInstaller privileges, improving consistency and flexibility.

Key changes:

- Introduce `DisableServiceInRegistryAsTrustedInstaller` as a shared
  function to standardize the disabling process. This function aligns
  with existing ones to facilitate easier testing and method switching.
- Update the revert logic to avoid unnecessary service restarts when
  they are manually started.
- Enhance readability with added comments in generated code sections.
- Improve documentation for `DisableService` and
  `DisableServiceInRegistry` to reflect new functionalities.
- Support multiline code in `RunInlineCodeAsTrustedInstaller` for
  complex scenarios.

Other supporting changes:

- Remove redundant TrustedInstaller privileges in the `Sense` service
  disabling.
- Document default service statuses to inform about service behaviors
  across different Windows versions.
2024-07-06 12:28:42 +02:00
undergroundwires-bot
cca397c8c7 ⬆️ bump everywhere to 0.13.5 2024-06-27 08:13:03 +00:00
undergroundwires
1430d5215a win: add more Edge scripts including AI & ads
This commit improves the scripts configuring Edge. It improves their
categorization, naming and adds scripts to disable Bing ads and Search
bar along with others to disable ads/data collection.

Changes:

- Add new scripts to configure Edge, such as blocking ads and AI
  features that collects data.
- Improve categorization and documentation consistency.
2024-06-26 16:48:49 +02:00
undergroundwires
c09c5ffa47 win, linux, mac: fix typos #373
This commit fixes typos, corrects markdown syntax, and archived URLs.

Co-authored-by: RainRat <rainrat78@yahoo.ca>
2024-06-26 08:13:13 +02:00
undergroundwires
ed7e69c07e win: add disabling Edge/WebView2 auto-updates #309
This commit adds scripts to block automatic updates for Microsoft Edge
and WebView2 on Windows, aimed at enhancing user privacy as per the
issue #309.

Changes:

- Create a new category for scripts targeting Edge and WebView2 updates.
- Add scripts for disabling automatic update services, scheduled tasks,
  and executable blocking, along with registry configurations.

Other supporting changes:

- Remove comments in code that indicates reusing of small text parts.
  This approach does not encourage creating unique content.
2024-06-25 12:23:55 +02:00
undergroundwires
f286f92b1f win: categorize, rename, doc Chrome & Edge scripts
This commit improves the script organization, documentation and code for
Edge and Chrome browser by simplifying naming, categorizing
configurations, and unifying documentation and generated code.

Changes:

- Rename "Edge (Chromium)" to "Edge" for clarity, with "Edge (Legacy)"
  detailed explicitly in the script titles.
- Flatten Edge settings under a unified "Configure" category.
- Enhance script documentation to improve clarity.
- Move "Your browser is managed" warning from script titles to script
  documentation.
- Introduce shared functions for configuring Edge and Chrome, leading to
  better consistency in generated code.
- Update scripts to include restart suggestions in generated code.
- Improve documentation of affected scripts.
- Split some scripts for increased granularity, easier maintenance and
  focused documentation.
- Fix some Windows UI scripts being incorrectly categorized as Edge
  configurations.
2024-06-24 20:27:52 +02:00
undergroundwires
e7031a3ae4 win: fix latest Edge removal on Windows 10 #309
This commit introduces a placeholder ifle creation step necessary for
the uninstallation process of Microsoft Edge on Windows 10, as discussed
in #309. The file simulates the presence of Microsoft Edge (Legacy),
which newer uninstallers check for before proceeding with the
uninstallation.

This change resolves the observed issue where the uninstaller fails to
recognize the absence of Legacy Edge, hindering the uninstallation
process.

Changes:

- Add placeholder file creation/removal for legacy Edge.
- Update and improve the documentation.
2024-06-23 12:51:15 +02:00
undergroundwires
2f828735a8 win: fix errors due to missing Edge uninstaller
If Edge is uninstalled using an existing installer, it may delete other
installers. When the script attempts to use these deleted installers,
it results in an error: `The system cannot find the file specified.`.

This commit addresses the issue by checking for the existence of the
uninstaller during the iteration and handling cases where it is missing.
2024-06-22 14:01:06 +02:00
undergroundwires
78c62cfc95 Trim compiler error output for better readability
Previously, compiler outputted whole executable in error context. This
caused long and hard to read error messages, especially when the
executable is a long category with many children. This commit improves
readability by trimming the error output.

Changes:

- Trim the error output (max characters: 1000).
- Improve indenting and newlines.
2024-06-21 12:46:30 +02:00
undergroundwires
ed93614ca3 Bump Electron to latest
- Bump Electron to latest.
- Adjust types to new Electron types.
2024-06-20 10:49:24 +02:00
undergroundwires
fac26a6ca0 Add type validation for parameters and fix types
This commit introduces type validation for parameter values within the
parser/compiler, aligning with the YAML schema. It aims to eliminate
dependencies on side effects in the collection files.

This update changes the treatment of data types in the Windows
collection, moving away from unintended type casting by the compiler.
Previously, numeric and boolean values were used even though only
string types were supported. This behavior was unstable and untested,
and has now been adjusted to use strings exclusively.

Changes ensure that parameter values are correctly validated
as strings, enhancing stability and maintainability.
2024-06-19 17:01:27 +02:00
undergroundwires
48761f62a2 win: fix incomplete VSCEIP, location scripts
This commit improves the validation logic in parser, corrects Windows
collection files to adhere to expected structure. This validation helps
catch errors that previously led to incomplete generated code in scripts
for disabling VSCEIP and location settings.

Changes:

- Add type validation for function call structures in the
  parser/compiler. This helps prevent runtime errors by ensuring that
  only correctly structured data is processed.
- Fix scripts in the Windows collection that previoulsy had incomplete
  `code` or `revertCode` values. These corrections ensure that the
  scripts function as intended.
- Refactor related logic within the compiler/parser to improve
  testability and maintainability.
2024-06-18 17:59:32 +02:00
undergroundwires
dc03bff324 Add schema validation for collection files #369
This commit improves collection file editing and error detection
directly in the IDE. It adds YAML schema, IDE configuration and
automatic tests to validate it.

- Introduce a YAML schema for collection file.
- Use `yaml-language-server` for enhanced YAML support in VSCode.
- Add telemetry disabling in `configure_vscode.py` to respect user
  privacy.
- Add automated checks to validate YAML file structure against the
  schema.
- Remove unused properties and do not allow them in compiler.
2024-06-17 14:01:07 +02:00
undergroundwires
e9a52859f6 mac: document, improve, encourage clearing logs
Previously, scripts under the 'Clear operating system logs' category for
macOS were misaligned due to a lack of individual script
recommendations, as the category itself wrongly used the `recommend:
strict` property. This misconfiguration caused none of these scripts to
appear recommended.

This commit assigns accurate `recommend:` values to each script
within the category.

Key changes:

- Introduce individual recommendations for each script.
- Document scripts to justify recommendations.
- Standardize deletion operations through shared functions.
- Improve script and category naming for clarity.
- Simplify code by unifying redundant path references.
- Add comments in generated user script code.
- Fix specific issue where clearing daily os logs inadvertently affected
  configuration files.
2024-06-16 11:27:48 +02:00
undergroundwires
1a10cf2e5f win: fix text and handwriting script omission #369
This commit corrects a syntax error that prevented the 'Disable text
and handwriting data collection' script from being included since
version 0.13.4. The error was identified in a previous syntax validation
update (commit 6ecfa9b954).

Changes:

- Add a missing dash before 'Disable location access' category. This
  fixes the script omission as reported in the issue #369.
- Remove the dash from `revertCode` of `HarvestContacts` registry
  modification code, fixing the revert code for 'Disable text and
  handwriting data collection' script.

This fix addresses the build errors introduced by stricter syntax checks
and ensures that the script is now properly recognized and executed.
This fix is part of ongoing efforts to improve data handling robustness
and management in script processing.
2024-06-15 08:32:58 +02:00
undergroundwires
1c2d82dc9b win: fix missing app access recommendations #369
This script fixes the recommendation property syntax in Windows script
collection. This syntax error prevented the application from
recommending these scripts, even though they were intended to be
recommended.

Affected scripts:

- Disable app access to physical movement
- Disable app access to eye tracking
- Disable app access to human presence
- Disable app access to screen capture

Previously, these scripts used the unsupported 'recommended: standard'
property, which was identified as incorrect after implementing stricter
property validation.
Related commit: 6ecfa9b954
Related issue: #369

This change update these properties to the correct 'recommend:
standard', resolving issues where scripts were not being recommended as
expected.
2024-06-14 12:36:15 +02:00
undergroundwires
6ecfa9b954 Add object property validation in parser #369
This commit introduces stricter type validation across the application
to reject objects with unexpected properties, enhancing the robustness
and predictability of data handling.

Changes include:

- Implement a common utility to validate object types.
- Refactor across various parsers and data handlers to utilize the new
  validations.
- Update error messages for better clarity and troubleshooting.
2024-06-13 22:26:57 +02:00
undergroundwires
c138f74460 Refactor to unify scripts/categories as Executable
This commit consolidates scripts and categories under a unified
'Executable' concept. This simplifies the architecture and improves code
readability.

- Introduce subfolders within `src/domain` to segregate domain elements.
- Update class and interface names by removing the 'I' prefix in
  alignment with new coding standards.
- Replace 'Node' with 'Executable' to clarify usage; reserve 'Node'
  exclusively for the UI's tree component.
2024-06-12 12:36:40 +02:00
undergroundwires
8becc7dbc4 win: fix revert scripts for removing shortcuts
Revert scripts for removing shortcuts previously used hardcoded paths.
These paths are now replaced with system environment variables to allow
for broader configuration compatibility.
2024-06-11 12:06:46 +02:00
undergroundwires
b29cd7b5f7 mac: discourage and document captive portal script
This commit adjusts the recommendation level for disabling captive
portal detection from 'Standard' to 'Strict'. This aligns macOS settings
with equivalent recommendations for Linux and Windows.

It improves documentation to provide additional context on implications,
facilitating a better understanding of the change.
2024-06-10 13:22:32 +02:00
undergroundwires
f21ef9250a win: improve executable blocking, Chrome reporting
This commit improves blocking of execution of executables, providing a
more reliable way to stop execution of unwanted executables.

Introduce a new function to block shell execution of an executable. This
logic is extracted from disabling Chrome Software Reporter tool with
improved logic which does no longer or reset if there is other rules.
This resolves potential issues if there was a blocking rule using same
number which privacy.sexy before overwrote or restored on revert.

Other scripts which terminated executables on launch does now block
their shell execution too for more reliability. A common function is
introduced which streamlines blocking execution of an executable all
known ways which is now reused by these scripts.

This commit additionally improves the Google Software Reporter disabling
script. It removes the code that adds denies permissions on its
installation directory as the new way of preventing executable from
running should be enough. It also adds missing documentation to the
related scripts.
2024-06-09 13:59:06 +02:00
undergroundwires
fa2a92bf89 Add image to README.md to thank supporters 2024-06-08 11:49:04 +02:00
undergroundwires
8341411be4 win: document and improve Firefox telemetry #259
This commit improves the existing Firefox privacy scripts and improves
the categorization and documentation to be simpler and more clear.

Changes:

- Rename and reorganize scripts for disabling browser telemetry and
  default browser agent reporting to simplify the structure.
- Improve documentation across scripts to provide clearer guidance on
  how the changes improve user privacy.
- Fix revert scripts by removing unnecessary registry key configuration.
2024-05-29 08:17:50 +02:00
undergroundwires
22d6c7991e ci/cd: centralize and bump artifact uploads
- Upgrade `actions/upload-artifact` to `v4` to address deprecation
  warnings related to Node.js 16, improving compatibility with GitHub
  runners. This resolves the following warning from the runners:
  > Node.js 16 actions are deprecated. Please update the following actions
  > to use Node.js 20: actions/upload-artifact@v3.
- Centralize the use of the `upload-artifact` action through a new
  custom action, improving maintainability and consistency across
  workflows.
2024-05-28 12:53:45 +02:00
undergroundwires-bot
795b7f0321 ⬆️ bump everywhere to 0.13.4 2024-05-27 13:45:04 +00:00
undergroundwires
9e34e64449 win, mac, linux: fix typos and dead URLs #367
- Fix multiple spelling errors in various scripts.
- Fix dead URLs with archived versions.
- Fix incorrect registry keys previously introduced in commit
  cec0b4b4f6.

Co-authored-by: RainRat <rainrat78@yahoo.ca>
2024-05-27 14:54:17 +02:00
undergroundwires
ce4cfdd169 win: add script to disable Recall feature 2024-05-27 10:44:11 +02:00
undergroundwires
12b1f183f7 win: document disabling firewall #115 #152 #364
This commit updates documentation to clarify the impacts of disabling
firewall services, specifically how they affect Windows Sandbox, Docker
and WSL.

This update responds to user feedback from issues #115, #152, #364. The
documentation now guides users more clearly on the consequences of their
actions, potentially preventing unintended service disruptions.

Changes include:

- Expand the caution notes to explicitly mention the impact on
  virtualization and isolation features like Windows Sandbox, Docker and
  WSL.
- Expand script titles to briefly mention affects on these features.
- Expand documentation to suggest system restart.
- Add an informative message to restart the computer in terminal outputs
  after service changes to ensure the settings are applied.
2024-05-26 13:42:25 +02:00
undergroundwires
4212c7b9e0 Improve context for errors thrown by compiler
This commit introduces a custom error object to provide additional
context for errors throwing during parsing and compiling operations,
improving troubleshooting.

By integrating error context handling, the error messages become more
informative and user-friendly, providing sequence of trace with context
to aid in troubleshooting.

Changes include:

- Introduce custom error object that extends errors with contextual
  information. This replaces previous usages of `AggregateError` which
  is not displayed well by browsers when logged.
- Improve parsing functions to encapsulate error context with more
  details.
- Increase unit test coverage and refactor the related code to be more
  testable.
2024-05-25 13:55:30 +02:00
undergroundwires
7794846185 win: discourage blocking app access #121 #339 #350
This commit adjusts the recommendation level for scripts that disable
UWP app access to accommodate user issues #121, #339, #350. It also
extends their documentation to reflect the new changes and with
cautions.

Changes:

- Add caution text for all scripts about potential impacts.
- Move disabling app access to notifications from 'Standard' to
  'Strict'. This addresses #121 and #339, where users report lack of
  notification as unintended side-effects.
- Move disabling app access to phone calls from 'Standard' to 'Strict'.
  This addresses #350 where its effect on the Phone Link app was
  reported as an unintended side-effect.
2024-05-24 10:45:23 +02:00
undergroundwires
150e067039 win: improve printing removal /w Print Queue #279
- Consolidate removal of printing UIs under the same category.
- Improve documentation for printing app removal scripts.
- Add removal of previously unlisted 'Print Queue' app, #279.
- Combine removal of `Microsoft.Print3D` and `Windows.Print3D`
  into a single script.
- Highlight the importance of removing 'Print 3D' app due to
  security risks and recommend it on 'Standard'.
2024-05-23 09:27:14 +02:00
undergroundwires
f347fde0c8 win: document and discourage RSA key script #363
This commit improves the documentation of RSA key handling script and
changes its recommendation level to address potential issues with
Hyper-V (as reported in #363).

Changes:

- Add documentation to describe potential disruptions caused by stronger
  RSA key requirements.
- Move RSA key script from 'Standard' to 'Strict' due to its impact on
  Hyper-V VMs.
- Use bullet points for easier expansion in cautions of secret key
  hardening scripts.
2024-05-22 08:10:37 +02:00
undergroundwires
ff3d5c4841 win: improve app access disabling and docs #138
This commit improves disabling app access by correcting minor issues,
adding missing access control configurations and adding more
documentation to increase maintainability and user understanding of
privacy settings, resolving #138.

- Introduce shared functions to streamline modifications of app access,
  improving maintainability and simplifying the codebase.
- Move disabling app access to first position in the category.
- Improve code comments for better clarity on generated outputs.
- Resolve error display in revert codes due to incorrect use of `reg
  delete` commands.
- Fix disabling app access to trusted devices disables access to account
  information, name and picture on older versions of Windows.
- Add missing privacy settings and configurations.
- Add more documentation to scripts
- Rename script names for consistency and clarity.
- Move disabling access to SMS/MMS to phone access disablement category.
- Set empty `REG_MULTI_SZ` values to null for GPO access settings to
  maintain registry integrity.
2024-05-21 13:02:48 +02:00
undergroundwires
292362135d Centralize and optimize ResizeObserver usage
This commit addresses failures in end-to-end tests that occurred due to
`ResizeObserver` loop limit exceptions.

These errors were triggered by Vue dependency upgrades in the commit
aae5434451.
The errors had the following message:
> `ResizeObserver loop completed with undelivered notifications`

This error happens when there are too many observations and the observer
is not able to deliver all observations within a single animation frame.
See: WICG/resize-observer#38

his commit resolves the issue by controlling how many observations are
delivered per animation frame and limiting it to only one.

It improves performance by reducing layout trashing, improving frame
rates, and managing resources more effectively.

Changes:

- Introduce an animation frame control to manage observations more
  efficiently.
- Centralized `ResizeObserver` management within the `UseResizeObserver`
  hook to improve consistency and reuse across the application.
2024-05-20 10:36:49 +02:00
undergroundwires
aae5434451 Bump Vue to latest and fix universal selector CSS
This commit updates the Vue package from v3.4.21 to v3.4.27.

This version change addressed styling issues introduced by changes in
CSS universal selector handling in Vue 3.4.22.
The change that has caused this:
- vuejs/core#10551
- vuejs/core#10548
- vuejs/core@54a6afa75a

This commit fixes two main issues that this has led to:

1. Universal CSS selector causing 'Revert' buttons to stretch and
   truncate incorrectly.
   This is fixed by modifying selectors to apply styles more
   specifically, maintaining correct display of toggle buttons.
2. Universal `*` selector that's used to understand parent HTML
   structure causing information tooltip icons to be misaligned.
   This is fixed by replacing `*` with a new `InfoTooltipWrapper`
   component, which manages layout concerns more explicitly and
   maintainably.
2024-05-19 15:02:38 +02:00
undergroundwires
2390530d92 ci/cd: fix quality checks not running on all OSes
Previously, quality checks were mistakenly configured to run only on
Ubuntu.

This commit modifies the CI/CD workflow to use the matrix strategy,
allowing the quality checks to be executed on macOS, Ubuntu and Windows.

Additionally, this update resolves the `MD034/no-bare-urls Bare URL
used` linting error that surfaced when testing on Windows.
2024-05-18 13:14:05 +02:00
undergroundwires
9ab3ff75b0 Migrate to GitHub issue forms
This commit transitions from HTML-based issue templates to GitHub issue
forms, enhancing user experience by preventing accidental submissions
with comment-like metadata. This change makes submitting issues more
intuitive and reduces the chances of user errors (such as #355).

Key change include:

- Use a friendlier tone in the templates.
- Detail examples and descriptions to guide users more effectively.
- Rename templates for improved clarity and easy navigation.
- Add "a note from the maintainer".
- Include a direct link for donations to support the project.
2024-05-17 17:52:14 +02:00
undergroundwires
d25c4e8c81 Add support for macOS universal binary #348, #362
This commit introduces a universal binary format in the distributed MDG
files for macOS, improving support for both Apple Silicon (ARM) and
Intel (x64) architectures.

It uses `electron-builder` to package both architectures into a single
executable, ensuring the application can natively on any macOS hardware
without depending on the GitHub runners' architecture. It fixes the
issue related to prior releases that supported only the architecture of
the build environment itself, which is subject to change.

Changes:

- Update DMG distribution to include both ARM64 and x64 architectures.
- Enhance system requirements documentation to reflect support for both
  architectures.
- Modify CI/CD workflows to check desktop runtime errors for both ARM64
  and x64 versions on macOS.

Resolves:

- Issue #348: Initial request for Apple Silicon support.
- Issue #362: Correction of distribution limited to ARM64 in release
  0.13.3.

`electron-builder` support:
- electron-userland/electron-builder#5475
- electron-userland/electron-builder#5689
- electron-userland/electron-builder#5426
2024-05-16 10:53:55 +02:00
undergroundwires
4a7efa27c8 Fix e2e test failing on Windows
The recent addition of revert logic in first visible card on Windows
(Privacy Cleanup) in cec0b4b, introduced an issue where end-to-end (e2e)
started failing due to the handling of hidden elements.

This commit improves the test to correctly handle the hidden card
scenario, explicitly filtering visible elements to ensure that only
visible elements are handled.
2024-05-15 09:03:18 +02:00
undergroundwires
cec0b4b4f6 win: standardize registry edit + delete on revert
This commit standardizes the management of registry keys and their
corresponding revert on delete action across all scripts using
`SetRegistryValue` function.

It improves script reliability, addresses previous errors, and corrects
the revert actions to match the default OS state when not explicitly set
by the OS.

Key changes:

- Use SetRegistryValue for uniformity.
- Remove error messages for non-existent registry keys, recognizing them
  as expected states rather than errors.
- Add missing revert actions to scripts where they were absent.
- Correct the revert logic in existing scripts to match the default OS
  configurations, particularly when the OS does not set a default value.
- Update documentation about default OS state for the related scripts.

This change improves maintainability by centralizing and standardizing
registry interactions, reducing the risk of errors and inconsistencies
in script behaviors.
2024-05-14 12:41:20 +02:00
undergroundwires
a1922c50c1 ci/cd: fix recent Docker build failures on macOS
The GitHub workflow for testing Docker builds on macOS was consistently
failing. This commit downgrades the macOS version used for Docker tests
to `macos-13`, which is the latest Intel-based macOS runner, instead of
the ARM-based `macos-14` which `macos-latest` points to.

This change is necessary because the hypervisor framework required for
Docker is not supported on the ARM-based macOS runners provided by
GitHub. This issue was causing failures when attempting to run Colima
with QEMU using `-accel hvf`, which is unsupported on these runners.
Switching to an Intel-based runner resolves this issue.

Related issues:
- actions/runner-images#9460
- actions/runner-images#9741
- abiosoft/colima#1023
2024-05-13 11:09:58 +02:00
undergroundwires
870120bc13 Add specific empty function name compiler error
This commit adds checks to rjeect functions with empty or whitespace
names. The compiler throws a specific errror when it encounters a
function data object lacking a proper name.

This provides early detection and clear feedback on invalid function
definitions, helping in faster debugging and ensuring script integrity
in the compilation process.

The enhancement aims to provide early detection and clear feedback
on invalid function definitions, aiding in faster debugging and
ensuring script integrity in the compilation process.

when it encounters a function data object lacking a proper name.
It covers scenarios where the function name might be an empty string,
undefined, or solely consist of whitespace.
2024-05-12 12:32:42 +02:00
undergroundwires-bot
f38cf73485 ⬆️ bump everywhere to 0.13.3 2024-05-11 09:58:21 +00:00
undergroundwires
9fd193e676 win: categorize and rename network security #131
This commit restructures the categorization of network security-related
scripts to improve clarity and align with user expectations. It involves
renaming and reorganizing categories to more accurately reflect their
functions beyond just IIS configurations.

This reorganization helps users find and utilize network security
scripts more efficiently and ensures that the categorization accurately
reflects the broader application of the scripts beyond server
configurations.

Changes:

- Merge network security enhancements under a single category.
- Rename categories for simplicity and increased technical accuracy.
- Flatten nested categories to streamline navigation and enhance
  clarity.
- Update documentation to match the new category structures.
- Revise script recommendations to encourage broader use.
- Fix revert codes of some related scripts to reflect default OS
  settings.
2024-05-11 11:39:28 +02:00
undergroundwires
52a4730073 ci/cd: remove check-latest from setup-node
This commit addresses the issue where Windows GitHub runners experience
failures due to unstable Node.js releases, particularly version 20.13.0,
as detailed in nodejs/node#52884 and nodejs/node#52682.

The 'check-latest' input in the 'setup-node' GitHub Action forces
every job to verify and potentially install the latest Node.js version.
This input was originally introduced to reduce maintenance efforts to
keep CI/CD setup up-to-date with the latest Node version.

However, the necessity to always run the latest Node.js version is not
critical for the CI/CD setup. Additionally, it causes increased network
requests and may inadvertently introduce unstable Node.js versions.

This commit removes the 'check-latest' option to prevent the immediate
adoption of new, potentially unstable Node.js releases, thus simplifying
the CI/CD pipeline. This keeps  CI/CD process is robust and predictable,
reducing the chances of unexpected disruptions in service deployment.
2024-05-10 12:18:16 +02:00
undergroundwires
bc4879cfe9 Fix Chromium scrollbar-induced layout shifts
This commit addresses an issue in Chromium on Linux and Windows where
the appearance of a vertical scrollbar causes unexpected horizontal
layout shifts. This behavior typically occurs when the window is
resized, a card is opened or a script is selected, resulting in content
being pushed to the left.

The solution implemented involves using `scrollbar-gutter: stable` to
ensure space is always allocated for the scrollbar, thus preventing any
shift in the page layout. This fix primarily affects Chromium-based
browsers on Linux and Windows. It has no impact on Firefox on any
platform, or any browser on macOS (including Chromium). Because these
render the scrollbar as an overlay, and do not suffer from this issue.

Steps to reproduce the issue using Chromium browser on Linux/Windows:

1. Open the app with a height large enough where a vertical scrollbar is
   not visible.
2. Resize the window to a height that triggers a vertical scrollbar.
3. Notice the layout shift as the body content moves to the right.

Changes:

- Add a CSS mixin to handle scrollbar gutter allocation with a fallback.
- Add support for modal dialog background lock to handle
  `scrollbar-gutter: stable;` in calculations to avoid layout shift when
  a modal is open.
- Add E2E test to avoid regression.
- Update DevToolkit to accommodate new scrollbar spacing.
2024-05-09 18:35:02 +02:00
undergroundwires
dd71536316 Fix misaligned tooltip positions in modal dialogs
This commit fixes a bug that causes tooltips to be slightly misaligned.

Tooltip positioning was incorrect during modal transitions due to their
initial movement, causing tooltips to align incorrectly at the start of
the animation rather than the end.

One way to solve this would be using `autoUpdate` from `floating-ui`
with `animationFrame: true`. However, this recalculates positions tens
of times per second, impacting performance. This is a monkey solution.

This commit adopts a more efficient approach by updating tooltip
positions only at the end of the transitions, which reduces calculations
and conserves resources.

Key changes:

- Addd transition end event listener for updating tooltip positions.
- Use throttling to eliminate excessive position recalculations.

Other supporting changes:

- Improve throttle function to support efficient recalculations of
  positions:
  - Add ability to optionally exclude the first execution (leading
    call).
  - Refactor to simplify it make it easier to follow and read.
  - Fix a bug where initial calls were incorrectly throttled if
    `dateNow()` returned `0`.
- Introduce and use a global hook for efficient DOM event management.
  This greatily introduce safety, reuse and testability of event
  listening.
2024-05-08 15:24:12 +02:00
undergroundwires
a3343205b1 Fix win execution with whitespace in username #351
This commit addresses the issue where scripts fail to execute on Windows
environments with usernames containing spaces. The problem stemmed from
PowerShell and cmd shell's handling of spaces in quoted arguments.

The solution involves encoding PowerShell commands before execution,
which mitigates the quoting issues previously causing script failures.
This approach is now integrated into the execution flow, ensuring that
commands are correctly handled irrespective of user names or other
variables that may include spaces.

Changes:

- Implement encoding for PowerShell commands to handle spaces in usernames
  and other similar scenarios.
- Update script documentation URLs to reflect changes in directory
  structure.

Fixes #351
2024-05-07 13:57:19 +02:00
undergroundwires
1d7cafc831 Fix VSCode script issues with added CI/CD tests
- Correct incorrect attribute in `configure_vscode.py`.
- Introduce CI tests for early error detection in the script.
- Replace emojis with ASCII in CI logs to avoid Windows encoding issues.
2024-05-06 17:18:19 +02:00
undergroundwires
c75df1c8c1 win: improve enabling secure connections #175
This commit refines the configuration of TLS and DTLS protocols on
Windows to enhance compatibility and stability across different
Windows versions.

Changes:

- Enable TLS 1.3 exclusively on Windows 11 and newer, addressing
  stability concerns with previous Windows versions, and resolving
  issue #175.
- Enable DTLS 1.2, replacing DTLS 1.3 due to lack of support in
  Windows. DTLS is enabled only on Windows 10 version 16007 and later
  for compatibility.
- Reorganize script categories for better clarity and manageability.
- Update revert codes for registry deletions to prevent false negative
  error outputs.
- Adjust recommendation levels to encourage more scripts due to system
  stability and documentation improvements introduced in this commit.
- Remove incorrect registry keys previously set for .NET apps.
- Add missing 64-bit registry keys for .NET apps.
- Rename scripts for improved simplicity and consistency.
- Improve documentation for affected scripts, correcting the
  misleading information about DTLS 1.2 vulnerability.
- Convert hexadecimal values to decimal in scripts to improve
  clarity.
- Introduce shared functions to reduce redundancy and improve
  script maintainability.
- Add more comments in generated code and simplify existing comments.
2024-05-05 10:57:45 +02:00
undergroundwires
ab25e0a066 Improve desktop icon quality and generation
This commit refactors the icon and logo generation process by replacing
multiple dependencies with ImageMagick. This simplifies the build
process and enhances maintainability.

Key changes:

- Remove unnecessary icon files for macOS (.icns) and Linux
  (size-specific PNGs). Electron-builder can now auto-generate these
  from a single `logo.png` starting from version 19.54.0, see:
  - electron-userland/electron-builder#1682
  - electron-userland/electron-builder#2533
- Retain `ico` generation with multiple sizes to fix pixelated/bad
  looking icons on Windows, see:
  - electron-userland/electron-builder#7328
  - electron-userland/electron-builder#3867
- Replaced `svgexport`, `icon-gen`, and `electron-icon-builder`
  dependencies with ImageMagick, addressing issues with outdated
  dependencies and unreliable CI/CD builds.
- Move electron-builder build resources to
  `src/presentation/electron/build` for better project structure.
- Improve `electron-builder` configuration file by making it
  importable/reusable without prebuilding the Electron application.
2024-05-04 12:09:42 +02:00
undergroundwires
813d820b85 Fix blank window on load on desktop version #348
This commit updates the application startup behavior to prevent showing
a blank window until it's fully loaded on all platforms. This enhancement
improves the user experience by ensuring the UI only becomes visible
when it is ready to interact with.

This fix contributes to a smoother user experience by aligning the
window display timing with content readiness, thus avoiding the brief
display of an empty screen.

Changes:

- Set window to initially hide until fully loaded using the
  `ready-to-show` event.
- Show the window, focus on it and bring it front once it is loaded.
  Windows requires additional logic to put Window to front, see
  electron/electron#2867.
- Parametrize the behavior of opening developer tools for easier
  configuration during testing.
2024-05-03 12:03:36 +02:00
undergroundwires
66a56888a4 win: fix Copilot by excluding r.bing.com #329
This commit modifies the blocking behavior of `r.bing.com` due to its
extensive use across multiple Windows features, including Copilot and
Maps. Previously, included in the 'Cortana and Live Tiles' block list,
this host was causing issues for Copilot functionalites, as noted in
issue #329. By excluding `r.bing.com` from block list, this update aims
to prevent unintended disruptions without compromising the privacy gains
of other scripts.

Changes include:

- Exclude `r.bing.com` from the "Cortana and Live Tiles" block list.
- Improve documentation to clarify the role and exclusions.
- Improve documentation with consistent header for blocked hosts.
2024-05-02 13:52:09 +02:00
undergroundwires
4ef16cea56 win: improve disabling protocols
This commit groups scripts related to disabling protocols under same
category, streamlining the process for disabling protocols like NetBios,
SMBv1, and various TLS/SSL versions. It improves the documentation and
scripts of the related scripts.

Key changes:

- Introduce new category for disabling insecure protocols and move
  related scripts under it.
- Remove .NET configuration from TLS 1.0 disabling to prevent unwanted
  side effects on .NET applications, maintaining system integrity.
- Remove the script disabling DTLS 1.1 as this protocol does not exist.
- Recommend previously not recommended scripts:
  - SSL 2.0 in 'Standard' because it's already removed from Windows.
  - SSL 3.0 in 'Standard' because it's already disabled by default.
  - TLS 1.0 in 'Strict' as it's deprecated on Windows.
  - TLS 1.1 in 'Strict' as it's deprecated on Windows.
- Rename and reorder scripts for consistency and enhanced readability.
- Fix revert codes to accurately reflect successful operations, by
  adding `2>nul` on `reg delete` commands.
- Expand documentation to include detailed precautions and references,
  aiding users in understanding the implications of their actions
  (addressing user feedback from #57, #131, #183, #185).

Other supporting changes:

- Convert hexadecimal values to decimal to enhance script readability.
- Refactor scripts to utilize shared functions, improving maintainability.
- Add detailed comments within the scripts to aid in comprehension.
- Minor updates to other crypto scripts for consistency.
- Reorganize protocol listing by age for a logical script flow.
- Standardize comments across various TLS configuration scripts for
  clarity.
- Fix enabling DTLS 1.3 being categorized as disabling insecure
  connection.
2024-05-01 12:18:55 +02:00
undergroundwires
8c17396285 Fix script cancellation with new dialog on Linux
This commit improves the management of script execution process by
enhancing the way terminal commands are handled, paving the way for
easier future modifications and providing clearer feedback to users when
scripts are cancelled.

Previously, the UI displayed a generic error message which could lead to
confusion if the user intentionally cancelled the script execution. Now,
a specific error dialog will appear, improving the user experience by
accurately reflecting the action taken by the user.

This change affects code execution on Linux where closing GNOME terminal
returns exit code `137` which is then treated by script cancellation by
privacy.sexy to show the accurate error dialog. It does not affect macOS
and Windows as curret commands result in success (`0`) exit code on
cancellation.

Additionally, this update encapsulates OS-specific logic into dedicated
classes, promoting better separation of concerns and increasing the
modularity of the codebase. This makes it simpler to maintain and extend
the application.

Key changes:

- Display a specific error message for script cancellations.
- Refactor command execution into dedicated classes.
- Improve file permission setting flexibility and avoid setting file
  permissions on Windows as it's not required to execute files.
- Introduce more granular error types for script execution.
- Increase logging for shell commands to aid in debugging.
- Expand test coverage to ensure reliability.
- Fix error dialogs not showing the error messages due to incorrect
  propagation of errors.

Other supported changes:

- Update `SECURITY.md` with details on script readback and verification.
- Fix a typo in `IpcRegistration.spec.ts`.
- Document antivirus scans in `desktop-vs-web-features.md`.
2024-04-30 15:04:59 +02:00
undergroundwires
694bf1a74d win, linux, mac: fix various typos #349
This commit fixes various typos in documentation and code.

Co-authored-by: RainRat <rainrat78@yahoo.ca>
2024-04-29 13:04:33 +02:00
undergroundwires
0fc2ffc1ea Add system requirements documentation #134
- Create system requirements documentation for desktop versions,
  addressing issue #134.
- Reorganize related documents into `docs/desktop` for improved
  structure and accessibility.
- Update references to address ARM chip emulation issues noted in user
  feedback, issue #348.
2024-04-28 16:57:55 +02:00
undergroundwires
d19dde603d win: improve disabling insecure hashes #131
This commit addresses reports in issue #131 about third-party cloud
services like MEGA and Dropbox being affected by hash disabling. It
updates the documentation to guide users on the potential impact,
adjusts the recommendation levels along with other minor improvements.

- Recommend hash disabling scripts in 'Strict'.
- Expand and refine documentation, adding warnings to inform user
  decisions (addressing issues #57, #131, #175, #183).
- Add a new shared function to standardize hash disabling, increasing
  code maintainability.
- Change from hexadecimal to decimal in scripts for clarity.
- Improve code comments for better understanding.
- Add comments in generated to code to make it easier to follow.
- Fix revert codes showing errors by using `2>nul` in `reg delete`
  commands.
- Rename scripts for consistent naming conventions.

Supporting changes in other SSL/TLS handshake scripts:

- Update documentation for consistency.
- Rename shared functions for consistency and clarity.
- Improve generated code comments for clarity.
2024-04-27 11:27:26 +02:00
undergroundwires
23bac0fc76 ci/cd: lint Python scripts using pylint
This commit integrates `pylint` into the CI/CD pipeline to improve the
quality of Python scripts within the project. By enforcing stricter
linting standards, the aim is to identify and correct potential issues
more efficiently, ultimately contributing to more reliable and
maintainable code.

Changes:

- Introduce `npm run lint:pylint` command to facilitate unified way to
  run linting on different environments.
- Include `npm run lint:pylint` in the CI/CD workflow to ensure all
  commits adhere to established Python coding standards.
- Fix an issue identified by `pylint` in `configure_vscode.py`.
- Rename the workflow to match the latest naming convention.
2024-04-26 17:03:38 +02:00
undergroundwires
e18907ca91 win: improve 'Snipping Tool' removal #343
Due to changes in how Windows handles the Snipping Tool, this commit
reclassifies the tool's disablement into its own distinct category.
This update introduces alternative methods to disable the tool,
enhances documentation, and improves script functionality.

Changes include:

- Move Snipping Tool removal to a standalone category for clearer
  navigation.
- Expand documentation to better describe the tool's impact on privacy.
- Add methods to disable the tool without removing the app.
- Implement a shared function to disable specific Windows hotkeys.
- Rename Cortana shortcut disablement script for consistency.
2024-04-25 10:36:33 +02:00
undergroundwires
4e21f05031 ci/cd: add check for TODO comments
This commit introduces a new GitHub Actions job within the quality
checks workflow that scans the latest commit for TODO comments. The
intention is to prevent such comments from being merged into the main
branch, promoting cleaner and more maintainable code.

The script uses a specific pattern to avoid IDE detection and
misclassification of the script line as a TODO item itself. If any TODO
comments are found, the script exists with a non-zero status,
indicating an issue that must be addressed before proceeding.
2024-04-24 23:59:55 +02:00
undergroundwires
8b224eefe7 win: doc, improve, encourage cipher disabling
- Introduce 'Disable insecure ciphers' category to organize and group
  cipher disabling scripts.
- Expand documentation, adding cautionary notes to help users make
  informed decisions, addressing issues #57, #131, #175, and #183.
- Implement `DisableCipherAlgorithm` function to standardize the
  approach to disabling cipher algorithms, enhancing maintainability
  and promoting code reuse.
- Replace hexadecimal numbers with decimals in scripts to improve
  readability.
- Add comments to generated code for better understandability.
- Update revert codes to avoid incorrect error messages when
  operations are successful, using `2>nul` in `reg delete` commands.
- Rename scripts for consistency, incorporating 'insecure' in titles.
- Adjust recommendations to disable all insecure ciphers in 'Strict'
  mode due to security risks, and recommend disabling `NULL` in
  'Standard' mode as it removes encryption.
- Remove disabling of `DES 56`, correcting a redundancy as this cipher
  configuration does not exist.
2024-04-21 14:31:00 +02:00
undergroundwires
f261ab4cd9 win: improve disabling insecure renegotiations
This commit improves script clarity and user guidance on disabling
insecure renegotiations.

- Update script name for clarity.
- Improve documentation for better understanding.
- Recommend the script as 'Strict' to align with its security focus.
- Modify revert codes to suppress misleading error messages upon
  successful reversion by including `2>nul` in `reg delete` commands.
- Convert hexadecimal to decimal in registry commands to improve
  readability.
2024-04-20 19:18:52 +02:00
undergroundwires
f584fabb50 win: improve disabling SMBv1 protocol
- Improve documentation.
- Add disabling `mrxsmb10` service (enabled with SMB1 feature).
- Configure Windows Server service for server side.
2024-04-19 16:16:00 +02:00
undergroundwires
2eed6f4afb win: organize and document network disablement
Reorganize and document scripts for disabling network features,
enhancing their discoverability and manageability. This commit
categorizes scripts related to disabling insecure network connections,
improves documentation, and makes these scripts more accessible.

- Group scripts under `Disable insecure connections` category.
- Move SMBv1 and NetBios disablement scripts to this new category.
- Improve documentation, highlighting the security improvements
  and potential compatibility issues with older systems.

Addresses issues #57, #115, #183, #175, and #185 by simplifying the
process of troubleshooting and reversing changes if necessary.
2024-04-17 21:35:56 +02:00
undergroundwires-bot
1c9dc93246 ⬆️ bump everywhere to 0.13.2 2024-04-16 07:45:17 +00:00
507 changed files with 25622 additions and 12725 deletions

View File

@@ -1,57 +0,0 @@
---
name: Bug report (script bug or unexpected script behavior)
about: Create a bug report for generated scripts to help privacy.sexy improve
labels: bug
title: '[BUG]: '
---
<!--
Thank you for reporting an issue with generated script(s).
Please fill in as much of the template below as you're able.
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
-->
### Description
<!--
A clear and concise description of what the bug is.
-->
### OS
<!--
Which OS are you using? What version of OS you were using?
On Windows: Open "Start button" > "Settings" > "System" > "About".
On macOS: Open "Apple menu (top left corner)" > "About This Mac".
On Linux: Open terminal > type: lsb_release -a > copy paste the result.
-->
### Reproduction steps
<!--
How can the bug be recreated?
It's the most important information in the bug report. Bugs that cannot be reproduced cannot be fixed and verified.
E.g.
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
### Scripts
<!--
If applicable, please attach the generated privacy.sexy file instead of copy pasting which becomes too long.
-->
### Screenshots
<!--
If applicable, add screenshots to help explain your problem.
-->
### Additional information
<!--
If applicable, add any other context about the problem here.
-->

View File

@@ -0,0 +1,114 @@
name: "Bug Report: Script Issues"
description: 🐛 Report issues with generated scripts to enhance privacy.sexy
labels: [ 'bug' ]
title: '[Bug]: '
body:
-
type: markdown
attributes:
value: |-
Thank you for contributing to privacy.sexy and guiding our direction! 🌟
Please complete as much of the form below as possible.
Your feedback is valuable, even if you can't provide all details.
-
type: textarea
attributes:
label: Description
description: A clear and concise description of what the bug is.
placeholder: >-
For example: "After running the cleanup script, music playback stopped functioning."
validations:
required: true
-
type: textarea
attributes:
label: How can the bug be recreated?
description: |-
This is the most important information in the bug report.
Bugs that cannot be reproduced cannot be fixed or verified.
placeholder: |-
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
-
type: textarea
attributes:
label: Operating system
description: |-
Please specify your operating system and its version.
- On Windows: Open "Start button" > "Settings" > "System" > "About".
- On macOS: Open "Apple menu (top left corner)" > "About This Mac".
- On Linux: Open terminal > type: lsb_release -a > copy paste the result.
placeholder: >-
For example: "Windows 11 Pro 22H3"
validations:
required: false
-
type: textarea
attributes:
label: Script file
description: |-
If applicable, share the generated privacy.sexy file.
GitHub may restrict script file attachments.
Upload your script file to a service like [GitHub Gist](https://gist.github.com/) and share the link here.
If you used the desktop version to run the script, it is already stored on your system.
See the [documentation to locate it](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/desktop/desktop-vs-web-features.md#secure-script-executionstorage).
> **💡 Tip:** You can attach script files by dragging them into this area.
placeholder: |-
Attach the script, or post GitHub Gist link.
For example: https://gist.github.com/privacysexy-forks/6d85ad8ca27acc8c6a5417d4af28c9b6.
validations:
required: false
-
type: textarea
attributes:
label: Screenshots
description: |-
If applicable, add screenshots to help explain your problem.
> **💡 Tip:** You can attach screenshots by clicking this area to highlight it and then pasting them or dragging files in.
placeholder: Attach screenshots here or link to image hosting.
validations:
required: false
-
type: textarea
attributes:
label: Additional information
description: |-
If applicable, add any other context about the problem here.
Helpful information includes:
- Application logs (desktop version only), see: [how to find application logs](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/desktop/desktop-vs-web-features.md#logging).
- Terminal output
- Proposed solutions
- Other related context such as related issues, software behavior, etc.
> **💡 Tip:** You can attach log files by dragging them into this area.
placeholder: >-
For example: "Here are the logs I get from the privacy.sexy 0.13.2 desktop application: ..."
validations:
required: false
-
type: markdown
attributes:
value: |-
---
**✉️ A friendly note from the maintainer:**
> [!NOTE]
> We are a small open-source project with a small community.
> It can sometimes take a long time for issues to be addressed, so please be patient.
> Consider [donating](https://undergroundwires.dev/donate) to keep privacy.sexy alive and improve support ❤️.
> But your issue will eventually get attention regardless.
> <p align="right">@undergroundwires</p>
---

View File

@@ -0,0 +1,104 @@
name: "Bug Report: General"
description: 🐛 Report general issues to enhance privacy.sexy
labels: [ 'bug' ]
title: '[Bug]: '
body:
-
type: markdown
attributes:
value: |-
Thank you for contributing to privacy.sexy and guiding our direction! 🌟
Please complete as much of the form below as possible.
Your feedback is valuable, even if you can't provide all details.
-
type: textarea
attributes:
label: Description
description: Provide a clear and concise description of the issue.
placeholder: >-
For example: "I cannot select any scripts."
validations:
required: true
-
type: textarea
attributes:
label: Reproduction steps
description: |-
This is the most important information in the bug report.
Bugs that cannot be reproduced cannot be fixed or verified.
placeholder: |-
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
-
type: textarea
attributes:
label: Expected behavior
description: Describe what you expected to happen when the error occurred.
placeholder: >-
For example: "I expected the settings menu to open smoothly without crashing.".
validations:
required: true
-
type: textarea
attributes:
label: Screenshots
description: |-
If applicable, add screenshots to help explain your problem.
> **💡 Tip:** You can attach screenshots by clicking this area to highlight it and then pasting them or dragging files in.
placeholder: >-
Attach screenshots here or link to image hosting.
validations:
required: false
-
type: textarea
attributes:
label: privacy.sexy environment details
description: |-
If applicable, mention how you were using privacy.sexy when the bug occurred:
- Web (on which operating system and browser?)
- Or desktop (Windows, macOS, or Linux?)
placeholder: >-
For example: "The web version on Edge browser on Windows 11 23H2."
validations:
required: false
-
type: textarea
attributes:
label: Additional context
description: |-
If applicable, add any other context about the problem here.
Helpful information includes:
- Application logs (desktop version only), see: [how to find application logs](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/desktop/desktop-vs-web-features.md#logging).
- Terminal output
- Proposed solutions
- Other related context such as related issues, software behavior, etc.
> **💡 Tip:** You can attach log files by dragging them into this area.
placeholder: >-
For example: "Here are the logs I get from the privacy.sexy 0.13.2 desktop application: ..."
validations:
required: false
-
type: markdown
attributes:
value: |-
---
**✉️ A friendly note from the maintainer:**
> [!NOTE]
> We are a small open-source project with a small community.
> It can sometimes take a long time for issues to be addressed, so please be patient.
> Consider [donating](https://undergroundwires.dev/donate) to keep privacy.sexy alive and improve support ❤️.
> But your issue will eventually get attention regardless.
> <p align="right">@undergroundwires</p>
---

View File

@@ -1,55 +0,0 @@
---
name: Bug report (unrelated to generated scripts)
about: Create a bug report that's not related to generated scripts to help privacy.sexy improve
labels: bug
title: '[BUG]: '
---
<!--
Thank you for reporting an issue.
Please fill in as much of the template below as you're able.
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
-->
### Description
<!--
A clear and concise description of what the bug is.
-->
### Reproduction steps
<!--
It's the most important information in the bug report. Bugs that cannot be reproduced cannot be fixed and verified.
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
### Expected behavior
<!--
A clear and concise description of what you expected to happen.
-->
### Screenshots
<!--
If applicable, add screenshots to help explain your problem.
-->
### Distribution
<!--
If applicable, mention how you were using privacy.sexy when the bug was encountered:
- Web (on Desktop or mobile?)
- Or desktop (Windows, macOS or Linux?)
-->
### Additional context
<!--
If applicable, add any other context about the problem here.
-->

View File

@@ -1,36 +0,0 @@
---
name: Feature request
about: Suggest an idea for privacy.sexy
labels: enhancement
---
<!--
Thank you for suggesting an idea to improve privacy better 🤗.
Please fill in as much of the template below as you're able.
-->
### Problem description
<!--
What are we trying to solve?
Please add a clear and concise description of the problem you are seeking to solve with this feature request.
E.g. I'm always frustrated when [...]
-->
### Proposed solution
<!--
Describe the solution you'd like in a clear and concise manner.
-->
### Alternatives considered
<!--
A clear and concise description of any alternative solutions or features you've considered.
-->
### Additional information
<!--
If applicable, add any other context or screenshots about the feature request here.
-->

View File

@@ -0,0 +1,73 @@
name: "Suggestion: Feature"
description: 💡 Suggest new ideas to enhance privacy.sexy
labels: [ 'enhancement' ]
title: '[Feature]: '
body:
-
type: markdown
attributes:
value: |-
Thank you for contributing to privacy.sexy and guiding our direction! 🌟
Please complete as much of the form below as possible.
Your feedback is valuable, even if you can't provide all details.
-
type: textarea
attributes:
label: Problem statement
description: |-
What are we trying to solve?
Please add a clear and concise description of the problem you are seeking to solve with this feature request.
placeholder: >-
For example: "Every time I use the app, I struggle with..."
validations:
required: true
-
type: textarea
attributes:
label: Proposed solution
description: |-
Describe the solution you'd like in a clear and concise manner.
placeholder: >-
For example: "It would be great if the app could..."
validations:
required: true
-
type: textarea
attributes:
label: Alternatives considered
description: |-
Have you considered any alternative solutions or features?
Different perspectives can inspire new ideas.
placeholder: >-
For example: "We could also solve it by...".
validations:
required: false
-
type: textarea
attributes:
label: Additional information
description: |-
If applicable, add any other context or screenshots about the feature request here.
> **💡 Tip:** You can attach files or screenshots by dragging them into this area.
placeholder: >-
For example: "Challenges can be ..., but I'm unsure about ..., here is some documentation about it: ..."
validations:
required: false
-
type: markdown
attributes:
value: |-
---
**✉️ A friendly note from the maintainer:**
> [!NOTE]
> We are a small open-source project with a small community.
> It can sometimes take a long time for issues to be addressed, so please be patient.
> Consider [donating](https://undergroundwires.dev/donate) to keep privacy.sexy alive and improve support ❤️.
> But your issue will eventually get attention regardless.
> <p align="right">@undergroundwires</p>
---

View File

@@ -1,60 +0,0 @@
---
name: New script suggestion
about: Suggest a new script for privacy.sexy
labels: enhancement
---
<!--
Thank you for contributing to privacy.sexy! 🌟
For guidance, see our script guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md.
Consider submitting a PR for faster implementation: https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md#extend-scripts.
-->
### Operating system
<!--
Specify the OS: Windows, macOS, or Linux.
-->
### Name
<!--
Suggest a name for the script.
Naming conventions: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#name.
-->
### Code
<!--
Provide or explain the code to execute when the script runs.
Code guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#code.
-->
### Revert code
<!--
Include code to revert changes to the default state.
Leave blank for non-reversible scripts.
-->
### Category
<!--
Suggest a category for the script.
If unsure, leave blank for maintainers to decide.
-->
### Recommendation level
<!--
Suggest a recommendation level: STANDARD (non-breaking), STRICT (limits functionality), or NONE (for advanced users).
If unsure, leave blank for maintainers to decide.
-->
### Documentation/References
<!--
Provide any relevant documentation or references.
Prefer high-quality sources such as vendor documentation.
Documentation guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#documentation.
-->

View File

@@ -0,0 +1,133 @@
name: "Suggestion: New Script"
description: 💡 Suggest new scripts to enhance privacy.sexy
labels: [ 'enhancement' ]
title: '[New script]: '
body:
-
type: markdown
attributes:
value: |-
Thank you for contributing to privacy.sexy and guiding our direction! 🌟
Please complete as much of the form below as possible.
Your feedback is valuable, even if you can't provide all details.
For guidance, see our [script guidelines](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md).
Consider submitting a PR to get your script added more quickly: (see [CONTRIBUTING.md](https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md#extend-scripts))
-
type: dropdown
attributes:
label: Operating system
description: Which operating system will the new script configure?
options:
- macOS
- Windows
- Linux
- All of them
validations:
required: false
-
type: textarea
attributes:
label: Name of the script
description: |-
Suggest a name for the script that clearly describes its function.
See [script naming conventions](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#name) for best practices.
placeholder: E.g, "Disable error data submission"
validations:
required: true
-
type: textarea
attributes:
label: Documentation/References
description: |-
Provide any relevant documentation or references.
Prefer high-quality sources such as vendor documentation.
See [documentation guidelines](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#documentation) for best practices.
placeholder: >-
For example: "This script will disable the error data submission, see https://microsoft.com/...".
validations:
required: true
-
type: textarea
attributes:
label: Code
description: |-
If possible, provide or explain the code that the script should execute.
See [script code guidelines](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#code).
placeholder: |-
For example: "Set registry key like this `reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" /v "AllowTelemetry" /t "REG_DWORD" /d "1"`".
validations:
required: false
-
type: textarea
attributes:
label: Revert code
description: |-
If applicable, provide revert code to restore the changes made by the script.
The revert code restores changes to their default state before script execution.
Leave blank for non-reversible scripts.
placeholder: |-
For example: "Revert to operating system default like this `reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" /v "AllowTelemetry" /t "REG_DWORD" /d "0"`".
validations:
required: false
-
type: textarea
attributes:
label: Suggested category
description: |-
Suggest a category for the script.
If unsure, leave blank for maintainers to decide.
placeholder: >-
For example: "Privacy Cleanup > Clear system logs"
-
type: dropdown
attributes:
label: Recommendation level
description: |-
Suggest a recommendation level for the script:
- **Standard**: Recommended for most users without side-effects.
- **Strict**: Provides improved privacy at the cost of some functionality.
- **None**: For advanced users or specific needs.
If unsure, leave blank for maintainers to decide.
options:
- Standard
- Strict
- None (do not recommend)
validations:
required: false
-
type: textarea
attributes:
label: Additional information
description: |-
If applicable, add any other context or screenshots about the script request here.
> **💡 Tip:** You can attach additional documents or screenshots by dragging them into this area or pasting directly.
placeholder: >-
For example: "Challenges can be ..., but I am unsure about ..."
validations:
required: false
-
type: markdown
attributes:
value: |-
---
**✉️ A friendly note from the maintainer:**
> [!NOTE]
> We are a small open-source project with a small community.
> It can sometimes take a long time for issues to be addressed, so please be patient.
> Consider [donating](https://undergroundwires.dev/donate) to keep privacy.sexy alive and improve support ❤️.
> But your issue will eventually get attention regardless.
> <p align="right">@undergroundwires</p>
---

View File

@@ -1 +1,7 @@
blank_issues_enabled: true
# This file must be named `config.yml`. GitHub does not recognize the file if it is named `config.yaml`.
blank_issues_enabled: true
contact_links:
- name: Donate
url: https://undergroundwires.dev/donate/
about: ❤️ Donate to support the free software you love to keep it alive.
# A separate link for reporting vulnerabilities is not included here because GitHub generates it automatically.

View File

@@ -6,4 +6,4 @@ runs:
uses: actions/setup-node@v4
with:
node-version: 20.x
check-latest: true
# check-latest: true # Newest versions can potentially have undiscovered bugs or regressions

View File

@@ -0,0 +1,15 @@
inputs:
name:
required: true
path:
required: true
runs:
using: composite
steps:
-
name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}

View File

@@ -72,16 +72,19 @@ jobs:
build-docker:
strategy:
matrix:
os: [ macos, ubuntu ] # Windows runners do not support Linux containers
os:
- macos-13 # Downgraded due to lack of nested virtualization support in ARM-based runners (See: actions/runner-images#9460, actions/runner-images#9741, abiosoft/colima#1023)
- ubuntu-latest
# - windows-latest # Windows runners do not support Linux containers
fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest
runs-on: ${{ matrix.os }}
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Install Docker on macOS
if: matrix.os == 'macos' # macOS runner is missing Docker
if: contains(matrix.os, 'macos') # macOS runner is missing Docker
run: |-
# Install Docker
brew install docker

View File

@@ -9,9 +9,13 @@ jobs:
run-check:
strategy:
matrix:
os: [ macos, ubuntu, windows ]
os:
- macos-latest # Apple silicon (ARM64)
- macos-13 # Intel-based (x86-64)
- ubuntu-latest
- windows-latest
fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest
runs-on: ${{ matrix.os }}
steps:
-
name: Checkout
@@ -24,7 +28,7 @@ jobs:
uses: ./.github/actions/npm-install-dependencies
-
name: Configure Ubuntu
if: matrix.os == 'ubuntu'
if: contains(matrix.os, 'ubuntu') # macOS runner is missing Docker
shell: bash
run: |-
sudo apt update
@@ -66,7 +70,7 @@ jobs:
-
name: Upload screenshot
if: always() # Run even if previous step fails
uses: actions/upload-artifact@v3
uses: ./.github/actions/upload-artifact
with:
name: screenshot-${{ matrix.os }}
path: screenshot.png

View File

@@ -1,10 +1,10 @@
name: quality-checks
name: checks.quality
on: [ push, pull_request ]
jobs:
lint:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
lint-command:
@@ -28,3 +28,74 @@ jobs:
-
name: Lint
run: ${{ matrix.lint-command }}
todo-check:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Scan latest commit for TODO comments
shell: bash
run: |-
readonly todo_comment_search_pattern='TODO'':' # Define search pattern in parts to prevent IDE from flagging this script line as a TODO item
if git grep "$todo_comment_search_pattern" HEAD; then
echo 'TODO comments found in the latest commit.'
exit 1
else
echo 'No TODO comments found in the latest commit.'
exit 0
fi
pylint:
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: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
-
name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
-
name: Analyzing the code with pylint
run: npm run lint:pylint
validate-collection-files:
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: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
-
name: Install dependencies
run: python3 -m pip install -r ./scripts/validate-collections-yaml/requirements.txt
-
name: Validate
run: python3 ./scripts/validate-collections-yaml

View File

@@ -15,6 +15,10 @@ jobs:
-
name: Checkout
uses: actions/checkout@v4
-
name: Install ImageMagick on macOS
if: matrix.os == 'macos'
run: brew install imagemagick
-
name: Setup node
uses: ./.github/actions/setup-node
@@ -53,3 +57,31 @@ jobs:
-
name: Run install-deps
run: ${{ matrix.install-command }}
configure-vscode:
runs-on: ${{ matrix.os.name }}-latest
strategy:
matrix:
os:
- name: macos
install-vscode-command: brew install --cask visual-studio-code
- name: ubuntu
install-vscode-command: sudo snap install code --classic
- name: windows
install-vscode-command: choco install vscode
fail-fast: false # Still interested to see results from other combinations
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
-
name: Install VSCode
run: ${{ matrix.os.install-vscode-command }}
-
name: Configure VSCode
run: python3 ./scripts/configure_vscode.py

View File

@@ -51,14 +51,14 @@ jobs:
-
name: Upload screenshots
if: failure() # Run only if previous steps fail because screenshots will be generated only if E2E test failed
uses: actions/upload-artifact@v3
uses: ./.github/actions/upload-artifact
with:
name: e2e-screenshots-${{ matrix.os }}
path: ${{ steps.artifacts.outputs.SCREENSHOTS_DIR }}
-
name: Upload videos
if: always() # Run even if previous steps fail because test run video is always captured
uses: actions/upload-artifact@v3
uses: ./.github/actions/upload-artifact
with:
name: e2e-videos-${{ matrix.os }}
path: ${{ steps.artifacts.outputs.VIDEOS_DIR }}

4
.gitignore vendored
View File

@@ -14,3 +14,7 @@ node_modules
# macOS
.DS_Store
# Python
__pycache__
.venv

View File

@@ -5,8 +5,10 @@
"wengerk.highlight-bad-chars", // Highlights bad chars.
"wayou.vscode-todo-highlight", // Highlights TODO.
"wix.vscode-import-cost", // Shows in KB how much a require include in code.
// Documentation
// Markdown
"davidanson.vscode-markdownlint", // Lints markdown.
// YAML
"redhat.vscode-yaml", // Lints YAML files, validates against schema.
// TypeScript / JavaScript
"dbaeumer.vscode-eslint", // Lints JavaScript/TypeScript.
"pmneo.tsimporter", // Provides better auto-complete for TypeScripts imports.

View File

@@ -1,5 +1,107 @@
# Changelog
## 0.13.5 (2024-06-26)
* ci/cd: centralize and bump artifact uploads | [22d6c79](https://github.com/undergroundwires/privacy.sexy/commit/22d6c7991eb2c138578a7d41950f301906dbf703)
* win: document and improve Firefox telemetry #259 | [8341411](https://github.com/undergroundwires/privacy.sexy/commit/8341411be434c6d145e942b1792020ccf02f58c8)
* Add image to `README.md` to thank supporters | [fa2a92b](https://github.com/undergroundwires/privacy.sexy/commit/fa2a92bf893933bf5cd04512a712b7aa1b921277)
* win: improve executable blocking, Chrome reporting | [f21ef92](https://github.com/undergroundwires/privacy.sexy/commit/f21ef9250a2f459dbd4f789d857c78298fc202e6)
* mac: discourage and document captive portal script | [b29cd7b](https://github.com/undergroundwires/privacy.sexy/commit/b29cd7b5f74accf92c9700c3171670f82c8cb3b3)
* win: fix revert scripts for removing shortcuts | [8becc7d](https://github.com/undergroundwires/privacy.sexy/commit/8becc7dbc46af4441900e9841a716a53735bc82e)
* Refactor to unify scripts/categories as Executable | [c138f74](https://github.com/undergroundwires/privacy.sexy/commit/c138f74460bafaba3da55a65f3942bb6f95b1d99)
* Add object property validation in parser #369 | [6ecfa9b](https://github.com/undergroundwires/privacy.sexy/commit/6ecfa9b954edc10401acaf5c735eec0fc9f991cd)
* win: fix missing app access recommendations #369 | [1c2d82d](https://github.com/undergroundwires/privacy.sexy/commit/1c2d82dc9bd412ea601ab2550ba0b4f7d144f8e8)
* win: fix text and handwriting script omission #369 | [1a10cf2](https://github.com/undergroundwires/privacy.sexy/commit/1a10cf2e5f87cd8eb421ef77f6ce764b5482515e)
* mac: document, improve, encourage clearing logs | [e9a5285](https://github.com/undergroundwires/privacy.sexy/commit/e9a52859f63609c3f56def0b3e4d1ac6e5661536)
* Add schema validation for collection files #369 | [dc03bff](https://github.com/undergroundwires/privacy.sexy/commit/dc03bff324d673101002bb16f14e0429e8170fbb)
* win: fix incomplete VSCEIP, location scripts | [48761f6](https://github.com/undergroundwires/privacy.sexy/commit/48761f62a242f0910307994271cbe6730fb30f7e)
* Add type validation for parameters and fix types | [fac26a6](https://github.com/undergroundwires/privacy.sexy/commit/fac26a6ca07479c84fe62c5ea2a572dad1898ef8)
* Bump Electron to latest | [ed93614](https://github.com/undergroundwires/privacy.sexy/commit/ed93614ca34b1ab166e645cc5bedd497b0caeaac)
* Trim compiler error output for better readability | [78c62cf](https://github.com/undergroundwires/privacy.sexy/commit/78c62cfc953dbba543d8bdc42828a4ef4b13a7c7)
* win: fix errors due to missing Edge uninstaller | [2f82873](https://github.com/undergroundwires/privacy.sexy/commit/2f828735a87f98ba87b4fc826823d1482d4f2db2)
* win: fix latest Edge removal on Windows 10 #309 | [e7031a3](https://github.com/undergroundwires/privacy.sexy/commit/e7031a3ae4e57b6522c6ca67fc30e8a8718506b2)
* win: categorize, rename, doc Chrome & Edge scripts | [f286f92](https://github.com/undergroundwires/privacy.sexy/commit/f286f92b1fec49e89eea8982dffbc3d6ef1defde)
* win: add disabling Edge/WebView2 auto-updates #309 | [ed7e69c](https://github.com/undergroundwires/privacy.sexy/commit/ed7e69c07efe83fdb7f4af13aa220ff991fbbe59)
* win, linux, mac: fix typos #373 | [c09c5ff](https://github.com/undergroundwires/privacy.sexy/commit/c09c5ffa47865f7c76910644558b6783ed44f1e4)
* win: add more Edge scripts including AI & ads | [1430d52](https://github.com/undergroundwires/privacy.sexy/commit/1430d5215ab094d8201710761d631dc2bd740918)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.13.4...0.13.5)
## 0.13.4 (2024-05-27)
* Add specific empty function name compiler error | [870120b](https://github.com/undergroundwires/privacy.sexy/commit/870120bc13909a3681e0f0a2351806849476342f)
* ci/cd: fix recent Docker build failures on macOS | [a1922c5](https://github.com/undergroundwires/privacy.sexy/commit/a1922c50c12b3b7806e9e681ace842194a178bda)
* win: standardize registry edit + delete on revert | [cec0b4b](https://github.com/undergroundwires/privacy.sexy/commit/cec0b4b4f63c3563a0e7923ce6324a38d71a3955)
* Fix e2e test failing on Windows | [4a7efa2](https://github.com/undergroundwires/privacy.sexy/commit/4a7efa27c8df73ef9b7960afed29f216b066cba2)
* Add support for macOS universal binary #348, #362 | [d25c4e8](https://github.com/undergroundwires/privacy.sexy/commit/d25c4e8c812b8d012010ba38070a2931dcd28908)
* Migrate to GitHub issue forms | [9ab3ff7](https://github.com/undergroundwires/privacy.sexy/commit/9ab3ff75b0a69ac2ba27dd02e82db9b5bd76ea0f)
* ci/cd: fix quality checks not running on all OSes | [2390530](https://github.com/undergroundwires/privacy.sexy/commit/2390530d929fb92c266558c52376569a0ecb90c1)
* Bump Vue to latest and fix universal selector CSS | [aae5434](https://github.com/undergroundwires/privacy.sexy/commit/aae54344511ec51d17ad0420a92cb5a064e0e7bb)
* Centralize and optimize `ResizeObserver` usage | [2923621](https://github.com/undergroundwires/privacy.sexy/commit/292362135db0519ec1050bab80ed373aad115731)
* win: improve app access disabling and docs #138 | [ff3d5c4](https://github.com/undergroundwires/privacy.sexy/commit/ff3d5c48419f663379f5aba8936636c22f2c5de8)
* win: document and discourage RSA key script #363 | [f347fde](https://github.com/undergroundwires/privacy.sexy/commit/f347fde0c85f8b51b0060fdea0a2724b042aaeed)
* win: improve printing removal /w Print Queue #279 | [150e067](https://github.com/undergroundwires/privacy.sexy/commit/150e0670392bb62348c20ec644a4ed8a6bbffe74)
* win: discourage blocking app access #121 #339 #350 | [7794846](https://github.com/undergroundwires/privacy.sexy/commit/77948461856e6837ddfbcbbef72a1bf9fc706b4e)
* Improve context for errors thrown by compiler | [4212c7b](https://github.com/undergroundwires/privacy.sexy/commit/4212c7b9e0b1500378a1e4e88efc2d59f39f3d29)
* win: document disabling firewall #115 #152 #364 | [12b1f18](https://github.com/undergroundwires/privacy.sexy/commit/12b1f183f7ce966d6ce090d98aeea7ec491f8c7c)
* win: add script to disable Recall feature | [ce4cfdd](https://github.com/undergroundwires/privacy.sexy/commit/ce4cfdd169b7da0edc3da61143c988ed5f3c976e)
* win, mac, linux: fix typos and dead URLs #367 | [9e34e64](https://github.com/undergroundwires/privacy.sexy/commit/9e34e644493674ca709b64a47206763d5d4bd60c)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.13.3...0.13.4)
## 0.13.3 (2024-05-11)
* win: organize and document network disablement | [2eed6f4](https://github.com/undergroundwires/privacy.sexy/commit/2eed6f4afb6cf85fdc1d6acb808f82405a35cafd)
* win: improve disabling SMBv1 protocol | [f584fab](https://github.com/undergroundwires/privacy.sexy/commit/f584fabb50c7de70ba43751d721af94d8fa2fa8a)
* win: improve disabling insecure renegotiations | [f261ab4](https://github.com/undergroundwires/privacy.sexy/commit/f261ab4cd9a53e31325e5c6da9129542971fe84b)
* win: doc, improve, encourage cipher disabling | [8b224ee](https://github.com/undergroundwires/privacy.sexy/commit/8b224eefe71be6a556a1085d8fe20dbd4b889430)
* ci/cd: add check for TODO comments | [4e21f05](https://github.com/undergroundwires/privacy.sexy/commit/4e21f05031d6cc90cda684bd598bec4735f8103b)
* win: improve 'Snipping Tool' removal #343 | [e18907c](https://github.com/undergroundwires/privacy.sexy/commit/e18907ca91e483255b44d14d7d923d7eef92afbd)
* ci/cd: lint Python scripts using `pylint` | [23bac0f](https://github.com/undergroundwires/privacy.sexy/commit/23bac0fc76ad697abb34f3fb327df5cdeb40286a)
* win: improve disabling insecure hashes #131 | [d19dde6](https://github.com/undergroundwires/privacy.sexy/commit/d19dde603ddac47022ee2e0ea865d53857560c26)
* Add system requirements documentation #134 | [0fc2ffc](https://github.com/undergroundwires/privacy.sexy/commit/0fc2ffc1ea36a9248c6a92da85a29f7b04b33796)
* win, linux, mac: fix various typos #349 | [694bf1a](https://github.com/undergroundwires/privacy.sexy/commit/694bf1a74d935531d7cd46891823af1fa58c3c8c)
* Fix script cancellation with new dialog on Linux | [8c17396](https://github.com/undergroundwires/privacy.sexy/commit/8c173962857a39dc0c9e5886cb2af4937e6618e7)
* win: improve disabling protocols | [4ef16ce](https://github.com/undergroundwires/privacy.sexy/commit/4ef16cea56789120cd041412d86b5577cccf0725)
* win: fix Copilot by excluding `r.bing.com` #329 | [66a5688](https://github.com/undergroundwires/privacy.sexy/commit/66a56888a4b3ead1a6bfef0feffa0218535701fe)
* Fix blank window on load on desktop version #348 | [813d820](https://github.com/undergroundwires/privacy.sexy/commit/813d820b85e1b623c50f8e0325ad372bf2f344f9)
* Improve desktop icon quality and generation | [ab25e0a](https://github.com/undergroundwires/privacy.sexy/commit/ab25e0a066be14ea979dafd0f80e1091bd5d33f8)
* win: improve enabling secure connections #175 | [c75df1c](https://github.com/undergroundwires/privacy.sexy/commit/c75df1c8c1151b64cbf014383dea0b748a8c78b3)
* Fix VSCode script issues with added CI/CD tests | [1d7cafc](https://github.com/undergroundwires/privacy.sexy/commit/1d7cafc831dcc339a10646794410dad7096bfe60)
* Fix win execution with whitespace in username #351 | [a334320](https://github.com/undergroundwires/privacy.sexy/commit/a3343205b1196d5a81fd3cee2ae661ce871a7bef)
* Fix misaligned tooltip positions in modal dialogs | [dd71536](https://github.com/undergroundwires/privacy.sexy/commit/dd71536316ec819caeb418b8635d544ac80e58ad)
* Fix Chromium scrollbar-induced layout shifts | [bc4879c](https://github.com/undergroundwires/privacy.sexy/commit/bc4879cfe97becac3c54f6b40780a89464d3b772)
* ci/cd: remove `check-latest` from `setup-node` | [52a4730](https://github.com/undergroundwires/privacy.sexy/commit/52a4730073b8ebfb2ce9d530b44e4a179f5849fe)
* win: categorize and rename network security #131 | [9fd193e](https://github.com/undergroundwires/privacy.sexy/commit/9fd193e676f1f0646898f5130fbfaaf25050b2e3)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.13.2...0.13.3)
## 0.13.2 (2024-04-15)
* Update documentation for `logo-update.js` script | [4a9b430](https://github.com/undergroundwires/privacy.sexy/commit/4a9b430702bc6082426b50ecc3a06362b5720796)
* win: improve and document removing Phone apps #279 | [8924337](https://github.com/undergroundwires/privacy.sexy/commit/89243371faa5d6aef5fce52b0d54a442143cdd39)
* Fix bottom gap in card expansion panel | [79183d6](https://github.com/undergroundwires/privacy.sexy/commit/79183d64173e588d88bf074d5b50a52a71c2d885)
* ci/cd: Fix macOS Docker build reliability issues | [8a5592f](https://github.com/undergroundwires/privacy.sexy/commit/8a5592f92be4366a806afc9eee9135696a1dd993)
* ci/cd: fix IPv6 timeouts with `force-ipv4` action | [52fadcd](https://github.com/undergroundwires/privacy.sexy/commit/52fadcd6177ed06216be9c67dad57192ae02a4f9)
* ci/cd: bump Node.js environment to 20.x | [59decd1](https://github.com/undergroundwires/privacy.sexy/commit/59decd17e273bada1493eaa855c43cbabf90308f)
* ci/cd: trigger URL checks more, and limit amount | [4fb6302](https://github.com/undergroundwires/privacy.sexy/commit/4fb6302c67f2a3fedff419e8c22872593cf800ef)
* Fix overflow in tree node content on small screens | [557cea3](https://github.com/undergroundwires/privacy.sexy/commit/557cea3f4866dc33236874f5fe4d2d69ee963dae)
* Fix horizontal layout shift after script selection | [bc7e1fa](https://github.com/undergroundwires/privacy.sexy/commit/bc7e1faa1c3f2b61bf2046fdd6d6a4141b484662)
* Fix card header expansion glitch on card collapse | [5d940b5](https://github.com/undergroundwires/privacy.sexy/commit/5d940b57ef2a4c219932cd15201401f8550cfb41)
* Ignore `ResizeObserver` errors in Cypress tests | [4472c28](https://github.com/undergroundwires/privacy.sexy/commit/4472c2852e4b87083bda7979471ab9f377d17a01)
* win: improve and document secret key scripts | [49f22f0](https://github.com/undergroundwires/privacy.sexy/commit/49f22f048f39e7388633c488b5fe59101b831984)
* Fix card arrow not being animated in sync | [7b546c5](https://github.com/undergroundwires/privacy.sexy/commit/7b546c567c4683a37fe94595362f4c2bf92ffd59)
* win: improve Windows feature disablement scripts | [b68711e](https://github.com/undergroundwires/privacy.sexy/commit/b68711ef88982c0ee2b1d41b4452e899821adc64)
* Fix top script menu overflow on small screens | [b7a20d9](https://github.com/undergroundwires/privacy.sexy/commit/b7a20d9d41ea8bcefdd553b87641f3c22b4cde97)
* win: fix Visual Studio remote analysis script #327 | [4142d08](https://github.com/undergroundwires/privacy.sexy/commit/4142d084f64a3b540487ff68b28032977d12006d)
* win: improve firewall docs /w `winget` impact #142 | [ffd647d](https://github.com/undergroundwires/privacy.sexy/commit/ffd647d1529375474b81900cc7bee4c32fbf861f)
* Centralize and use global spacing variables | [ae17200](https://github.com/undergroundwires/privacy.sexy/commit/ae172000a64416e5a3e2b2e32b7846f039f445f0)
* win: improve service revert and docs | [b87b7aa](https://github.com/undergroundwires/privacy.sexy/commit/b87b7aac7d118a23a0d1bfb881e385347de4adb7)
* Bump dependencies to latest, hold ESLint | [f3571ab](https://github.com/undergroundwires/privacy.sexy/commit/f3571abeafdbe1e6d152958fab26de91a9c08bc3)
* Fix inability to tap outside modal on mobile | [cb144ae](https://github.com/undergroundwires/privacy.sexy/commit/cb144ae47273deeb7058d4b1380e480ebccdaf81)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.13.1...0.13.2)
## 0.13.1 (2024-03-22)
* ci/cd: Fix cross-platform git command compability | [255c51c](https://github.com/undergroundwires/privacy.sexy/commit/255c51c8a0524d3ea8a3b16ffc1b178650525010)

View File

@@ -60,8 +60,8 @@
<br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Quality checks status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg"
alt="Status of quality checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.quality/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml" target="_blank" rel="noopener noreferrer">
@@ -122,9 +122,12 @@
## Get started
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.1/privacy.sexy-Setup-0.13.1.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.1/privacy.sexy-0.13.1.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.1/privacy.sexy-0.13.1.AppImage). For more options, see [here](#additional-install-options).
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.5/privacy.sexy-Setup-0.13.5.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.5/privacy.sexy-0.13.5.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.5/privacy.sexy-0.13.5.AppImage). For more options, see [here](#additional-install-options).
For a detailed comparison of features between the desktop and web versions of privacy.sexy, see [Desktop vs. Web Features](./docs/desktop-vs-web-features.md).
See also:
- [Desktop vs. Web Features](./docs/desktop/desktop-vs-web-features.md): Differences and unique aspects of desktop and web versions.
- [System Requirements](./docs/desktop/system-requirements.md): Hardware and software requirements for the desktop version.
💡 Regularly applying your configuration with privacy.sexy is recommended, especially after each new release and major operating system updates. Each version updates scripts to enhance stability, privacy, and security.
@@ -183,3 +186,7 @@ Check [architecture.md](./docs/architecture.md) for an overview of design and ho
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).
## Supporters
[![Supporters appreciation banner showing the supporters](https://undergroundwires.dev/img/supporters.jpg)](https://undergroundwires.dev/supporters)

View File

@@ -43,10 +43,17 @@ privacy.sexy adopts a defense in depth strategy to protect users on multiple lay
elevation of privileges for system modifications with explicit user consent and logs every action taken with high privileges. This
approach actively minimizes potential security risks by limiting privileged operations and aligning with the principle of least privilege.
- **Secure Script Execution/Storage:**
Before executing any script, the desktop application stores a copy to allow antivirus software to perform scans. This safeguards against
any unwanted modifications. Furthermore, the application incorporates integrity checks for tamper protection. If the script file differs from
the user's selected script, the application will not execute or save the script, ensuring the processing of authentic scripts.
Recognizing that some users prefer not to keep these records, privacy.sexy provides specialized scripts for deletion of these scripts.
- **Antivirus scans:**
Before executing any script, the desktop application stores a copy to allow antivirus software to perform scans.
This step allows confirming that the scripts are secure and safe to use.
- **Tamper protection:**
The application incorporates integrity checks for tamper protection.
If the script file differs from the user's selected script, the application will not execute or save the script, ensuring the processing
of authentic scripts.
This safeguards against any unwanted modifications.
- **Clean-up:**
Recognizing that some users prefer not to keep these records, privacy.sexy provides specialized scripts for deletion of these scripts.
This allows users to maintain their privacy by removing traces of their usage patterns or script preferences.
### Update Security and Integrity

View File

@@ -1,5 +0,0 @@
# build
This folder contains files that are used by Electron to serve the desktop version.
Icons are created from the main logo file and should not be changed manually, see [related documentation](./../img/README.md).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

View File

@@ -41,5 +41,5 @@ Application layer compiles templating syntax during parsing to create the end sc
The steps to extend the templating syntax:
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts).
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Executable/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts).

View File

@@ -1,11 +1,11 @@
# Collection files
privacy.sexy is a data-driven application that reads YAML files.
This document details the structure and syntax of the YAML files located in [`application/collections`](./../src/application/collections/), which form the backbone of the application's data model.
This document details the structure and syntax of the YAML files located in [`application/collections`](./../src/application/collections/), which form the backbone of the application's data model. The YAML schema [`.schema.yaml`](./../src/application/collections/.schema.yaml) is provided to provide better IDE support and be used in automated validations.
Related documentation:
- 📖 [`collection.yaml.d.ts`](./../src/application/collections/collection.yaml.d.ts) outlines code types.
- 📖 [`Collections README`](./../src/application/collections/README.md) includes references to code as documentation.
- 📖 [Script Guidelines](./script-guidelines.md) provide guidance on script creation including best-practices.
## Objects
@@ -28,11 +28,22 @@ Related documentation:
- `scripting:` ***[`ScriptingDefinition`](#scriptingdefinition)*** **(required)**
- Sets the scripting language for all inline code used within the collection.
### `Category`
### Executables
They represent independently executable tweaks with documentation and reversibility.
An Executable is a logical entity that can
- execute once compiled,
- include a `docs` property for documentation.
It's either [Category](#category) or a [Script](#script).
#### `Category`
Represents a logical group of scripts and subcategories.
#### `Category` syntax
##### `Category` syntax
- `category:` *`string`* **(required)**
- Name of the category.
@@ -43,7 +54,7 @@ Represents a logical group of scripts and subcategories.
- `docs`: *`string`* | `[`*`string`*`, ... ]`
- Markdown-formatted documentation related to the category.
### `Script`
#### `Script`
Represents an individual tweak.
@@ -58,7 +69,7 @@ Types (like [functions](#function)):
📖 For detailed guidelines, see [Script Guidelines](./script-guidelines.md).
#### `Script` syntax
##### `Script` syntax
- `name`: *`string`* **(required)**
- Script name.

View File

@@ -1,6 +1,6 @@
# Desktop vs. Web Features
This table highlights differences between the desktop and web versions of `privacy.sexy`.
This table outlines the differences between the desktop and web versions of `privacy.sexy`.
| Feature | Desktop | Web |
| ------- | ------- | --- |
@@ -8,10 +8,8 @@ This table highlights differences between the desktop and web versions of `priva
| [Offline usage](#offline-usage) | 🟢 Available | 🟡 Partially available |
| [Auto-updates](#auto-updates) | 🟢 Available | 🟢 Available |
| [Logging](#logging) | 🟢 Available | 🔴 Not available |
| [Script execution](#script-execution) | 🟢 Available | 🔴 Not available |
| [Error handling](#error-handling) | 🟢 Advanced | 🟡 Limited |
| [Native dialogs](#native-dialogs) | 🟢 Available | 🔴 Not available |
| [Secure script execution/storage](#secure-script-executionstorage) | 🟢 Available | 🔴 Not available |
| [Native dialogs](#native-dialogs) | 🟢 Available | 🔴 Not available |
## Feature descriptions
@@ -30,11 +28,11 @@ Desktop version inherently allows offline usage.
### Auto-updates
Both the desktop and web versions of privacy.sexy provide timely access to the latest features and security improvements. The updates are automatically deployed from source code, reflecting the latest changes for enhanced security and reliability. For more details, see [CI/CD documentation](./ci-cd.md).
Both the desktop and web versions of privacy.sexy provide timely access to the latest features and security improvements. The updates are automatically deployed from source code, reflecting the latest changes for enhanced security and reliability. For more details, see [CI/CD documentation](./../ci-cd.md).
The desktop version ensures secure delivery through cryptographic signatures and version checks.
[Security is a top priority](./../SECURITY.md#update-security-and-integrity) at privacy.sexy.
[Security is a top priority](./../../SECURITY.md#update-security-and-integrity) at privacy.sexy.
> **Note for macOS users:** On macOS, the desktop version's auto-update process involves manual steps due to Apple's code signing costs.
> Users get notified about updates but might need to complete the installation manually.
@@ -53,7 +51,7 @@ Log file locations vary by operating system:
> 💡 privacy.sexy provides scripts to securely erase these logs.
### Script execution
### Secure script execution/storage
The desktop version of privacy.sexy enables direct script execution, providing a seamless and integrated experience.
This direct execution capability isn't available in the web version due to inherent browser restrictions.
@@ -69,31 +67,27 @@ These locations vary based on the operating system:
> 💡 privacy.sexy provides scripts to securely erase your script execution history.
### Error handling
**Script antivirus scans:**
The desktop version of privacy.sexy features advanced error handling capabilities.
It employs robust and reliable execution strategies, including self-healing mechanisms, and provides guidance and troubleshooting information to resolve issues effectively.
In contrast, the web version has more basic error handling due to browser limitations and the nature of web applications.
To enhance system protection, the desktop version of privacy.sexy automatically verifies the security of script
execution files by reading them back.
This process triggers antivirus scans to verify that scripts are safe before the execution.
### Native dialogs
The desktop version uses native dialogs, offering more features and reliability compared to the browser's file system dialogs.
These native dialogs provide a more integrated and user-friendly experience, aligning with the operating system's standard interface and functionalities.
### Secure script execution/storage
**Integrity checks:**
**Script integrity checks:**
The desktop version of privacy.sexy implements robust integrity checks for both script execution and storage.
Featuring tamper protection, the application actively verifies the integrity of script files before executing or saving them.
If the actual contents of a script file do not align with the expected contents, the application refuses to execute or save the script.
This proactive approach ensures only unaltered and verified scripts undergo processing, thereby enhancing both security and reliability.
Due to browser constraints, this feature is absent in the web version.
**Error handling:**
The desktop version of privacy.sexy features advanced error handling capabilities.
In scenarios where script execution or storage encounters failure, the desktop application initiates automated troubleshooting and self-healing processes.
It also guides users through potential issues with filesystem or third-party software, such as antivirus interventions.
Specifically, the application is capable of identifying when antivirus software blocks or removes a script, providing users with tailored error messages
and detailed resolution steps. This level of proactive error handling and user guidance enhances the application's security and reliability,
offering a feature not achievable in the web version due to browser limitations.
It employs robust and reliable execution strategies, including self-healing mechanisms, and provides guidance and troubleshooting information to resolve issues effectively.
This proactive error handling and user guidance enhances the application's security and reliability.
### Native dialogs
The desktop version uses native dialogs, offering more features and reliability compared to the browser's file system dialogs.
These native dialogs provide a more integrated and user-friendly experience, aligning with the operating system's standard interface and functionalities.

View File

@@ -0,0 +1,36 @@
# System Requirements for the Desktop Version
The following system requirements are the official ones for the desktop version.
While we have tested and confirmed these requirements, the application might also work on other
systems or configurations that haven't undergone official testing.
## Windows
- **Version:** Windows 10 and later.
- **Processor:** Intel Pentium 4 or later.
- **Architecture:** 64-bit (x86-64), ARM (ARM64).
> **⚠️ Compatibility Note:**
> ARM version is only compatible with Windows 11 and later.
> It runs non-natively, leading to slower performance due to emulation [1].
## macOS
- **Version:** macOS Catalina (10.15) and later.
- **Architecture:** Intel-based (x86-64), Apple silicon (ARM64).
## Linux
- **Version:** Ubuntu 18.04 and later, Fedora 32 and later, and Debian 10 and later.
- **Processor:** Intel Pentium 4 or later.
- **Architecture:** 64-bit (x86-64).
## References
System requirements reflect Electron's platform capabilities [2] and Chromium's recommended configurations [3].
For details on the build process, see [electron-builder configuration file](./../../electron-builder.cjs).
[1]: https://web.archive.org/web/20240428082726/https://learn.microsoft.com/en-us/windows/arm/add-arm-support#emulation-on-arm-based-devices-for-x86-or-x64-windows-apps "Add support Arm devices to your Windows app | Microsoft Learn | learn.microsoft.com"
[2]: https://archive.ph/2024.04.28-082958/https://github.com/electron/electron/blob/main/README.md#platform-support "Platform Support | electron/README.md at main · electron/electron · GitHub | github.com"
[3]: https://web.archive.org/web/20240428082945/https://support.google.com/chrome/a/answer/7100626?hl=en "Chrome browser system requirements - Chrome Enterprise and Education Help | support.google.com"

View File

@@ -80,8 +80,10 @@ See [ci-cd.md](./ci-cd.md) for more information.
- [**`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.
- [**`python ./scripts/configure_vscode.py`**](../scripts/configure_vscode.py):
- [**`python3 ./scripts/configure_vscode.py`**](../scripts/configure_vscode.py):
- Optimizes Visual Studio Code settings and installs essential extensions, enhancing the development environment.
- [**`python3 ./scripts/validate-collections-yaml`**](../scripts/validate-collections-yaml/README.md):
- Validates the syntax and structure of collection YAML files.
#### Automation scripts

View File

@@ -23,8 +23,10 @@ The presentation layer uses an event-driven architecture for bidirectional react
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint..
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
- [`/main/` **`index.ts`**](./../src/presentation/main.ts): Main entry for Electron, managing application windows and lifecycle events.
- [`/preload/` **`index.ts`**](./../src/presentation/main.ts): Script executed before the renderer, securing Node.js features for renderer use.
- [`/main/` **`index.ts`**](./../src/presentation/electron/main/index.ts): Main entry for Electron, managing application windows and lifecycle events.
- [`/preload/` **`index.ts`**](./../src/presentation/electron/preload/index.ts): Script executed before the renderer, securing Node.js features for renderer use.
- [**`/shared/`**](./../src/presentation/electron/shared/): Shared logic between different Electron processes.
- [**`/build/`**](./../src/presentation/electron/build/): `electron-builder` build resources directory, [README.md](./../src/presentation/electron/build/README.md).
- [**`/vite.config.ts`**](./../vite.config.ts): Contains Vite configurations for building web application.
- [**`/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.

View File

@@ -27,6 +27,7 @@ Key attributes of a good script:
- `Minimize` over `Limit`, `Reduce`
- `Maximize` over `Extend`, `Delay`, `Postpone`, `Prolong`
- `Remove` over `Uninstall`
- `Improve` over `Increase`
- Structure your phrases for clarity, examples:
- Prefer `Disable XX telemetry` over `Disable telemetry in XX`
- Prefer `Clear XX data` over `Clear data from XX`, or `Clear data of XX`.

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-template-curly-in-string */
const { join } = require('node:path');
const { readdirSync } = require('fs');
const { join, resolve } = require('node:path');
const { readdirSync, existsSync } = require('node:fs');
const { electronBundled, electronUnbundled } = require('./dist-dirs.json');
/**
@@ -17,6 +17,7 @@ module.exports = {
},
directories: {
output: electronBundled,
buildResources: resolvePathFromProjectRoot('src/presentation/electron/build'),
},
extraMetadata: {
main: findMainEntryFile(
@@ -42,7 +43,10 @@ module.exports = {
// macOS
mac: {
target: 'dmg',
target: {
target: 'dmg',
arch: 'universal',
},
},
dmg: {
artifactName: '${name}-${version}.${ext}',
@@ -53,10 +57,18 @@ module.exports = {
* Finds by accommodating different JS file extensions and module formats.
*/
function findMainEntryFile(parentDirectory) {
const files = readdirSync(parentDirectory);
const absoluteParentDirectory = resolvePathFromProjectRoot(parentDirectory);
if (!existsSync(absoluteParentDirectory)) {
return null; // Avoid disrupting other processes such `npm install`.
}
const files = readdirSync(absoluteParentDirectory);
const entryFile = files.find((file) => /^index\.(cjs|mjs|js)$/.test(file));
if (!entryFile) {
throw new Error(`Main entry file not found in ${parentDirectory}.`);
throw new Error(`Main entry file not found in ${absoluteParentDirectory}.`);
}
return join(parentDirectory, entryFile);
}
function resolvePathFromProjectRoot(pathSegment) {
return resolve(__dirname, pathSegment);
}

4196
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "privacy.sexy",
"version": "0.13.1",
"version": "0.13.5",
"private": true,
"slogan": "Privacy is sexy",
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy.",
@@ -14,7 +14,7 @@
"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 && npm run lint:pylint",
"install-deps": "node scripts/npm-install.js",
"icons:build": "node scripts/logo-update.js",
"check:desktop": "vitest run --dir tests/checks/desktop-runtime-errors --environment node",
@@ -29,6 +29,7 @@
"lint:md:consistency": "remark . --frail --use remark-preset-lint-consistent",
"lint:md:relative-urls": "remark . --frail --use remark-validate-links",
"lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml",
"lint:pylint": "pylint **/*.py",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
@@ -41,7 +42,7 @@
"electron-updater": "^6.1.9",
"file-saver": "^2.0.5",
"markdown-it": "^14.1.0",
"vue": "^3.4.21"
"vue": "^3.4.27"
},
"devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.1.0",
@@ -58,16 +59,14 @@
"@vue/test-utils": "^2.4.5",
"autoprefixer": "^10.4.19",
"cypress": "^13.7.3",
"electron": "^29.3.0",
"electron": "^31.0.2",
"electron-builder": "^24.13.3",
"electron-devtools-installer": "^3.2.0",
"electron-icon-builder": "^2.0.1",
"electron-vite": "^2.1.0",
"eslint": "8.57.0",
"eslint-plugin-cypress": "^2.15.1",
"eslint-plugin-vue": "^9.25.0",
"eslint-plugin-vuejs-accessibility": "^2.2.1",
"icon-gen": "^4.0.0",
"jsdom": "^24.0.0",
"markdownlint-cli": "^0.39.0",
"postcss": "^8.4.38",
@@ -77,7 +76,6 @@
"remark-validate-links": "^13.0.1",
"sass": "^1.75.0",
"start-server-and-test": "^2.0.3",
"svgexport": "^0.4.2",
"terser": "^5.30.3",
"tslib": "^2.6.2",
"typescript": "^5.4.5",

View File

@@ -1,6 +1,10 @@
"""
This script configures project-level VSCode settings in '.vscode/settings.json' for
development and installs recommended extensions from '.vscode/extensions.json'.
Description:
This script configures project-level VSCode settings in '.vscode/settings.json' for
development and installs recommended extensions from '.vscode/extensions.json'.
Usage:
python3 ./scripts/configure_vscode.py
"""
# pylint: disable=missing-function-docstring
@@ -40,7 +44,7 @@ def ensure_setting_file_exists() -> None:
print_success(f"Created empty {VSCODE_SETTINGS_JSON_FILE}")
except IOError as error:
print_error(f"Error creating file {VSCODE_SETTINGS_JSON_FILE}: {error}")
print(f"📄 Created empty {VSCODE_SETTINGS_JSON_FILE}")
print_success(f"Created empty {VSCODE_SETTINGS_JSON_FILE}")
def add_or_update_settings() -> None:
configure_setting_key('eslint.validate', ['vue', 'javascript', 'typescript'])
@@ -54,6 +58,10 @@ def add_or_update_settings() -> None:
# Details: # pylint: disable-next=line-too-long
# - https://archive.ph/2024.01.06-003914/https://github.com/microsoft/vscode/issues/179274, https://web.archive.org/web/20240106003915/https://github.com/microsoft/vscode/issues/179274
# Disable telemetry
configure_setting_key('redhat.telemetry.enabled', False)
configure_setting_key('gitlens.telemetry.enabled', False)
def configure_setting_key(configuration_key: str, desired_value: Any) -> None:
try:
with open(VSCODE_SETTINGS_JSON_FILE, 'r+', encoding='utf-8') as file:
@@ -98,7 +106,8 @@ def locate_vscode_cli() -> Optional[str]:
if vscode_alias:
return vscode_alias
potential_vscode_cli_paths = [
'/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code' # macOS VS Code may not register 'code' command in PATH
# VS Code on macOS may not register 'code' command in PATH
'/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code'
]
for vscode_cli_candidate_path in potential_vscode_cli_paths:
if Path(vscode_cli_candidate_path).is_file():
@@ -109,7 +118,7 @@ def remove_json_comments(json_like: str) -> str:
pattern: str = r'(?:"(?:\\.|[^"\\])*"|/\*[\s\S]*?\*/|//.*)|([^:]//.*$)'
return re.sub(
pattern,
lambda m: '' if m.group(1) else m.agroup(0), json_like, flags=re.MULTILINE,
lambda m: '' if m.group(1) else m.group(0), json_like, flags=re.MULTILINE,
)
def install_vscode_extensions(vscode_cli_path: str, extensions: list[str]) -> None:
@@ -166,16 +175,16 @@ def print_installation_results(successful_installations: int, total_extensions:
print_error("Failed to install any of the recommended extensions.")
def print_error(message: str) -> None:
print(f"💀 Error: {message}", file=sys.stderr)
print(f"[ERROR] {message}", file=sys.stderr)
def print_success(message: str) -> None:
print(f"✅ Success: {message}")
print(f"[SUCCESS] {message}")
def print_skip(message: str) -> None:
print(f"⏩ Skipped: {message}")
print(f"[SKIPPED] {message}")
def print_warning(message: str) -> None:
print(f"⚠️ Warning: {message}", file=sys.stderr)
print(f"[WARNING] {message}", file=sys.stderr)
if __name__ == "__main__":
main()

View File

@@ -2,94 +2,119 @@
* Description:
* This script updates the logo images across the project based on the primary
* logo file ('img/logo.svg' file).
*
* It handles the creation and update of various icon sizes for different purposes,
* including desktop launcher icons, tray icons, and web favicons from a single source
* SVG logo file.
*
* Usage:
* node ./scripts/logo-update.js
* node ./scripts/logo-update.js
*
* Notes:
* ImageMagick must be installed and accessible in the system's PATH
*/
import { resolve, join } from 'node:path';
import { rm, mkdtemp, stat } from 'node:fs/promises';
import { resolve, join, dirname } from 'node:path';
import { stat } from 'node:fs/promises';
import { spawn } from 'node:child_process';
import { URL, fileURLToPath } from 'node:url';
import electronBuilderConfig from '../electron-builder.cjs';
class Paths {
constructor(selfDirectory) {
const projectRoot = resolve(selfDirectory, '../');
class ImageAssetPaths {
constructor(currentScriptDirectory) {
const projectRoot = resolve(currentScriptDirectory, '../');
this.sourceImage = join(projectRoot, 'img/logo.svg');
this.publicDirectory = join(projectRoot, 'src/presentation/public');
this.electronBuildDirectory = join(projectRoot, 'build');
this.electronBuildResourcesDirectory = electronBuilderConfig.directories.buildResources;
}
get electronTrayIconFile() {
return join(this.publicDirectory, 'icon.png');
}
get webFaviconFile() {
return join(this.publicDirectory, 'favicon.ico');
}
toString() {
return `Source image: ${this.sourceImage}\n`
+ `Public directory: ${this.publicDirectory}\n`
+ `Electron build directory: ${this.electronBuildDirectory}`;
return `Source image: ${this.sourceImage}`
+ `\nPublic directory: ${this.publicDirectory}`
+ `\n\t Electron tray icon file: ${this.electronTrayIconFile}`
+ `\n\t Web favicon file: ${this.webFaviconFile}`
+ `\nElectron build directory: ${this.electronBuildResourcesDirectory}`;
}
}
async function main() {
const paths = new Paths(getCurrentScriptDirectory());
const paths = new ImageAssetPaths(getCurrentScriptDirectory());
console.log(`Paths:\n\t${paths.toString().replaceAll('\n', '\n\t')}`);
await updateDesktopLauncherAndTrayIcon(paths.sourceImage, paths.publicDirectory);
await updateWebFavicon(paths.sourceImage, paths.publicDirectory);
await updateDesktopIcons(paths.sourceImage, paths.electronBuildDirectory);
const convertCommand = await findAvailableImageMagickCommand();
await generateDesktopAndTrayIcons(
paths.sourceImage,
paths.electronTrayIconFile,
convertCommand,
);
await generateWebFavicon(
paths.sourceImage,
paths.webFaviconFile,
convertCommand,
);
await generateDesktopIcons(
paths.sourceImage,
paths.electronBuildResourcesDirectory,
convertCommand,
);
console.log('🎉 (Re)created icons successfully.');
}
async function updateDesktopLauncherAndTrayIcon(sourceImage, publicFolder) {
async function generateDesktopAndTrayIcons(sourceImage, targetFile, convertCommand) {
// Reference: https://web.archive.org/web/20240502124306/https://www.electronjs.org/docs/latest/api/tray
console.log(`Updating desktop launcher and tray icon at ${targetFile}.`);
await ensureFileExists(sourceImage);
await ensureFolderExists(publicFolder);
const electronTrayIconFile = join(publicFolder, 'icon.png');
console.log(`Updating desktop launcher and tray icon at ${electronTrayIconFile}.`);
await runCommand(
'npx',
'svgexport',
await ensureParentFolderExists(targetFile);
await convertFromSvgToPng(
convertCommand,
sourceImage,
electronTrayIconFile,
targetFile,
'512x512',
);
}
async function updateWebFavicon(sourceImage, faviconFolder) {
console.log('Updating favicon');
async function generateWebFavicon(sourceImage, faviconFilePath, convertCommand) {
console.log(`Updating favicon at ${faviconFilePath}.`);
await ensureFileExists(sourceImage);
await ensureFolderExists(faviconFolder);
await runCommand(
'npx',
'icon-gen',
`--input ${sourceImage}`,
`--output ${faviconFolder}`,
'--ico',
'--ico-name \'favicon\'',
'--report',
await ensureParentFolderExists(faviconFilePath);
await convertFromSvgToIco(
convertCommand,
sourceImage,
faviconFilePath,
[16, 24, 32, 48, 64, 128, 256],
);
}
async function updateDesktopIcons(sourceImage, electronIconsDir) {
async function generateDesktopIcons(sourceImage, electronBuildResourcesDirectory, convertCommand) {
console.log(`Creating Electron icon files to ${electronBuildResourcesDirectory}.`);
// Reference: https://web.archive.org/web/20240501103645/https://www.electron.build/icons.html
await ensureFolderExists(electronBuildResourcesDirectory);
await ensureFileExists(sourceImage);
await ensureFolderExists(electronIconsDir);
const temporaryDir = await mkdtemp('icon-');
const temporaryPngFile = join(temporaryDir, 'icon.png');
console.log(`Converting from SVG (${sourceImage}) to PNG: ${temporaryPngFile}`); // required by `icon-builder`
await runCommand(
'npx',
'svgexport',
const electronMainIconFile = join(electronBuildResourcesDirectory, 'icon.png');
await convertFromSvgToPng(
convertCommand,
sourceImage,
temporaryPngFile,
'1024:1024',
electronMainIconFile,
'1024x1024', // Should be at least 512x512
);
console.log(`Creating electron icons to ${electronIconsDir}.`);
await runCommand(
'npx',
'electron-icon-builder',
`--input="${temporaryPngFile}"`,
`--output="${electronIconsDir}"`,
'--flatten',
// Relying on `electron-builder`s conversion from png to ico results in pixelated look on Windows
// 10 and 11 according to tests, see:
// - https://web.archive.org/web/20240502114650/https://github.com/electron-userland/electron-builder/issues/7328
// - https://web.archive.org/web/20240502115448/https://github.com/electron-userland/electron-builder/issues/3867
const electronWindowsIconFile = join(electronBuildResourcesDirectory, 'icon.ico');
await convertFromSvgToIco(
convertCommand,
sourceImage,
electronWindowsIconFile,
[16, 24, 32, 48, 64, 128, 256],
);
console.log('Cleaning up temporary directory.');
await rm(temporaryDir, { recursive: true, force: true });
}
async function ensureFileExists(filePath) {
@@ -100,12 +125,60 @@ async function ensureFileExists(filePath) {
}
async function ensureFolderExists(folderPath) {
if (!folderPath) {
throw new Error('Path is missing');
}
const path = await stat(folderPath);
if (!path.isDirectory()) {
throw new Error(`Not a directory: ${folderPath}`);
}
}
function ensureParentFolderExists(filePath) {
return ensureFolderExists(dirname(filePath));
}
const BaseImageMagickConvertArguments = Object.freeze([
'-background none', // Transparent, so they do not get filled with white.
'-strip', // Strip metadata.
'-gravity Center', // Center the image when there's empty space
]);
async function convertFromSvgToIco(
convertCommand,
inputFile,
outputFile,
sizes,
) {
await runCommand(
convertCommand,
...BaseImageMagickConvertArguments,
`-density ${Math.max(...sizes).toString()}`, // High enough for sharpness
`-define icon:auto-resize=${sizes.map((s) => s.toString()).join(',')}`, // Automatically store multiple sizes in an ico image
'-compress None',
inputFile,
outputFile,
);
}
async function convertFromSvgToPng(
convertCommand,
inputFile,
outputFile,
size = undefined,
) {
await runCommand(
convertCommand,
...BaseImageMagickConvertArguments,
...(size === undefined ? [] : [
`-resize ${size}`,
`-density ${size}`, // High enough for sharpness
]),
inputFile,
outputFile,
);
}
async function runCommand(...args) {
const command = args.join(' ');
console.log(`Running command: ${command}`);
@@ -135,4 +208,27 @@ function getCurrentScriptDirectory() {
return fileURLToPath(new URL('.', import.meta.url));
}
async function findAvailableImageMagickCommand() {
// Reference: https://web.archive.org/web/20240502120041/https://imagemagick.org/script/convert.php
const potentialBaseCommands = [
'convert', // Legacy command, usually available on Linux/macOS installations
'magick convert', // Newer command, available on Windows installations
];
for (const baseCommand of potentialBaseCommands) {
const testCommand = `${baseCommand} -version`;
try {
await runCommand(testCommand); // eslint-disable-line no-await-in-loop
console.log(`Confirmed: ImageMagick command '${baseCommand}' is available and operational.`);
return baseCommand;
} catch (err) {
console.log(`Error: The command '${baseCommand}' is not found or failed to execute. Detailed error: ${err.message}"`);
}
}
throw new Error([
'Unable to locate any operational ImageMagick command.',
`Attempted commands were: ${potentialBaseCommands.join(', ')}.`,
'Please ensure ImageMagick is correctly installed and accessible.',
].join('\n'));
}
await main();

View File

@@ -0,0 +1,51 @@
# validate-collections-yaml
This script validates YAML collection files against a predefined schema to ensure their integrity.
## Prerequisites
- Python 3.x installed on your system.
## Running in a Virtual Environment (Recommended)
Using a virtual environment isolates dependencies and prevents conflicts.
1. **Create a virtual environment:**
```bash
python3 -m venv ./scripts/validate-collections-yaml/.venv
```
2. **Activate the virtual environment:**
```bash
source ./scripts/validate-collections-yaml/.venv/bin/activate
```
3. **Install dependencies:**
```bash
python3 -m pip install -r ./scripts/validate-collections-yaml/requirements.txt
```
4. **Run the script:**
```bash
python3 ./scripts/validate-collections-yaml
```
## Running Globally
Running the script globally is less recommended due to potential dependency conflicts.
1. **Install dependencies:**
```bash
python3 -m pip install -r ./scripts/validate-collections-yaml/requirements.txt
```
2. **Run the script:**
```bash
python3 ./scripts/validate-collections-yaml
```

View File

@@ -0,0 +1,62 @@
"""
Description:
This script validates collection YAML files against the expected schema.
Usage:
python3 ./scripts/validate-collections-yaml
Notes:
This script requires the `jsonschema` and `pyyaml` packages (see requirements.txt).
"""
# pylint: disable=missing-function-docstring
from os import path
import sys
from glob import glob
from typing import List
from jsonschema import exceptions, validate # pylint: disable=import-error
import yaml # pylint: disable=import-error
SCHEMA_FILE_PATH = './src/application/collections/.schema.yaml'
COLLECTIONS_GLOB_PATTERN = './src/application/collections/*.yaml'
def main() -> None:
schema_yaml = read_file(SCHEMA_FILE_PATH)
schema_json = convert_yaml_to_json(schema_yaml)
collection_file_paths = find_collection_files(COLLECTIONS_GLOB_PATTERN)
print(f'Found {len(collection_file_paths)} YAML files to validate.')
total_invalid_files = 0
for collection_file_path in collection_file_paths:
file_name = path.basename(collection_file_path)
print(f'Validating {file_name}...')
collection_yaml = read_file(collection_file_path)
collection_json = convert_yaml_to_json(collection_yaml)
try:
validate(instance=collection_json, schema=schema_json)
print(f'Success: {file_name} is valid.')
except exceptions.ValidationError as err:
print(f'Error: Validation failed for {file_name}.', file=sys.stderr)
print(str(err), file=sys.stderr)
total_invalid_files += 1
if total_invalid_files > 0:
print(f'Validation complete with {total_invalid_files} invalid files.', file=sys.stderr)
sys.exit(1)
else:
print('Validation complete. All files are valid.')
sys.exit(0)
def read_file(file_path: str) -> str:
with open(file_path, 'r', encoding='utf-8') as file:
return file.read()
def find_collection_files(glob_pattern: str) -> List[str]:
files = glob(glob_pattern)
filtered_files = [f for f in files if not path.basename(f).startswith('.')]
return filtered_files
def convert_yaml_to_json(yaml_content: str) -> dict:
return yaml.safe_load(yaml_content)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,6 @@
attrs==23.2.0
jsonschema==4.22.0
jsonschema-specifications==2023.12.1
PyYAML==6.0.1
referencing==0.35.1
rpds-py==0.18.1

View File

@@ -11,10 +11,11 @@ export type CodeRunErrorType =
| 'FileWriteError'
| 'FileReadbackVerificationError'
| 'FilePathGenerationError'
| 'UnsupportedOperatingSystem'
| 'FileExecutionError'
| 'UnsupportedPlatform'
| 'DirectoryCreationError'
| 'UnexpectedError';
| 'FilePermissionChangeError'
| 'FileExecutionError'
| 'ExternalProcessTermination';
interface CodeRunStatus {
readonly success: boolean;

View File

@@ -1,4 +1,4 @@
import { isFunction } from '@/TypeHelpers';
import { isFunction, type ConstructorArguments } from '@/TypeHelpers';
/*
Provides a unified and resilient way to extend errors across platforms.
@@ -12,8 +12,8 @@ import { isFunction } from '@/TypeHelpers';
> 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);
constructor(...args: ConstructorArguments<typeof Error>) {
super(...args);
fixPrototype(this, new.target.prototype);
ensureStackTrace(this);

View File

@@ -5,13 +5,13 @@ export type EnumType = number | string;
export type EnumVariable<T extends EnumType, TEnumValue extends EnumType>
= { [key in T]: TEnumValue };
export interface IEnumParser<TEnum> {
export interface EnumParser<TEnum> {
parseEnum(value: string, propertyName: string): TEnum;
}
export function createEnumParser<T extends EnumType, TEnumValue extends EnumType>(
enumVariable: EnumVariable<T, TEnumValue>,
): IEnumParser<TEnumValue> {
): EnumParser<TEnumValue> {
return {
parseEnum: (value, propertyName) => parseEnumValue(value, propertyName, enumVariable),
};

View File

@@ -1,44 +1,164 @@
/* eslint-disable max-classes-per-file */
import { PlatformTimer } from './PlatformTimer';
import type { Timer, TimeoutType } from './Timer';
export type CallbackType = (..._: readonly unknown[]) => void;
export function throttle(
callback: CallbackType,
waitInMs: number,
timer: Timer = PlatformTimer,
): CallbackType {
const throttler = new Throttler(timer, waitInMs, callback);
return (...args: unknown[]) => throttler.invoke(...args);
export interface ThrottleOptions {
/** Skip the immediate execution of the callback on the first invoke */
readonly excludeLeadingCall: boolean;
readonly timer: Timer;
}
class Throttler {
private queuedExecutionId: TimeoutType | undefined;
const DefaultOptions: ThrottleOptions = {
excludeLeadingCall: false,
timer: PlatformTimer,
};
private previouslyRun: number;
export interface ThrottleFunction {
(
callback: CallbackType,
waitInMs: number,
options?: Partial<ThrottleOptions>,
): CallbackType;
}
export const throttle: ThrottleFunction = (
callback: CallbackType,
waitInMs: number,
options: Partial<ThrottleOptions> = DefaultOptions,
): CallbackType => {
const defaultedOptions: ThrottleOptions = {
...DefaultOptions,
...options,
};
const throttler = new Throttler(waitInMs, callback, defaultedOptions);
return (...args: unknown[]) => throttler.invoke(...args);
};
class Throttler {
private lastExecutionTime: number | null = null;
private executionScheduler: DelayedCallbackScheduler;
constructor(
private readonly timer: Timer,
private readonly waitInMs: number,
private readonly callback: CallbackType,
private readonly options: ThrottleOptions,
) {
if (!waitInMs) { throw new Error('missing delay'); }
if (waitInMs < 0) { throw new Error('negative delay'); }
this.executionScheduler = new DelayedCallbackScheduler(options.timer);
}
public invoke(...args: unknown[]): void {
const now = this.timer.dateNow();
if (this.queuedExecutionId !== undefined) {
this.timer.clearTimeout(this.queuedExecutionId);
this.queuedExecutionId = undefined;
}
if (!this.previouslyRun || (now - this.previouslyRun >= this.waitInMs)) {
this.callback(...args);
this.previouslyRun = now;
} else {
const nextCall = () => this.invoke(...args);
const nextCallDelayInMs = this.waitInMs - (now - this.previouslyRun);
this.queuedExecutionId = this.timer.setTimeout(nextCall, nextCallDelayInMs);
switch (true) {
case this.isLeadingCallWithinThrottlePeriod(): {
if (this.options.excludeLeadingCall) {
this.scheduleNext(args);
return;
}
this.executeNow(args);
return;
}
case this.isAlreadyScheduled(): {
this.updateNextScheduled(args);
return;
}
case !this.isThrottlePeriodPassed(): {
this.scheduleNext(args);
return;
}
default:
throw new Error('Throttle logical error: no conditions for execution or scheduling were met.');
}
}
private isLeadingCallWithinThrottlePeriod(): boolean {
return this.isThrottlePeriodPassed()
&& !this.isAlreadyScheduled();
}
private isThrottlePeriodPassed(): boolean {
if (this.lastExecutionTime === null) {
return true;
}
const timeSinceLastExecution = this.options.timer.dateNow() - this.lastExecutionTime;
const isThrottleTimePassed = timeSinceLastExecution >= this.waitInMs;
return isThrottleTimePassed;
}
private isAlreadyScheduled(): boolean {
return this.executionScheduler.getNext() !== null;
}
private scheduleNext(args: unknown[]): void {
if (this.executionScheduler.getNext()) {
throw new Error('An execution is already scheduled.');
}
this.executionScheduler.resetNext(
() => this.executeNow(args),
this.waitInMs,
);
}
private updateNextScheduled(args: unknown[]): void {
const nextScheduled = this.executionScheduler.getNext();
if (!nextScheduled) {
throw new Error('A non-existent scheduled execution cannot be updated.');
}
const nextDelay = nextScheduled.scheduledTime - this.dateNow();
this.executionScheduler.resetNext(
() => this.executeNow(args),
nextDelay,
);
}
private executeNow(args: unknown[]): void {
this.callback(...args);
this.lastExecutionTime = this.dateNow();
}
private dateNow(): number {
return this.options.timer.dateNow();
}
}
interface ScheduledCallback {
readonly scheduleTimeoutId: TimeoutType;
readonly scheduledTime: number;
}
class DelayedCallbackScheduler {
private scheduledCallback: ScheduledCallback | null = null;
constructor(
private readonly timer: Timer,
) { }
public getNext(): ScheduledCallback | null {
return this.scheduledCallback;
}
public resetNext(
callback: () => void,
delayInMs: number,
) {
this.clear();
this.scheduledCallback = {
scheduledTime: this.timer.dateNow() + delayInMs,
scheduleTimeoutId: this.timer.setTimeout(() => {
this.clear();
callback();
}, delayInMs),
};
}
private clear() {
if (this.scheduledCallback === null) {
return;
}
this.timer.clearTimeout(this.scheduledCallback.scheduleTimeoutId);
this.scheduledCallback = null;
}
}

View File

@@ -1,6 +1,6 @@
import type { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import { EventSource } from '@/infrastructure/Events/EventSource';
import { assertInRange } from '@/application/Common/Enum';
import { CategoryCollectionState } from './State/CategoryCollectionState';

View File

@@ -1,4 +1,4 @@
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { AdaptiveFilterContext } from './Filter/AdaptiveFilterContext';
import { ApplicationCode } from './Code/ApplicationCode';

View File

@@ -1,4 +1,4 @@
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
import type { ICodeChangedEvent } from './ICodeChangedEvent';
@@ -6,13 +6,13 @@ import type { ICodeChangedEvent } from './ICodeChangedEvent';
export class CodeChangedEvent implements ICodeChangedEvent {
public readonly code: string;
public readonly addedScripts: ReadonlyArray<IScript>;
public readonly addedScripts: ReadonlyArray<Script>;
public readonly removedScripts: ReadonlyArray<IScript>;
public readonly removedScripts: ReadonlyArray<Script>;
public readonly changedScripts: ReadonlyArray<IScript>;
public readonly changedScripts: ReadonlyArray<Script>;
private readonly scripts: Map<IScript, ICodePosition>;
private readonly scripts: Map<Script, ICodePosition>;
constructor(
code: string,
@@ -25,7 +25,7 @@ export class CodeChangedEvent implements ICodeChangedEvent {
this.addedScripts = selectIfNotExists(newScripts, oldScripts);
this.removedScripts = selectIfNotExists(oldScripts, newScripts);
this.changedScripts = getChangedScripts(oldScripts, newScripts);
this.scripts = new Map<IScript, ICodePosition>();
this.scripts = new Map<Script, ICodePosition>();
scripts.forEach((position, selection) => {
this.scripts.set(selection.script, position);
});
@@ -35,13 +35,13 @@ export class CodeChangedEvent implements ICodeChangedEvent {
return this.scripts.size === 0;
}
public getScriptPositionInCode(script: IScript): ICodePosition {
return this.getPositionById(script.id);
public getScriptPositionInCode(script: Script): ICodePosition {
return this.getPositionById(script.executableId);
}
private getPositionById(scriptId: string): ICodePosition {
const position = [...this.scripts.entries()]
.filter(([s]) => s.id === scriptId)
.filter(([s]) => s.executableId === scriptId)
.map(([, pos]) => pos)
.at(0);
if (!position) {
@@ -65,7 +65,7 @@ function ensureAllPositionsExist(script: string, positions: ReadonlyArray<ICodeP
function getChangedScripts(
oldScripts: ReadonlyArray<SelectedScript>,
newScripts: ReadonlyArray<SelectedScript>,
): ReadonlyArray<IScript> {
): ReadonlyArray<Script> {
return newScripts
.filter((newScript) => oldScripts.find((oldScript) => oldScript.id === newScript.id
&& oldScript.revert !== newScript.revert))

View File

@@ -1,11 +1,11 @@
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
export interface ICodeChangedEvent {
readonly code: string;
readonly addedScripts: ReadonlyArray<IScript>;
readonly removedScripts: ReadonlyArray<IScript>;
readonly changedScripts: ReadonlyArray<IScript>;
readonly addedScripts: ReadonlyArray<Script>;
readonly removedScripts: ReadonlyArray<Script>;
readonly changedScripts: ReadonlyArray<Script>;
isEmpty(): boolean;
getScriptPositionInCode(script: IScript): ICodePosition;
getScriptPositionInCode(script: Script): ICodePosition;
}

View File

@@ -1,5 +1,5 @@
import { EventSource } from '@/infrastructure/Events/EventSource';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import { FilterChange } from './Event/FilterChange';
import { LinearFilterStrategy } from './Strategy/LinearFilterStrategy';
import type { FilterResult } from './Result/FilterResult';

View File

@@ -1,11 +1,11 @@
import type { IScript } from '@/domain/IScript';
import type { ICategory } from '@/domain/ICategory';
import type { Script } from '@/domain/Executables/Script/Script';
import type { Category } from '@/domain/Executables/Category/Category';
import type { FilterResult } from './FilterResult';
export class AppliedFilterResult implements FilterResult {
constructor(
public readonly scriptMatches: ReadonlyArray<IScript>,
public readonly categoryMatches: ReadonlyArray<ICategory>,
public readonly scriptMatches: ReadonlyArray<Script>,
public readonly categoryMatches: ReadonlyArray<Category>,
public readonly query: string,
) {
if (!query) { throw new Error('Query is empty or undefined'); }

View File

@@ -1,8 +1,9 @@
import type { IScript, ICategory } from '@/domain/ICategory';
import type { Category } from '@/domain/Executables/Category/Category';
import type { Script } from '@/domain/Executables/Script/Script';
export interface FilterResult {
readonly categoryMatches: ReadonlyArray<ICategory>;
readonly scriptMatches: ReadonlyArray<IScript>;
readonly categoryMatches: ReadonlyArray<Category>;
readonly scriptMatches: ReadonlyArray<Script>;
readonly query: string;
hasAnyMatches(): boolean;
}

View File

@@ -1,4 +1,4 @@
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import type { FilterResult } from '../Result/FilterResult';
export interface FilterStrategy {

View File

@@ -1,7 +1,8 @@
import type { ICategory, IScript } from '@/domain/ICategory';
import type { IScriptCode } from '@/domain/IScriptCode';
import type { IDocumentable } from '@/domain/IDocumentable';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { Category } from '@/domain/Executables/Category/Category';
import type { ScriptCode } from '@/domain/Executables/Script/Code/ScriptCode';
import type { Documentable } from '@/domain/Executables/Documentable';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import type { Script } from '@/domain/Executables/Script/Script';
import { AppliedFilterResult } from '../Result/AppliedFilterResult';
import type { FilterStrategy } from './FilterStrategy';
import type { FilterResult } from '../Result/FilterResult';
@@ -24,7 +25,7 @@ export class LinearFilterStrategy implements FilterStrategy {
}
function matchesCategory(
category: ICategory,
category: Category,
filterLowercase: string,
): boolean {
return matchesAny(
@@ -34,7 +35,7 @@ function matchesCategory(
}
function matchesScript(
script: IScript,
script: Script,
filterLowercase: string,
): boolean {
return matchesAny(
@@ -58,7 +59,7 @@ function matchName(
}
function matchCode(
code: IScriptCode,
code: ScriptCode,
filterLowercase: string,
): boolean {
if (code.execute.toLowerCase().includes(filterLowercase)) {
@@ -71,7 +72,7 @@ function matchCode(
}
function matchDocumentation(
documentable: IDocumentable,
documentable: Documentable,
filterLowercase: string,
): boolean {
return documentable.docs.some(

View File

@@ -1,4 +1,4 @@
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
import type { IApplicationCode } from './Code/IApplicationCode';
import type { ReadonlyFilterContext, FilterContext } from './Filter/FilterContext';

View File

@@ -1,9 +1,9 @@
import type { ICategory } from '@/domain/ICategory';
import type { Category } from '@/domain/Executables/Category/Category';
import type { CategorySelectionChangeCommand } from './CategorySelectionChange';
export interface ReadonlyCategorySelection {
areAllScriptsSelected(category: ICategory): boolean;
isAnyScriptSelected(category: ICategory): boolean;
areAllScriptsSelected(category: Category): boolean;
isAnyScriptSelected(category: Category): boolean;
}
export interface CategorySelection extends ReadonlyCategorySelection {

View File

@@ -1,3 +1,5 @@
import type { ExecutableId } from '@/domain/Executables/Identifiable';
type CategorySelectionStatus = {
readonly isSelected: true;
readonly isReverted: boolean;
@@ -6,7 +8,7 @@ type CategorySelectionStatus = {
};
export interface CategorySelectionChange {
readonly categoryId: number;
readonly categoryId: ExecutableId;
readonly newStatus: CategorySelectionStatus;
}

View File

@@ -1,5 +1,5 @@
import type { ICategory } from '@/domain/ICategory';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { Category } from '@/domain/Executables/Category/Category';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import type { CategorySelectionChange, CategorySelectionChangeCommand } from './CategorySelectionChange';
import type { CategorySelection } from './CategorySelection';
import type { ScriptSelection } from '../Script/ScriptSelection';
@@ -13,7 +13,7 @@ export class ScriptToCategorySelectionMapper implements CategorySelection {
}
public areAllScriptsSelected(category: ICategory): boolean {
public areAllScriptsSelected(category: Category): boolean {
const { selectedScripts } = this.scriptSelection;
if (selectedScripts.length === 0) {
return false;
@@ -23,11 +23,11 @@ export class ScriptToCategorySelectionMapper implements CategorySelection {
return false;
}
return scripts.every(
(script) => selectedScripts.some((selected) => selected.id === script.id),
(script) => selectedScripts.some((selected) => selected.id === script.executableId),
);
}
public isAnyScriptSelected(category: ICategory): boolean {
public isAnyScriptSelected(category: Category): boolean {
const { selectedScripts } = this.scriptSelection;
if (selectedScripts.length === 0) {
return false;
@@ -50,7 +50,7 @@ export class ScriptToCategorySelectionMapper implements CategorySelection {
const scripts = category.getAllScriptsRecursively();
const scriptsChangesInCategory = scripts
.map((script): ScriptSelectionChange => ({
scriptId: script.id,
scriptId: script.executableId,
newStatus: {
...change.newStatus,
},

View File

@@ -1,8 +1,8 @@
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import { EventSource } from '@/infrastructure/Events/EventSource';
import type { ReadonlyRepository, Repository } from '@/application/Repository/Repository';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import { batchedDebounce } from '@/application/Common/Timing/BatchedDebounce';
import { UserSelectedScript } from './UserSelectedScript';
import type { ScriptSelection } from './ScriptSelection';
@@ -16,7 +16,7 @@ export type DebounceFunction = typeof batchedDebounce<ScriptSelectionChangeComma
export class DebouncedScriptSelection implements ScriptSelection {
public readonly changed = new EventSource<ReadonlyArray<SelectedScript>>();
private readonly scripts: Repository<string, SelectedScript>;
private readonly scripts: Repository<SelectedScript>;
public readonly processChanges: ScriptSelection['processChanges'];
@@ -25,7 +25,7 @@ export class DebouncedScriptSelection implements ScriptSelection {
selectedScripts: ReadonlyArray<SelectedScript>,
debounce: DebounceFunction = batchedDebounce,
) {
this.scripts = new InMemoryRepository<string, SelectedScript>();
this.scripts = new InMemoryRepository<SelectedScript>();
for (const script of selectedScripts) {
this.scripts.addItem(script);
}
@@ -49,7 +49,7 @@ export class DebouncedScriptSelection implements ScriptSelection {
public selectAll(): void {
const scriptsToSelect = this.collection
.getAllScripts()
.filter((script) => !this.scripts.exists(script.id))
.filter((script) => !this.scripts.exists(script.executableId))
.map((script) => new UserSelectedScript(script, false));
if (scriptsToSelect.length === 0) {
return;
@@ -80,7 +80,7 @@ export class DebouncedScriptSelection implements ScriptSelection {
});
}
public selectOnly(scripts: readonly IScript[]): void {
public selectOnly(scripts: readonly Script[]): void {
assertNonEmptyScriptSelection(scripts);
this.processChanges({
changes: [
@@ -116,9 +116,9 @@ export class DebouncedScriptSelection implements ScriptSelection {
private applyChange(change: ScriptSelectionChange): number {
const script = this.collection.getScript(change.scriptId);
if (change.newStatus.isSelected) {
return this.addOrUpdateScript(script.id, change.newStatus.isReverted);
return this.addOrUpdateScript(script.executableId, change.newStatus.isReverted);
}
return this.removeScript(script.id);
return this.removeScript(script.executableId);
}
private addOrUpdateScript(scriptId: string, revert: boolean): number {
@@ -145,31 +145,31 @@ export class DebouncedScriptSelection implements ScriptSelection {
}
}
function assertNonEmptyScriptSelection(selectedItems: readonly IScript[]) {
function assertNonEmptyScriptSelection(selectedItems: readonly Script[]) {
if (selectedItems.length === 0) {
throw new Error('Provided script array is empty. To deselect all scripts, please use the deselectAll() method instead.');
}
}
function getScriptIdsToBeSelected(
existingItems: ReadonlyRepository<string, SelectedScript>,
desiredScripts: readonly IScript[],
existingItems: ReadonlyRepository<SelectedScript>,
desiredScripts: readonly Script[],
): string[] {
return desiredScripts
.filter((script) => !existingItems.exists(script.id))
.map((script) => script.id);
.filter((script) => !existingItems.exists(script.executableId))
.map((script) => script.executableId);
}
function getScriptIdsToBeDeselected(
existingItems: ReadonlyRepository<string, SelectedScript>,
desiredScripts: readonly IScript[],
existingItems: ReadonlyRepository<SelectedScript>,
desiredScripts: readonly Script[],
): string[] {
return existingItems
.getItems()
.filter((existing) => !desiredScripts.some((script) => existing.id === script.id))
.filter((existing) => !desiredScripts.some((script) => existing.id === script.executableId))
.map((script) => script.id);
}
function equals(a: SelectedScript, b: SelectedScript): boolean {
return a.script.equals(b.script.id) && a.revert === b.revert;
return a.script.executableId === b.script.executableId && a.revert === b.revert;
}

View File

@@ -1,5 +1,5 @@
import type { IEventSource } from '@/infrastructure/Events/IEventSource';
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { SelectedScript } from './SelectedScript';
import type { ScriptSelectionChangeCommand } from './ScriptSelectionChange';
@@ -10,7 +10,7 @@ export interface ReadonlyScriptSelection {
}
export interface ScriptSelection extends ReadonlyScriptSelection {
selectOnly(scripts: readonly IScript[]): void;
selectOnly(scripts: readonly Script[]): void;
selectAll(): void;
deselectAll(): void;
processChanges(action: ScriptSelectionChangeCommand): void;

View File

@@ -1,9 +1,7 @@
import type { IEntity } from '@/infrastructure/Entity/IEntity';
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { RepositoryEntity } from '@/application/Repository/RepositoryEntity';
type ScriptId = IScript['id'];
export interface SelectedScript extends IEntity<ScriptId> {
readonly script: IScript;
export interface SelectedScript extends RepositoryEntity {
readonly script: Script;
readonly revert: boolean;
}

View File

@@ -1,17 +1,16 @@
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
import type { IScript } from '@/domain/IScript';
import type { SelectedScript } from './SelectedScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { RepositoryEntity } from '@/application/Repository/RepositoryEntity';
type SelectedScriptId = SelectedScript['id'];
export class UserSelectedScript implements RepositoryEntity {
public readonly id: string;
export class UserSelectedScript extends BaseEntity<SelectedScriptId> {
constructor(
public readonly script: IScript,
public readonly script: Script,
public readonly revert: boolean,
) {
super(script.id);
this.id = script.executableId;
if (revert && !script.canRevert()) {
throw new Error(`The script with ID '${script.id}' is not reversible and cannot be reverted.`);
throw new Error(`The script with ID '${script.executableId}' is not reversible and cannot be reverted.`);
}
}
}

View File

@@ -1,4 +1,4 @@
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import { ScriptToCategorySelectionMapper } from './Category/ScriptToCategorySelectionMapper';
import { DebouncedScriptSelection } from './Script/DebouncedScriptSelection';
import type { CategorySelection } from './Category/CategorySelection';

View File

@@ -1,40 +1,48 @@
import type { CollectionData } from '@/application/collections/';
import type { IApplication } from '@/domain/IApplication';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import WindowsData from '@/application/collections/windows.yaml';
import MacOsData from '@/application/collections/macos.yaml';
import LinuxData from '@/application/collections/linux.yaml';
import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser';
import { parseProjectDetails, type ProjectDetailsParser } from '@/application/Parser/ProjectDetailsParser';
import { Application } from '@/domain/Application';
import type { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
import { parseCategoryCollection } from './CategoryCollectionParser';
import { parseCategoryCollection, type CategoryCollectionParser } from './CategoryCollectionParser';
import { createTypeValidator, type TypeValidator } from './Common/TypeValidator';
export function parseApplication(
categoryParser = parseCategoryCollection,
projectDetailsParser = parseProjectDetails,
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
collectionsData = PreParsedCollections,
collectionsData: readonly CollectionData[] = PreParsedCollections,
utilities: ApplicationParserUtilities = DefaultUtilities,
): IApplication {
validateCollectionsData(collectionsData);
const projectDetails = projectDetailsParser(metadata);
validateCollectionsData(collectionsData, utilities.validator);
const projectDetails = utilities.parseProjectDetails();
const collections = collectionsData.map(
(collection) => categoryParser(collection, projectDetails),
(collection) => utilities.parseCategoryCollection(collection, projectDetails),
);
const app = new Application(projectDetails, collections);
return app;
}
export type CategoryCollectionParserType
= (file: CollectionData, projectDetails: ProjectDetails) => ICategoryCollection;
const PreParsedCollections: readonly CollectionData [] = [
const PreParsedCollections: readonly CollectionData[] = [
WindowsData, MacOsData, LinuxData,
];
function validateCollectionsData(collections: readonly CollectionData[]) {
if (!collections.length) {
throw new Error('missing collections');
}
function validateCollectionsData(
collections: readonly CollectionData[],
validator: TypeValidator,
) {
validator.assertNonEmptyCollection({
value: collections,
valueName: 'collections',
});
}
interface ApplicationParserUtilities {
readonly parseCategoryCollection: CategoryCollectionParser;
readonly validator: TypeValidator;
readonly parseProjectDetails: ProjectDetailsParser;
}
const DefaultUtilities: ApplicationParserUtilities = {
parseCategoryCollection,
parseProjectDetails,
validator: createTypeValidator(),
};

View File

@@ -1,34 +1,75 @@
import type { CollectionData } from '@/application/collections/';
import { OperatingSystem } from '@/domain/OperatingSystem';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollection } from '@/domain/CategoryCollection';
import type { ICategoryCollection } from '@/domain/Collection/ICategoryCollection';
import { CategoryCollection } from '@/domain/Collection/CategoryCollection';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { createEnumParser } from '../Common/Enum';
import { parseCategory } from './CategoryParser';
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser';
import { createEnumParser, type EnumParser } from '../Common/Enum';
import { parseCategory, type CategoryParser } from './Executable/CategoryParser';
import { parseScriptingDefinition, type ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser';
import { createTypeValidator, type TypeValidator } from './Common/TypeValidator';
import { createCollectionUtilities, type CategoryCollectionSpecificUtilitiesFactory } from './Executable/CategoryCollectionSpecificUtilities';
export function parseCategoryCollection(
content: CollectionData,
projectDetails: ProjectDetails,
osParser = createEnumParser(OperatingSystem),
): ICategoryCollection {
validate(content);
const scripting = new ScriptingDefinitionParser()
.parse(content.scripting, projectDetails);
const context = new CategoryCollectionParseContext(content.functions, scripting);
const categories = content.actions.map((action) => parseCategory(action, context));
const os = osParser.parseEnum(content.os, 'os');
const collection = new CategoryCollection(
os,
categories,
scripting,
export const parseCategoryCollection: CategoryCollectionParser = (
content,
projectDetails,
utilities: CategoryCollectionParserUtilities = DefaultUtilities,
) => {
validateCollection(content, utilities.validator);
const scripting = utilities.parseScriptingDefinition(content.scripting, projectDetails);
const collectionUtilities = utilities.createUtilities(content.functions, scripting);
const categories = content.actions.map(
(action) => utilities.parseCategory(action, collectionUtilities),
);
const os = utilities.osParser.parseEnum(content.os, 'os');
const collection = utilities.createCategoryCollection({
os, actions: categories, scripting,
});
return collection;
};
export type CategoryCollectionFactory = (
...parameters: ConstructorParameters<typeof CategoryCollection>
) => ICategoryCollection;
export interface CategoryCollectionParser {
(
content: CollectionData,
projectDetails: ProjectDetails,
utilities?: CategoryCollectionParserUtilities,
): ICategoryCollection;
}
function validate(content: CollectionData): void {
if (!content.actions.length) {
throw new Error('content does not define any action');
}
function validateCollection(
content: CollectionData,
validator: TypeValidator,
): void {
validator.assertObject({
value: content,
valueName: 'collection',
allowedProperties: [
'os', 'scripting', 'actions', 'functions',
],
});
validator.assertNonEmptyCollection({
value: content.actions,
valueName: '"actions" in collection',
});
}
interface CategoryCollectionParserUtilities {
readonly osParser: EnumParser<OperatingSystem>;
readonly validator: TypeValidator;
readonly parseScriptingDefinition: ScriptingDefinitionParser;
readonly createUtilities: CategoryCollectionSpecificUtilitiesFactory;
readonly parseCategory: CategoryParser;
readonly createCategoryCollection: CategoryCollectionFactory;
}
const DefaultUtilities: CategoryCollectionParserUtilities = {
osParser: createEnumParser(OperatingSystem),
validator: createTypeValidator(),
parseScriptingDefinition,
createUtilities: createCollectionUtilities,
parseCategory,
createCategoryCollection: (...args) => new CategoryCollection(...args),
};

View File

@@ -1,133 +0,0 @@
import type {
CategoryData, ScriptData, CategoryOrScriptData,
} from '@/application/collections/';
import { Script } from '@/domain/Script';
import { Category } from '@/domain/Category';
import { NodeValidator } from '@/application/Parser/NodeValidation/NodeValidator';
import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
import { parseDocs } from './DocumentationParser';
import { parseScript } from './Script/ScriptParser';
import type { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext';
let categoryIdCounter = 0;
export function parseCategory(
category: CategoryData,
context: ICategoryCollectionParseContext,
factory: CategoryFactoryType = CategoryFactory,
): Category {
return parseCategoryRecursively({
categoryData: category,
context,
factory,
});
}
interface ICategoryParseContext {
readonly categoryData: CategoryData,
readonly context: ICategoryCollectionParseContext,
readonly factory: CategoryFactoryType,
readonly parentCategory?: CategoryData,
}
function parseCategoryRecursively(context: ICategoryParseContext): Category | never {
ensureValidCategory(context.categoryData, context.parentCategory);
const children: ICategoryChildren = {
subCategories: new Array<Category>(),
subScripts: new Array<Script>(),
};
for (const data of context.categoryData.children) {
parseNode({
nodeData: data,
children,
parent: context.categoryData,
factory: context.factory,
context: context.context,
});
}
try {
return context.factory(
/* id: */ categoryIdCounter++,
/* name: */ context.categoryData.category,
/* docs: */ parseDocs(context.categoryData),
/* categories: */ children.subCategories,
/* scripts: */ children.subScripts,
);
} catch (err) {
return new NodeValidator({
type: NodeType.Category,
selfNode: context.categoryData,
parentNode: context.parentCategory,
}).throw(err.message);
}
}
function ensureValidCategory(category: CategoryData, parentCategory?: CategoryData) {
new NodeValidator({
type: NodeType.Category,
selfNode: category,
parentNode: parentCategory,
})
.assertDefined(category)
.assertValidName(category.category)
.assert(
() => category.children.length > 0,
`"${category.category}" has no children.`,
);
}
interface ICategoryChildren {
subCategories: Category[];
subScripts: Script[];
}
interface INodeParseContext {
readonly nodeData: CategoryOrScriptData;
readonly children: ICategoryChildren;
readonly parent: CategoryData;
readonly factory: CategoryFactoryType;
readonly context: ICategoryCollectionParseContext;
}
function parseNode(context: INodeParseContext) {
const validator = new NodeValidator({ selfNode: context.nodeData, parentNode: context.parent });
validator.assertDefined(context.nodeData);
if (isCategory(context.nodeData)) {
const subCategory = parseCategoryRecursively({
categoryData: context.nodeData,
context: context.context,
factory: context.factory,
parentCategory: context.parent,
});
context.children.subCategories.push(subCategory);
} else if (isScript(context.nodeData)) {
const script = parseScript(context.nodeData, context.context);
context.children.subScripts.push(script);
} else {
validator.throw('Node is neither a category or a script.');
}
}
function isScript(data: CategoryOrScriptData): data is ScriptData {
return hasCode(data) || hasCall(data);
}
function isCategory(data: CategoryOrScriptData): data is CategoryData {
return hasProperty(data, 'category');
}
function hasCode(data: unknown): boolean {
return hasProperty(data, 'code');
}
function hasCall(data: unknown) {
return hasProperty(data, 'call');
}
function hasProperty(object: unknown, propertyName: string) {
return Object.prototype.hasOwnProperty.call(object, propertyName);
}
export type CategoryFactoryType = (
...parameters: ConstructorParameters<typeof Category>) => Category;
const CategoryFactory: CategoryFactoryType = (...parameters) => new Category(...parameters);

View File

@@ -0,0 +1,42 @@
import { CustomError } from '@/application/Common/CustomError';
export interface ErrorWithContextWrapper {
(
error: Error,
additionalContext: string,
): Error;
}
export const wrapErrorWithAdditionalContext: ErrorWithContextWrapper = (
error: Error,
additionalContext: string,
) => {
return (error instanceof ContextualError ? error : new ContextualError(error))
.withAdditionalContext(additionalContext);
};
/* AggregateError is similar but isn't well-serialized or displayed by browsers */
class ContextualError extends CustomError {
private readonly additionalContext = new Array<string>();
constructor(
public readonly innerError: Error,
) {
super();
}
public withAdditionalContext(additionalContext: string): this {
this.additionalContext.push(additionalContext);
return this;
}
public get message(): string { // toString() is not used when Chromium logs it on console
return [
'\n',
this.innerError.message,
'\n',
'Additional context:',
...this.additionalContext.map((context, index) => `${index + 1}: ${context}`),
].join('\n');
}
}

View File

@@ -0,0 +1,131 @@
import type { PropertyKeys } from '@/TypeHelpers';
import {
isNullOrUndefined, isArray, isPlainObject, isString,
} from '@/TypeHelpers';
export interface TypeValidator {
assertObject<T>(assertion: ObjectAssertion<T>): void;
assertNonEmptyCollection(assertion: NonEmptyCollectionAssertion): void;
assertNonEmptyString(assertion: NonEmptyStringAssertion): void;
}
export interface NonEmptyCollectionAssertion {
readonly value: unknown;
readonly valueName: string;
}
export interface RegexValidationRule {
readonly expectedMatch: RegExp;
readonly errorMessage: string;
}
export interface NonEmptyStringAssertion {
readonly value: unknown;
readonly valueName: string;
readonly rule?: RegexValidationRule;
}
export interface ObjectAssertion<T> {
readonly value: T | unknown;
readonly valueName: string;
readonly allowedProperties?: readonly PropertyKeys<T>[];
}
export function createTypeValidator(): TypeValidator {
return {
assertObject: (assertion) => {
assertDefined(assertion.value, assertion.valueName);
assertPlainObject(assertion.value, assertion.valueName);
assertNoEmptyProperties(assertion.value, assertion.valueName);
if (assertion.allowedProperties !== undefined) {
const allowedProperties = assertion.allowedProperties.map((p) => p as string);
assertAllowedProperties(assertion.value, assertion.valueName, allowedProperties);
}
},
assertNonEmptyCollection: (assertion) => {
assertDefined(assertion.value, assertion.valueName);
assertArray(assertion.value, assertion.valueName);
assertNonEmpty(assertion.value, assertion.valueName);
},
assertNonEmptyString: (assertion) => {
assertDefined(assertion.value, assertion.valueName);
assertString(assertion.value, assertion.valueName);
if (assertion.value.length === 0) {
throw new Error(`'${assertion.valueName}' is missing.`);
}
if (assertion.rule) {
if (!assertion.value.match(assertion.rule.expectedMatch)) {
throw new Error(assertion.rule.errorMessage);
}
}
},
};
}
function assertDefined<T>(
value: T,
valueName: string,
): asserts value is NonNullable<T> {
if (isNullOrUndefined(value)) {
throw new Error(`'${valueName}' is missing.`);
}
}
function assertPlainObject(
value: unknown,
valueName: string,
): asserts value is object {
if (!isPlainObject(value)) {
throw new Error(`'${valueName}' is not an object.`);
}
}
function assertNoEmptyProperties(
value: object,
valueName: string,
): void {
if (Object.keys(value).length === 0) {
throw new Error(`'${valueName}' is an empty object without properties.`);
}
}
function assertAllowedProperties(
value: object,
valueName: string,
allowedProperties: readonly string[],
): void {
const properties = Object.keys(value).map((p) => p as string);
const disallowedProperties = properties.filter(
(prop) => !allowedProperties.map((p) => p as string).includes(prop),
);
if (disallowedProperties.length > 0) {
throw new Error(`'${valueName}' has disallowed properties: ${disallowedProperties.join(', ')}.`);
}
}
function assertArray(
value: unknown,
valueName: string,
): asserts value is Array<unknown> {
if (!isArray(value)) {
throw new Error(`'${valueName}' should be of type 'array', but is of type '${typeof value}'.`);
}
}
function assertString(
value: unknown,
valueName: string,
): asserts value is string {
if (!isString(value)) {
throw new Error(`'${valueName}' should be of type 'string', but is of type '${typeof value}'.`);
}
}
function assertNonEmpty(
value: Array<unknown>,
valueName: string,
): void {
if (value.length === 0) {
throw new Error(`'${valueName}' cannot be an empty array.`);
}
}

View File

@@ -0,0 +1,35 @@
import type { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import type { FunctionData } from '@/application/collections/';
import { ScriptCompiler } from './Script/Compiler/ScriptCompiler';
import { SyntaxFactory } from './Script/Validation/Syntax/SyntaxFactory';
import type { IScriptCompiler } from './Script/Compiler/IScriptCompiler';
import type { ILanguageSyntax } from './Script/Validation/Syntax/ILanguageSyntax';
import type { ISyntaxFactory } from './Script/Validation/Syntax/ISyntaxFactory';
export interface CategoryCollectionSpecificUtilities {
readonly compiler: IScriptCompiler;
readonly syntax: ILanguageSyntax;
}
export const createCollectionUtilities: CategoryCollectionSpecificUtilitiesFactory = (
functionsData: ReadonlyArray<FunctionData> | undefined,
scripting: IScriptingDefinition,
syntaxFactory: ISyntaxFactory = new SyntaxFactory(),
) => {
const syntax = syntaxFactory.create(scripting.language);
return {
compiler: new ScriptCompiler({
functions: functionsData ?? [],
syntax,
}),
syntax,
};
};
export interface CategoryCollectionSpecificUtilitiesFactory {
(
functionsData: ReadonlyArray<FunctionData> | undefined,
scripting: IScriptingDefinition,
syntaxFactory?: ISyntaxFactory,
): CategoryCollectionSpecificUtilities;
}

View File

@@ -0,0 +1,181 @@
import type {
CategoryData, ScriptData, ExecutableData,
} from '@/application/collections/';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
import type { Category } from '@/domain/Executables/Category/Category';
import type { Script } from '@/domain/Executables/Script/Script';
import { createCategory, type CategoryFactory } from '@/domain/Executables/Category/CategoryFactory';
import { parseDocs, type DocsParser } from './DocumentationParser';
import { parseScript, type ScriptParser } from './Script/ScriptParser';
import { createExecutableDataValidator, type ExecutableValidator, type ExecutableValidatorFactory } from './Validation/ExecutableValidator';
import { ExecutableType } from './Validation/ExecutableType';
import type { CategoryCollectionSpecificUtilities } from './CategoryCollectionSpecificUtilities';
export const parseCategory: CategoryParser = (
category: CategoryData,
collectionUtilities: CategoryCollectionSpecificUtilities,
categoryUtilities: CategoryParserUtilities = DefaultCategoryParserUtilities,
) => {
return parseCategoryRecursively({
categoryData: category,
collectionUtilities,
categoryUtilities,
});
};
export interface CategoryParser {
(
category: CategoryData,
collectionUtilities: CategoryCollectionSpecificUtilities,
categoryUtilities?: CategoryParserUtilities,
): Category;
}
interface CategoryParseContext {
readonly categoryData: CategoryData;
readonly collectionUtilities: CategoryCollectionSpecificUtilities;
readonly parentCategory?: CategoryData;
readonly categoryUtilities: CategoryParserUtilities;
}
function parseCategoryRecursively(
context: CategoryParseContext,
): Category | never {
const validator = ensureValidCategory(context);
const children: CategoryChildren = {
subcategories: new Array<Category>(),
subscripts: new Array<Script>(),
};
for (const data of context.categoryData.children) {
parseUnknownExecutable({
data,
children,
parent: context.categoryData,
categoryUtilities: context.categoryUtilities,
collectionUtilities: context.collectionUtilities,
});
}
try {
return context.categoryUtilities.createCategory({
executableId: context.categoryData.category, // arbitrary ID
name: context.categoryData.category,
docs: context.categoryUtilities.parseDocs(context.categoryData),
subcategories: children.subcategories,
scripts: children.subscripts,
});
} catch (error) {
throw context.categoryUtilities.wrapError(
error,
validator.createContextualErrorMessage('Failed to parse category.'),
);
}
}
function ensureValidCategory(
context: CategoryParseContext,
): ExecutableValidator {
const category = context.categoryData;
const validator: ExecutableValidator = context.categoryUtilities.createValidator({
type: ExecutableType.Category,
self: context.categoryData,
parentCategory: context.parentCategory,
});
validator.assertType((v) => v.assertObject({
value: category,
valueName: category.category ?? 'category',
allowedProperties: [
'docs', 'children', 'category',
],
}));
validator.assertValidName(category.category);
validator.assertType((v) => v.assertNonEmptyCollection({
value: category.children,
valueName: category.category,
}));
return validator;
}
interface CategoryChildren {
readonly subcategories: Category[];
readonly subscripts: Script[];
}
interface ExecutableParseContext {
readonly data: ExecutableData;
readonly children: CategoryChildren;
readonly parent: CategoryData;
readonly collectionUtilities: CategoryCollectionSpecificUtilities;
readonly categoryUtilities: CategoryParserUtilities;
}
function parseUnknownExecutable(context: ExecutableParseContext) {
const validator: ExecutableValidator = context.categoryUtilities.createValidator({
self: context.data,
parentCategory: context.parent,
});
validator.assertType((v) => v.assertObject({
value: context.data,
valueName: 'Executable',
}));
validator.assert(
() => isCategory(context.data) || isScript(context.data),
'Executable is neither a category or a script.',
);
if (isCategory(context.data)) {
const subCategory = parseCategoryRecursively({
categoryData: context.data,
collectionUtilities: context.collectionUtilities,
parentCategory: context.parent,
categoryUtilities: context.categoryUtilities,
});
context.children.subcategories.push(subCategory);
} else { // A script
const script = context.categoryUtilities.parseScript(context.data, context.collectionUtilities);
context.children.subscripts.push(script);
}
}
function isScript(data: ExecutableData): data is ScriptData {
return hasCode(data) || hasCall(data);
}
function isCategory(data: ExecutableData): data is CategoryData {
return hasProperty(data, 'category');
}
function hasCode(data: unknown): boolean {
return hasProperty(data, 'code');
}
function hasCall(data: unknown) {
return hasProperty(data, 'call');
}
function hasProperty(
object: unknown,
propertyName: string,
): object is NonNullable<object> {
if (typeof object !== 'object') {
return false;
}
if (object === null) { // `typeof object` is `null`
return false;
}
return Object.prototype.hasOwnProperty.call(object, propertyName);
}
interface CategoryParserUtilities {
readonly createCategory: CategoryFactory;
readonly wrapError: ErrorWithContextWrapper;
readonly createValidator: ExecutableValidatorFactory;
readonly parseScript: ScriptParser;
readonly parseDocs: DocsParser;
}
const DefaultCategoryParserUtilities: CategoryParserUtilities = {
createCategory,
wrapError: wrapErrorWithAdditionalContext,
createValidator: createExecutableDataValidator,
parseScript,
parseDocs,
};

View File

@@ -1,7 +1,7 @@
import type { DocumentableData, DocumentationData } from '@/application/collections/';
import { isString, isArray } from '@/TypeHelpers';
export function parseDocs(documentable: DocumentableData): readonly string[] {
export const parseDocs: DocsParser = (documentable) => {
const { docs } = documentable;
if (!docs) {
return [];
@@ -9,6 +9,12 @@ export function parseDocs(documentable: DocumentableData): readonly string[] {
let result = new DocumentationContainer();
result = addDocs(docs, result);
return result.getAll();
};
export interface DocsParser {
(
documentable: DocumentableData,
): readonly string[];
}
function addDocs(
@@ -44,5 +50,5 @@ class DocumentationContainer {
}
function throwInvalidType(): never {
throw new Error('docs field (documentation) must be an array of strings');
throw new Error('docs field (documentation) must be a single string or an array of strings.');
}

View File

@@ -1,4 +1,4 @@
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionCallArgumentCollection } from '../../Function/Call/Argument/FunctionCallArgumentCollection';
import { ExpressionEvaluationContext, type IExpressionEvaluationContext } from './ExpressionEvaluationContext';
import { ExpressionPosition } from './ExpressionPosition';
@@ -7,15 +7,18 @@ import type { IReadOnlyFunctionParameterCollection } from '../../Function/Parame
import type { IExpression } from './IExpression';
export type ExpressionEvaluator = (context: IExpressionEvaluationContext) => string;
export class Expression implements IExpression {
public readonly parameters: IReadOnlyFunctionParameterCollection;
constructor(
public readonly position: ExpressionPosition,
public readonly evaluator: ExpressionEvaluator,
parameters?: IReadOnlyFunctionParameterCollection,
) {
this.parameters = parameters ?? new FunctionParameterCollection();
public readonly position: ExpressionPosition;
public readonly evaluator: ExpressionEvaluator;
constructor(parameters: ExpressionInitParameters) {
this.parameters = parameters.parameters ?? new FunctionParameterCollection();
this.evaluator = parameters.evaluator;
this.position = parameters.position;
}
public evaluate(context: IExpressionEvaluationContext): string {
@@ -26,6 +29,12 @@ export class Expression implements IExpression {
}
}
export interface ExpressionInitParameters {
readonly position: ExpressionPosition,
readonly evaluator: ExpressionEvaluator,
readonly parameters?: IReadOnlyFunctionParameterCollection,
}
function validateThatAllRequiredParametersAreSatisfied(
parameters: IReadOnlyFunctionParameterCollection,
args: IReadOnlyFunctionCallArgumentCollection,

View File

@@ -1,8 +1,13 @@
import { ExpressionPosition } from './ExpressionPosition';
export function createPositionFromRegexFullMatch(
match: RegExpMatchArray,
): ExpressionPosition {
export interface ExpressionPositionFactory {
(
match: RegExpMatchArray,
): ExpressionPosition
}
export const createPositionFromRegexFullMatch
: ExpressionPositionFactory = (match) => {
const startPos = match.index;
if (startPos === undefined) {
throw new Error(`Regex match did not yield any results: ${JSON.stringify(match)}`);
@@ -13,4 +18,4 @@ export function createPositionFromRegexFullMatch(
}
const endPos = startPos + fullMatch.length;
return new ExpressionPosition(startPos, endPos);
}
};

View File

@@ -1,4 +1,4 @@
import { type IExpressionEvaluationContext, ExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
import { type IExpressionEvaluationContext, ExpressionEvaluationContext } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
import type { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
import type { IExpressionsCompiler } from './IExpressionsCompiler';

View File

@@ -3,10 +3,10 @@ import { WithParser } from '../SyntaxParsers/WithParser';
import type { IExpression } from '../Expression/IExpression';
import type { IExpressionParser } from './IExpressionParser';
const Parsers = [
const Parsers: readonly IExpressionParser[] = [
new ParameterSubstitutionParser(),
new WithParser(),
];
] as const;
export class CompositeExpressionParser implements IExpressionParser {
public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {

View File

@@ -0,0 +1,127 @@
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
import { Expression, type ExpressionEvaluator } from '../../Expression/Expression';
import { createPositionFromRegexFullMatch, type ExpressionPositionFactory } from '../../Expression/ExpressionPositionFactory';
import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from '../../../Function/Parameter/FunctionParameterCollectionFactory';
import type { IExpressionParser } from '../IExpressionParser';
import type { IExpression } from '../../Expression/IExpression';
import type { FunctionParameter } from '../../../Function/Parameter/FunctionParameter';
import type { IFunctionParameterCollection, IReadOnlyFunctionParameterCollection } from '../../../Function/Parameter/IFunctionParameterCollection';
export interface RegexParserUtilities {
readonly wrapError: ErrorWithContextWrapper;
readonly createPosition: ExpressionPositionFactory;
readonly createExpression: ExpressionFactory;
readonly createParameterCollection: FunctionParameterCollectionFactory;
}
export abstract class RegexParser implements IExpressionParser {
protected abstract readonly regex: RegExp;
public constructor(
private readonly utilities: RegexParserUtilities = DefaultRegexParserUtilities,
) {
}
public findExpressions(code: string): IExpression[] {
return Array.from(this.findRegexExpressions(code));
}
protected abstract buildExpression(match: RegExpMatchArray): PrimitiveExpression;
private* findRegexExpressions(code: string): Iterable<IExpression> {
if (!code) {
throw new Error(
this.buildErrorMessageWithContext({ errorMessage: 'missing code', code: 'EMPTY' }),
);
}
const createErrorContext = (message: string): ErrorContext => ({ code, errorMessage: message });
const matches = this.doOrRethrow(
() => code.matchAll(this.regex),
createErrorContext('Failed to match regex.'),
);
for (const match of matches) {
const primitiveExpression = this.doOrRethrow(
() => this.buildExpression(match),
createErrorContext('Failed to build expression.'),
);
const position = this.doOrRethrow(
() => this.utilities.createPosition(match),
createErrorContext('Failed to create position.'),
);
const parameters = this.doOrRethrow(
() => createParameters(
primitiveExpression,
this.utilities.createParameterCollection(),
),
createErrorContext('Failed to create parameters.'),
);
const expression = this.doOrRethrow(
() => this.utilities.createExpression({
position,
evaluator: primitiveExpression.evaluator,
parameters,
}),
createErrorContext('Failed to create expression.'),
);
yield expression;
}
}
private doOrRethrow<T>(
action: () => T,
context: ErrorContext,
): T {
try {
return action();
} catch (error) {
throw this.utilities.wrapError(
error,
this.buildErrorMessageWithContext(context),
);
}
}
private buildErrorMessageWithContext(context: ErrorContext): string {
return [
context.errorMessage,
`Class name: ${this.constructor.name}`,
`Regex pattern used: ${this.regex}`,
`Code: ${context.code}`,
].join('\n');
}
}
interface ErrorContext {
readonly errorMessage: string,
readonly code: string,
}
function createParameters(
expression: PrimitiveExpression,
parameterCollection: IFunctionParameterCollection,
): IReadOnlyFunctionParameterCollection {
return (expression.parameters || [])
.reduce((parameters, parameter) => {
parameters.addParameter(parameter);
return parameters;
}, parameterCollection);
}
export interface PrimitiveExpression {
readonly evaluator: ExpressionEvaluator;
readonly parameters?: readonly FunctionParameter[];
}
export interface ExpressionFactory {
(
...args: ConstructorParameters<typeof Expression>
): IExpression;
}
const DefaultRegexParserUtilities: RegexParserUtilities = {
wrapError: wrapErrorWithAdditionalContext,
createPosition: createPositionFromRegexFullMatch,
createExpression: (...args) => new Expression(...args),
createParameterCollection: createFunctionParameterCollection,
};

View File

@@ -1,5 +1,4 @@
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
import { RegexParser, type IPrimitiveExpression } from '../Parser/Regex/RegexParser';
import { RegexParser, type PrimitiveExpression } from '../Parser/Regex/RegexParser';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
export class ParameterSubstitutionParser extends RegexParser {
@@ -12,11 +11,14 @@ export class ParameterSubstitutionParser extends RegexParser {
.expectExpressionEnd()
.buildRegExp();
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
protected buildExpression(match: RegExpMatchArray): PrimitiveExpression {
const parameterName = match[1];
const pipeline = match[2];
return {
parameters: [new FunctionParameter(parameterName, false)],
parameters: [{
name: parameterName,
isOptional: false,
}],
evaluator: (context) => {
const { argumentValue } = context.args.getArgument(parameterName);
if (!pipeline) {

View File

@@ -1,7 +1,6 @@
// eslint-disable-next-line max-classes-per-file
import type { 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 type { IExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/IExpressionParser';
import { FunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { ExpressionPosition } from '../Expression/ExpressionPosition';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
import { createPositionFromRegexFullMatch } from '../Expression/ExpressionPositionFactory';
@@ -84,7 +83,10 @@ class WithStatementBuilder {
public buildExpression(endExpressionPosition: ExpressionPosition, input: string): IExpression {
const parameters = new FunctionParameterCollection();
parameters.addParameter(new FunctionParameter(this.parameterName, true));
parameters.addParameter({
name: this.parameterName,
isOptional: true,
});
const position = new ExpressionPosition(
this.startExpressionPosition.start,
endExpressionPosition.end,

View File

@@ -0,0 +1,41 @@
import { createTypeValidator, type TypeValidator } from '@/application/Parser/Common/TypeValidator';
import { validateParameterName, type ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
export interface FunctionCallArgument {
readonly parameterName: string;
readonly argumentValue: string;
}
export interface FunctionCallArgumentFactory {
(
parameterName: string,
argumentValue: string,
utilities?: FunctionCallArgumentFactoryUtilities,
): FunctionCallArgument;
}
export const createFunctionCallArgument: FunctionCallArgumentFactory = (
parameterName: string,
argumentValue: string,
utilities: FunctionCallArgumentFactoryUtilities = DefaultUtilities,
): FunctionCallArgument => {
utilities.validateParameterName(parameterName);
utilities.typeValidator.assertNonEmptyString({
value: argumentValue,
valueName: `Missing argument value for the parameter "${parameterName}".`,
});
return {
parameterName,
argumentValue,
};
};
interface FunctionCallArgumentFactoryUtilities {
readonly typeValidator: TypeValidator;
readonly validateParameterName: ParameterNameValidator;
}
const DefaultUtilities: FunctionCallArgumentFactoryUtilities = {
typeValidator: createTypeValidator(),
validateParameterName,
};

View File

@@ -1,10 +1,10 @@
import type { IFunctionCallArgument } from './IFunctionCallArgument';
import type { FunctionCallArgument } from './FunctionCallArgument';
import type { IFunctionCallArgumentCollection } from './IFunctionCallArgumentCollection';
export class FunctionCallArgumentCollection implements IFunctionCallArgumentCollection {
private readonly arguments = new Map<string, IFunctionCallArgument>();
private readonly arguments = new Map<string, FunctionCallArgument>();
public addArgument(argument: IFunctionCallArgument): void {
public addArgument(argument: FunctionCallArgument): void {
if (this.hasArgument(argument.parameterName)) {
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
}
@@ -22,7 +22,7 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl
return this.arguments.has(parameterName);
}
public getArgument(parameterName: string): IFunctionCallArgument {
public getArgument(parameterName: string): FunctionCallArgument {
if (!parameterName) {
throw new Error('missing parameter name');
}

View File

@@ -1,11 +1,11 @@
import type { IFunctionCallArgument } from './IFunctionCallArgument';
import type { FunctionCallArgument } from './FunctionCallArgument';
export interface IReadOnlyFunctionCallArgumentCollection {
getArgument(parameterName: string): IFunctionCallArgument;
getArgument(parameterName: string): FunctionCallArgument;
getAllParameterNames(): string[];
hasArgument(parameterName: string): boolean;
}
export interface IFunctionCallArgumentCollection extends IReadOnlyFunctionCallArgumentCollection {
addArgument(argument: IFunctionCallArgument): void;
addArgument(argument: FunctionCallArgument): void;
}

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