Compare commits

...

38 Commits

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

Other changes:

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

Windows:

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

Linux:

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

Both Linux and Windows:

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

Changes in detail:

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

Other changes:

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

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

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

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

Other changes:

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

Script changes:

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

Other changes:

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

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

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

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

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

Other improvements:

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

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

Changes:

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

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

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

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

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

Directory removal improvements include:

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

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

Key findings:

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

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

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

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

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

Other changes include:

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

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

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

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

This commit removes following third-party production dependencies:

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

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

Changes:

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

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

Changes include:

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

On Windows:

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

On Linux:

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

Changes include:

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

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

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

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

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

Changes include:

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

- Enable artifact output validation for Dockerfile.
- Correct the path references in Dockerfile for the distribution
  directory.
- Add Dockerfile specific indentation rules to `.editorconfig`.
- Use `npm run install-deps` for dependency installation, enhancing
  build reliability.
- Add automation script `verify-web-server-status.js` to verify running
  web server on given URL.
- Introduce automated build verification for Dockerfile:
  - On macOS, install Docker with colima as the container runtime
    because default agents do not include Docker and Docker runtime is
    not installed due to licensing issues (see actions/runner-images#17).
  - On Windows, there's no Linux container support (actions/runner#904,
    actions/runner-images#1143), so keep the checks for macOS and Ubuntu
    only.
2023-09-27 19:53:40 +02:00
undergroundwires-bot
5047c9b6e7 ⬆️ bump everywhere to 0.12.4 2023-09-26 11:45:04 +00:00
154 changed files with 12666 additions and 9727 deletions

View File

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

View File

@@ -8,4 +8,5 @@ runs:
- -
name: Run `npm ci` with retries name: Run `npm ci` with retries
shell: bash shell: bash
run: npm run install-deps -- --ci --root-directory "${{ inputs.working-directory }}" run: npm run install-deps -- --ci
working-directory: ${{ inputs.working-directory }}

View File

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

View File

@@ -1,4 +1,4 @@
name: build-checks name: checks.build
on: on:
push: push:
@@ -21,7 +21,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
@@ -49,7 +49,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
@@ -68,3 +68,33 @@ jobs:
- -
name: Verify bundled desktop build artifacts name: Verify bundled desktop build artifacts
run: npm run check:verify-build-artifacts -- --electron-bundled run: npm run check:verify-build-artifacts -- --electron-bundled
build-docker:
strategy:
matrix:
os: [ macos, ubuntu ] # Windows runners do not support Linux containers
fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Install Docker on macOS
if: matrix.os == 'macos' # macOS runner is missing Docker
run: |-
# Install Docker
brew install docker
# Docker on macOS misses daemon due to licensing, so install colima as runtime
brew install colima
# Start the daemon
colima start
-
name: Build Docker image
run: docker build -t undergroundwires/privacy.sexy:latest .
-
name: Run Docker image on port 8080
run: docker run -d -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest
-
name: Check server is up and returns HTTP 200
run: node ./scripts/verify-web-server-status.js --url http://localhost:8080

View File

@@ -15,7 +15,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node

View File

@@ -10,7 +10,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node

View File

@@ -18,7 +18,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
@@ -42,7 +42,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node

View File

@@ -1,4 +1,4 @@
name: security-checks name: checks.security.dependencies
on: on:
push: push:
@@ -13,7 +13,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node

View File

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

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- -
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
ref: master # otherwise it defaults to the version tag missing bump commit ref: master # otherwise it defaults to the version tag missing bump commit
fetch-depth: 0 # fetch all history fetch-depth: 0 # fetch all history

View File

@@ -10,7 +10,7 @@ jobs:
steps: steps:
- -
name: "Infrastructure: Checkout" name: "Infrastructure: Checkout"
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: aws path: aws
repository: undergroundwires/aws-static-site-with-cd repository: undergroundwires/aws-static-site-with-cd
@@ -75,7 +75,7 @@ jobs:
working-directory: aws working-directory: aws
- -
name: "App: Checkout" name: "App: Checkout"
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: app path: app
ref: master # otherwise we don't get version bump commit ref: master # otherwise we don't get version bump commit
@@ -102,7 +102,7 @@ jobs:
- -
name: "App: Deploy to S3" name: "App: Deploy to S3"
shell: bash shell: bash
run: >- run: |-
declare web_output_dir declare web_output_dir
if ! web_output_dir=$(cd app && node scripts/print-dist-dir.js --web); then if ! web_output_dir=$(cd app && node scripts/print-dist-dir.js --web); then
echo 'Error: Could not determine distribution directory.' echo 'Error: Could not determine distribution directory.'

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node

View File

@@ -16,7 +16,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Setup node name: Setup node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
- -
name: Set-up node name: Set-up node
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node

View File

@@ -1,5 +1,45 @@
# Changelog # Changelog
## 0.12.5 (2023-10-13)
* Fix Docker build and improve checks #220 | [7669985](https://github.com/undergroundwires/privacy.sexy/commit/7669985f8e1446e726a95626ecf35b3ce6b60a16)
* Add SAST security checks with SECURITY.md #178 | [3e5239f](https://github.com/undergroundwires/privacy.sexy/commit/3e5239f7d35e57749c01adf3dbbcd365aebb39c8)
* Add Scoop download instructions #174 | [cf55ca9](https://github.com/undergroundwires/privacy.sexy/commit/cf55ca9e28b064fa7a516077a9da23e3a8e3f534)
* win: fix and improve temp dir cleanup #176, #89 | [d457504](https://github.com/undergroundwires/privacy.sexy/commit/d45750428cca010daf2721b33a8ae3a01b28813b)
* win, linux: improve VSCode setting robustness #196 | [e8a52f7](https://github.com/undergroundwires/privacy.sexy/commit/e8a52f717dc799b34ceeb1c27c2b8219391dff6a)
* linux: fix obsolete Firefox DPI script #239 | [e5f6edf](https://github.com/undergroundwires/privacy.sexy/commit/e5f6edf405bcec7c29ea4d7932d1910620fa15f8)
* win: add removal of Edge assocations #64 | [888c916](https://github.com/undergroundwires/privacy.sexy/commit/888c9166fc66a2094137fa8be739cc21bafef5f6)
* win: improve Edge & OneDrive shortcut removal #73 | [8501495](https://github.com/undergroundwires/privacy.sexy/commit/8501495c170af61913288a63dbd369db5bbc5003)
* win: relocate and document SecHealthUI #190 | [2862951](https://github.com/undergroundwires/privacy.sexy/commit/286295128d0179358e0c6b7b6415d752175a1aed)
* Add developer toolkit UI component | [2147eae](https://github.com/undergroundwires/privacy.sexy/commit/2147eae687b82d05bc43bb4605d9068f148bb92a)
* win: fix and improve network data usage reset #265 | [5e359c2](https://github.com/undergroundwires/privacy.sexy/commit/5e359c2fb82a08e6acf7159b70ca86a8234b359b)
* win: improve app reversion and docs #260 | [a3f11df](https://github.com/undergroundwires/privacy.sexy/commit/a3f11dff187c821a00910c20dac05e285cda9073)
* Fix working directory in CI/CD web release | [698b570](https://github.com/undergroundwires/privacy.sexy/commit/698b570ee6e300d6703015464f4345b5e706f1cb)
* Implement new UI component for icons #230 | [48730bc](https://github.com/undergroundwires/privacy.sexy/commit/48730bca0506120bca4bf3a23545d59f2b1a9009)
* win: fix and improve AppCompat disabling #255 | [bab6316](https://github.com/undergroundwires/privacy.sexy/commit/bab6316e7625230cf4a4cf67c3aca417347db75c)
* win, linux, mac: fix typos and improve naming | [67c3677](https://github.com/undergroundwires/privacy.sexy/commit/67c3677621b201525a813e8a26f07d607176e89b)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.4...0.12.5)
## 0.12.4 (2023-09-25)
* win: fix Windows spotlight revert, docs, recommend | [659fea7](https://github.com/undergroundwires/privacy.sexy/commit/659fea7afcabcd0ea273cfdcc8c4bae190c126f3)
* win: fix Edge telemetry disabling for v116+ #242 | [6d301f9](https://github.com/undergroundwires/privacy.sexy/commit/6d301f99616ed49975876803d0098eafe4d3cb2e)
* win: fix, improve disabling automatic updates #252 | [6e9b65d](https://github.com/undergroundwires/privacy.sexy/commit/6e9b65d8b1b481c1471dde90876c37838b4ac4e5)
* win: refactor `update.mode` key for VSCode #215 | [c27172c](https://github.com/undergroundwires/privacy.sexy/commit/c27172c32e7c316b7cb0f44cab611eed89ca034e)
* Fix wrong action path in website CI deployment | [a1f2497](https://github.com/undergroundwires/privacy.sexy/commit/a1f24973813ccbdd7e1f06c64e1912a991a6bb64)
* Fix compiler bug with nested optional arguments | [53222fd](https://github.com/undergroundwires/privacy.sexy/commit/53222fd83c2846089746a217482195806f960d18)
* Fix no spacing after lists in documentation text | [f810ed0](https://github.com/undergroundwires/privacy.sexy/commit/f810ed0c147c2a46cae3b70b635ed81128646fff)
* Rewrite tooltip UI for efficiency and Vue 3.0 #230 | [8b930fc](https://github.com/undergroundwires/privacy.sexy/commit/8b930fc57c8ee6691ed6165bcb27d97e64a1a0c0)
* win: fix uninstallation of newer Edge #236 | [60dde11](https://github.com/undergroundwires/privacy.sexy/commit/60dde11311a2409537f5965f370b0daaaec53339)
* win: fix delivery optimization side-effects #173 | [203daeb](https://github.com/undergroundwires/privacy.sexy/commit/203daeb4a2fca0a0295cbc2a736394f9f87725e6)
* win: fix Defender scan artifacts removal #246 | [cb21a97](https://github.com/undergroundwires/privacy.sexy/commit/cb21a970b6b867e1476a5eb8a72b9a7fdd53a744)
* Fix outdated and broken links in README #161 | [0303ef2](https://github.com/undergroundwires/privacy.sexy/commit/0303ef2fd98b36306523e2a0c5f5ae812a4c6c99)
* Fix loss of tree node state when switching views | [8f188ac](https://github.com/undergroundwires/privacy.sexy/commit/8f188acd3c2d93e40c89569c74bc5cff992f0052)
* Fix slow appearance of nodes on tree view | [bd2082e](https://github.com/undergroundwires/privacy.sexy/commit/bd2082e8c574db065bb4462f30ea3ace2cb028cb)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.3...0.12.4)
## 0.12.3 (2023-09-09) ## 0.12.3 (2023-09-09)
* linux: use user.js over prefs.js for Firefox #232 | [dae6d11](https://github.com/undergroundwires/privacy.sexy/commit/dae6d114daab6857d773071211eb57619b136281) * linux: use user.js over prefs.js for Firefox #232 | [dae6d11](https://github.com/undergroundwires/privacy.sexy/commit/dae6d114daab6857d773071211eb57619b136281)

View File

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

View File

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

View File

@@ -16,14 +16,6 @@
src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat" src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"
/> />
</a> </a>
<!-- Code quality -->
<br />
<a href="https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript" target="_blank" rel="noopener noreferrer">
<img
alt="Language grade: JavaScript/TypeScript"
src="https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18"
/>
</a>
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability" target="_blank" rel="noopener noreferrer"> <a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability" target="_blank" rel="noopener noreferrer">
<img <img
alt="Maintainability" alt="Maintainability"
@@ -50,6 +42,20 @@
src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg"
/> />
</a> </a>
<!-- Security checks -->
<br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.sast.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Status of dependency security checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.sast/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.dependencies.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Status of Static Analysis Security Testing (SAST)"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.dependencies/badge.svg"
/>
</a>
<!-- Checks --> <!-- Checks -->
<br /> <br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer">
@@ -58,16 +64,10 @@
src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Security checks status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/security-checks/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml" target="_blank" rel="noopener noreferrer"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml" target="_blank" rel="noopener noreferrer">
<img <img
alt="Build checks status" alt="Status of build checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/build-checks/badge.svg" src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.build/badge.svg"
/> />
</a> </a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.desktop-runtime-errors.yaml" target="_blank" rel="noopener noreferrer"> <a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.desktop-runtime-errors.yaml" target="_blank" rel="noopener noreferrer">
@@ -122,7 +122,7 @@
## Get started ## Get started
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy). - 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
- 🖥️ **Offline**: Check [releases page](https://github.com/undergroundwires/privacy.sexy/releases), or download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-Setup-0.12.3.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-0.12.3.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.3/privacy.sexy-0.12.3.AppImage). - 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.5/privacy.sexy-Setup-0.12.5.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.5/privacy.sexy-0.12.5.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.5/privacy.sexy-0.12.5.AppImage). For more options, see [here](#additional-install-options).
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly. Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
@@ -150,6 +150,16 @@ Online version does not require to run any software on your computer. Offline ve
**Contribute 👷**. Contributions of any type are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) as the starting point. It includes useful information like [how to add new scripts](./CONTRIBUTING.md#extend-scripts). **Contribute 👷**. Contributions of any type are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) as the starting point. It includes useful information like [how to add new scripts](./CONTRIBUTING.md#extend-scripts).
## Additional Install Options
- Check the [releases page](https://github.com/undergroundwires/privacy.sexy/releases) for all available versions.
- Using [Scoop](https://scoop.sh/#/apps?q=privacy.sexy&s=2&d=1&o=true) package manager on Windows:
```powershell
scoop bucket add extras
scoop install privacy.sexy
```
## Development ## Development
Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment. Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment.
@@ -157,3 +167,7 @@ Refer to [development.md](./docs/development.md) for Docker usage and reading mo
Check [architecture.md](./docs/architecture.md) for an overview of design and how different parts and layers work together. You can refer to [application.md](./docs/application.md) for a closer look at application layer codebase and [presentation.md](./docs/presentation.md) for code related to GUI layer. [collection-files.md](./docs/collection-files.md) explains the YAML files that are the core of the application and [templating.md](./docs/templating.md) documents how to use templating language in those files. In [ci-cd.md](./docs/ci-cd.md), you can read more about the pipelines that automates maintenance tasks and ensures you get what see. Check [architecture.md](./docs/architecture.md) for an overview of design and how different parts and layers work together. You can refer to [application.md](./docs/application.md) for a closer look at application layer codebase and [presentation.md](./docs/presentation.md) for code related to GUI layer. [collection-files.md](./docs/collection-files.md) explains the YAML files that are the core of the application and [templating.md](./docs/templating.md) documents how to use templating language in those files. In [ci-cd.md](./docs/ci-cd.md), you can read more about the pipelines that automates maintenance tasks and ensures you get what see.
[docs/](./docs/) folder includes all other documentation. [docs/](./docs/) folder includes all other documentation.
## Security
Security is a top priority at privacy.sexy. An extensive commitment to security verification ensures this priority. For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).

31
SECURITY.md Normal file
View File

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

View File

@@ -6,7 +6,10 @@ const CYPRESS_BASE_DIR = 'tests/e2e/';
export default defineConfig({ export default defineConfig({
fixturesFolder: `${CYPRESS_BASE_DIR}/fixtures`, fixturesFolder: `${CYPRESS_BASE_DIR}/fixtures`,
screenshotsFolder: `${CYPRESS_BASE_DIR}/screenshots`, screenshotsFolder: `${CYPRESS_BASE_DIR}/screenshots`,
video: true,
videosFolder: `${CYPRESS_BASE_DIR}/videos`, videosFolder: `${CYPRESS_BASE_DIR}/videos`,
e2e: { e2e: {
baseUrl: `http://localhost:${ViteConfig.server.port}/`, baseUrl: `http://localhost:${ViteConfig.server.port}/`,
specPattern: `${CYPRESS_BASE_DIR}/**/*.cy.{js,jsx,ts,tsx}`, // Default: cypress/e2e/**/*.cy.{js,jsx,ts,tsx} specPattern: `${CYPRESS_BASE_DIR}/**/*.cy.{js,jsx,ts,tsx}`, // Default: cypress/e2e/**/*.cy.{js,jsx,ts,tsx}

View File

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

View File

@@ -60,6 +60,7 @@ See [ci-cd.md](./ci-cd.md) for more information.
1. Build: `docker build -t undergroundwires/privacy.sexy:latest .` 1. Build: `docker build -t undergroundwires/privacy.sexy:latest .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest` 2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest`
3. Application should be available at [`http://localhost:8080`](http://localhost:8080)
### Building ### Building
@@ -81,11 +82,12 @@ See [ci-cd.md](./ci-cd.md) for more information.
#### Automation scripts #### Automation scripts
- [**`node scripts/print-dist-dir.js [-- <options>]`**](../scripts/print-dist-dir.js): - [**`node scripts/print-dist-dir.js [<options>]`**](../scripts/print-dist-dir.js):
- Determines the absolute path of a distribution directory based on CLI arguments and outputs its absolute path. - Determines the absolute path of a distribution directory based on CLI arguments and outputs its absolute path.
- Primarily used by automation scripts.
- [**`npm run check:verify-build-artifacts [-- <options>]`**](../scripts/verify-build-artifacts.js): - [**`npm run check:verify-build-artifacts [-- <options>]`**](../scripts/verify-build-artifacts.js):
- Verifies the existence and content of build artifacts. Useful for ensuring that the build process is generating the expected output. - Verifies the existence and content of build artifacts. Useful for ensuring that the build process is generating the expected output.
- [**`node scripts/verify-web-server-status.js --url [URL]`**](../scripts/verify-web-server-status.js):
- Checks if a specified server is up with retries and returns an HTTP 200 status code.
## Recommended extensions ## Recommended extensions

View File

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

View File

@@ -56,6 +56,11 @@ These checks validate various qualities like runtime execution, building process
- Use [various tools](./../package.json) and [scripts](./../scripts). - Use [various tools](./../package.json) and [scripts](./../scripts).
- Are automatically executed as [GitHub workflows](./../.github/workflows). - Are automatically executed as [GitHub workflows](./../.github/workflows).
### Security checks
- [`checks.security.sast`](./../.github/workflows/checks.security.sast.yaml): Utilizes CodeQL to conduct Static Analysis Security Testing (SAST) to ensure the secure integrity of the codebase.
- [`checks.security.dependencies`](./../.github/workflows/checks.security.dependencies.yaml): Performs audits on third-party dependencies to identify and mitigate potential vulnerabilities, safeguarding the project from exploitable weaknesses.
## Tests structure ## Tests structure
- [`package.json`](./../package.json): Defines test commands and includes tools used in tests. - [`package.json`](./../package.json): Defines test commands and includes tools used in tests.

View File

@@ -8,15 +8,21 @@ import distDirs from './dist-dirs.json' assert { type: 'json' };
const MAIN_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/main/index.ts'); const MAIN_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/main/index.ts');
const PRELOAD_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/preload/index.ts'); const PRELOAD_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/preload/index.ts');
const WEB_INDEX_HTML_PATH = resolvePathFromProjectRoot('src/presentation/index.html'); const WEB_INDEX_HTML_PATH = resolvePathFromProjectRoot('src/presentation/index.html');
const DIST_DIR = resolvePathFromProjectRoot(distDirs.electronUnbundled); const ELECTRON_DIST_SUBDIRECTORIES = {
main: resolveElectronDistSubdirectory('main'),
preload: resolveElectronDistSubdirectory('preload'),
renderer: resolveElectronDistSubdirectory('renderer'),
};
process.env.ELECTRON_ENTRY = resolve(ELECTRON_DIST_SUBDIRECTORIES.main, 'index.cjs');
export default defineConfig({ export default defineConfig({
main: getSharedElectronConfig({ main: getSharedElectronConfig({
distDirSubfolder: 'main', distDirSubfolder: ELECTRON_DIST_SUBDIRECTORIES.main,
entryFilePath: MAIN_ENTRY_FILE, entryFilePath: MAIN_ENTRY_FILE,
}), }),
preload: getSharedElectronConfig({ preload: getSharedElectronConfig({
distDirSubfolder: 'preload', distDirSubfolder: ELECTRON_DIST_SUBDIRECTORIES.preload,
entryFilePath: PRELOAD_ENTRY_FILE, entryFilePath: PRELOAD_ENTRY_FILE,
}), }),
renderer: mergeConfig( renderer: mergeConfig(
@@ -25,7 +31,7 @@ export default defineConfig({
}), }),
{ {
build: { build: {
outDir: resolve(DIST_DIR, 'renderer'), outDir: ELECTRON_DIST_SUBDIRECTORIES.renderer,
rollupOptions: { rollupOptions: {
input: { input: {
index: WEB_INDEX_HTML_PATH, index: WEB_INDEX_HTML_PATH,
@@ -42,7 +48,7 @@ function getSharedElectronConfig(options: {
}): UserConfig { }): UserConfig {
return { return {
build: { build: {
outDir: resolve(DIST_DIR, options.distDirSubfolder), outDir: options.distDirSubfolder,
lib: { lib: {
entry: options.entryFilePath, entry: options.entryFilePath,
}, },
@@ -64,6 +70,11 @@ function getSharedElectronConfig(options: {
}; };
} }
function resolvePathFromProjectRoot(pathSegment: string) { function resolvePathFromProjectRoot(pathSegment: string): string {
return resolve(__dirname, pathSegment); return resolve(__dirname, pathSegment);
} }
function resolveElectronDistSubdirectory(subDirectory: string): string {
const electronDistDir = resolvePathFromProjectRoot(distDirs.electronUnbundled);
return resolve(electronDistDir, subDirectory);
}

10553
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,11 @@
{ {
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.12.3", "version": "0.12.5",
"private": true, "private": true,
"slogan": "Now you have the choice", "slogan": "Now you have the choice",
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆", "description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
"author": "undergroundwires", "author": "undergroundwires",
"type": "module", "type": "module",
"main": "./dist-electron-unbundled/main/index.cjs",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
@@ -35,77 +34,74 @@
}, },
"dependencies": { "dependencies": {
"@floating-ui/vue": "^1.0.2", "@floating-ui/vue": "^1.0.2",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^2.0.9",
"@juggle/resize-observer": "^3.4.0", "@juggle/resize-observer": "^3.4.0",
"ace-builds": "^1.23.4", "ace-builds": "^1.30.0",
"cross-fetch": "^4.0.0", "cross-fetch": "^4.0.0",
"electron-log": "^4.4.8",
"electron-progressbar": "^2.1.0", "electron-progressbar": "^2.1.0",
"electron-updater": "^6.1.4",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.2",
"npm": "^9.8.1", "vue": "^3.3.7"
"vue": "^2.7.14"
}, },
"devDependencies": { "devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.0.4", "@modyfi/vite-plugin-yaml": "^1.0.4",
"@rushstack/eslint-patch": "^1.3.2", "@rushstack/eslint-patch": "^1.5.1",
"@types/ace": "^0.0.48", "@types/ace": "^0.0.49",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-vue2": "^2.2.0", "@vitejs/plugin-vue": "^4.4.0",
"@vue/eslint-config-airbnb-with-typescript": "^7.0.0", "@vue/eslint-config-airbnb-with-typescript": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.3", "@vue/eslint-config-typescript": "^11.0.3",
"@vue/test-utils": "^1.3.6", "@vue/test-utils": "^2.4.1",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.16",
"cypress": "^12.17.2", "cypress": "^13.3.1",
"electron": "^25.3.2", "electron": "^27.0.0",
"electron-builder": "^24.6.3", "electron-builder": "^24.6.4",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-icon-builder": "^2.0.1", "electron-icon-builder": "^2.0.1",
"electron-log": "^4.4.8", "electron-vite": "^1.0.28",
"electron-updater": "^6.1.4", "eslint": "^8.51.0",
"electron-vite": "^1.0.27", "eslint-plugin-cypress": "^2.15.1",
"eslint": "^8.46.0", "eslint-plugin-vue": "^9.17.0",
"eslint-plugin-cypress": "^2.14.0", "eslint-plugin-vuejs-accessibility": "^2.2.0",
"eslint-plugin-vue": "^9.6.0", "icon-gen": "^4.0.0",
"eslint-plugin-vuejs-accessibility": "^1.2.0",
"icon-gen": "^3.0.1",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"markdownlint-cli": "^0.35.0", "markdownlint-cli": "^0.37.0",
"postcss": "^8.4.28", "postcss": "^8.4.31",
"remark-cli": "^11.0.0", "remark-cli": "^12.0.0",
"remark-lint-no-dead-urls": "^1.1.0", "remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^5.1.2", "remark-preset-lint-consistent": "^5.1.2",
"remark-validate-links": "^12.1.1", "remark-validate-links": "^13.0.0",
"sass": "^1.64.1", "sass": "^1.69.3",
"start-server-and-test": "^2.0.0", "start-server-and-test": "^2.0.1",
"svgexport": "^0.4.2", "svgexport": "^0.4.2",
"terser": "^5.19.2", "terser": "^5.21.0",
"tslib": "~2.4.0", "tslib": "^2.6.2",
"typescript": "~4.6.2", "typescript": "^5.2.2",
"vite": "^4.4.9", "vite": "^4.4.11",
"vitest": "^0.34.2", "vitest": "^0.34.6",
"vue-tsc": "^1.8.8", "vue-tsc": "^1.8.19",
"yaml-lint": "^1.7.0" "yaml-lint": "^1.7.0"
}, },
"//devDependencies": { "//devDependencies": {
"terser": "Used by @vitejs/plugin-legacy for minification", "terser": "Used by @vitejs/plugin-legacy for minification",
"typescript": [ "@rushstack/eslint-patch": "Needed by @vue/eslint-config-typescript",
"Cannot upgrade to 5.X.X due to unmaintained @vue/cli-plugin-typescript, https://github.com/vuejs/vue-cli/issues/7401", "@vue/eslint-config-typescript": "Cannot upgrade to 12.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
"Cannot upgrade to > 4.6.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252" "@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
], "@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58"
"tslib": "Cannot upgrade to > 2.4.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252",
"@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-typescript, https://github.com/vuejs/eslint-config-typescript/pull/60",
"@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-typescript, https://github.com/vuejs/eslint-config-typescript/pull/60"
}, },
"homepage": "https://privacy.sexy", "homepage": "https://privacy.sexy",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/undergroundwires/privacy.sexy.git" "url": "https://github.com/undergroundwires/privacy.sexy.git"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
},
"//optionalDependencies": {
"dmg-license": "Required by `electron-builder` for DMG builds on macOS, https://github.com/electron-userland/electron-builder/issues/6489, https://github.com/electron-userland/electron-builder/issues/6520"
} }
} }

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 160c8.8 0 16 7.2 16 16V336c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16H464zM80 96C35.8 96 0 131.8 0 176V336c0 44.2 35.8 80 80 80H464c44.2 0 80-35.8 80-80V320c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32V176c0-44.2-35.8-80-80-80H80zm368 96H96V320H448V192z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 160c8.8 0 16 7.2 16 16V336c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16H464zM80 96C35.8 96 0 131.8 0 176V336c0 44.2 35.8 80 80 80H464c44.2 0 80-35.8 80-80V320c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32V176c0-44.2-35.8-80-80-80H80zm208 96H96V320H288V192z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 363 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M208 0H332.1c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9V336c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V48c0-26.5 21.5-48 48-48zM48 128h80v64H64V448H256V416h64v48c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V176c0-26.5 21.5-48 48-48z"/></svg>

After

Width:  |  Height:  |  Size: 365 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M64 0C28.7 0 0 28.7 0 64V352c0 35.3 28.7 64 64 64H240l-10.7 32H160c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H346.7L336 416H512c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64zM512 64V288H64V64H512z"/></svg>

After

Width:  |  Height:  |  Size: 342 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm177.6 62.1C192.8 334.5 218.8 352 256 352s63.2-17.5 78.4-33.9c9-9.7 24.2-10.4 33.9-1.4s10.4 24.2 1.4 33.9c-22 23.8-60 49.4-113.6 49.4s-91.7-25.5-113.6-49.4c-9-9.7-8.4-24.9 1.4-33.9s24.9-8.4 33.9 1.4zM144.4 208a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm192-32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 495 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM216 232V334.1l31-31c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-72 72c-9.4 9.4-24.6 9.4-33.9 0l-72-72c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l31 31V232c0-13.3 10.7-24 24-24s24 10.7 24 24z"/></svg>

After

Width:  |  Height:  |  Size: 428 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V173.3c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32H64zm0 96c0-17.7 14.3-32 32-32H288c17.7 0 32 14.3 32 32v64c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V128zM224 288a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg>

After

Width:  |  Height:  |  Size: 407 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M384 480h48c11.4 0 21.9-6 27.6-15.9l112-192c5.8-9.9 5.8-22.1 .1-32.1S555.5 224 544 224H144c-11.4 0-21.9 6-27.6 15.9L48 357.1V96c0-8.8 7.2-16 16-16H181.5c4.2 0 8.3 1.7 11.3 4.7l26.5 26.5c21 21 49.5 32.8 79.2 32.8H416c8.8 0 16 7.2 16 16v32h48V160c0-35.3-28.7-64-64-64H298.5c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H87.7 384z"/></svg>

After

Width:  |  Height:  |  Size: 503 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 96C0 60.7 28.7 32 64 32H196.1c19.1 0 37.4 7.6 50.9 21.1L289.9 96H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H448c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16H286.6c-10.6 0-20.8-4.2-28.3-11.7L213.1 87c-4.5-4.5-10.6-7-17-7H64z"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M504.3 273.6c4.9-4.5 7.7-10.9 7.7-17.6s-2.8-13-7.7-17.6l-112-104c-7-6.5-17.2-8.2-25.9-4.4s-14.4 12.5-14.4 22l0 56-192 0 0-56c0-9.5-5.7-18.2-14.4-22s-18.9-2.1-25.9 4.4l-112 104C2.8 243 0 249.3 0 256s2.8 13 7.7 17.6l112 104c7 6.5 17.2 8.2 25.9 4.4s14.4-12.5 14.4-22l0-56 192 0 0 56c0 9.5 5.7 18.2 14.4 22s18.9 2.1 25.9-4.4l112-104z"/></svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>

After

Width:  |  Height:  |  Size: 343 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>

After

Width:  |  Height:  |  Size: 257 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 310 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 16c-6.7 0-10.8-2.8-15.5-6.1C201.9 5.4 194 0 176 0c-30.5 0-52 43.7-66 89.4C62.7 98.1 32 112.2 32 128c0 14.3 25 27.1 64.6 35.9c-.4 4-.6 8-.6 12.1c0 17 3.3 33.2 9.3 48H45.4C38 224 32 230 32 237.4c0 1.7 .3 3.4 1 5l38.8 96.9C28.2 371.8 0 423.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7c0-58.5-28.2-110.4-71.7-143L415 242.4c.6-1.6 1-3.3 1-5c0-7.4-6-13.4-13.4-13.4H342.7c6-14.8 9.3-31 9.3-48c0-4.1-.2-8.1-.6-12.1C391 155.1 416 142.3 416 128c0-15.8-30.7-29.9-78-38.6C324 43.7 302.5 0 272 0c-18 0-25.9 5.4-32.5 9.9c-4.8 3.3-8.8 6.1-15.5 6.1zm56 208H267.6c-16.5 0-31.1-10.6-36.3-26.2c-2.3-7-12.2-7-14.5 0c-5.2 15.6-19.9 26.2-36.3 26.2H168c-22.1 0-40-17.9-40-40V169.6c28.2 4.1 61 6.4 96 6.4s67.8-2.3 96-6.4V184c0 22.1-17.9 40-40 40zm-88 96l16 32L176 480 128 288l64 32zm128-32L272 480 240 352l16-32 64-32z"/></svg>

After

Width:  |  Height:  |  Size: 934 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>

After

Width:  |  Height:  |  Size: 390 B

View File

@@ -35,8 +35,7 @@
} }
.#{$name}-leave-active, .#{$name}-leave-active,
.#{$name}-enter, // Vue 2.X compatibility .#{$name}-enter-from
.#{$name}-enter-from // Vue 3.X compatibility
{ {
opacity: 0; opacity: 0;

View File

@@ -1,22 +1,23 @@
import { IconBootstrapper } from './Modules/IconBootstrapper'; import { Bootstrapper } from './Bootstrapper';
import { VueConstructor, IVueBootstrapper } from './IVueBootstrapper';
import { VueBootstrapper } from './Modules/VueBootstrapper';
import { RuntimeSanityValidator } from './Modules/RuntimeSanityValidator'; import { RuntimeSanityValidator } from './Modules/RuntimeSanityValidator';
import { AppInitializationLogger } from './Modules/AppInitializationLogger'; import { AppInitializationLogger } from './Modules/AppInitializationLogger';
import { DependencyBootstrapper } from './Modules/DependencyBootstrapper';
import type { App } from 'vue';
export class ApplicationBootstrapper implements IVueBootstrapper { export class ApplicationBootstrapper implements Bootstrapper {
public bootstrap(vue: VueConstructor): void { constructor(private readonly bootstrappers = ApplicationBootstrapper.getAllBootstrappers()) { }
const bootstrappers = ApplicationBootstrapper.getAllBootstrappers();
for (const bootstrapper of bootstrappers) { public async bootstrap(app: App): Promise<void> {
bootstrapper.bootstrap(vue); for (const bootstrapper of this.bootstrappers) {
// eslint-disable-next-line no-await-in-loop
await bootstrapper.bootstrap(app); // Not running `Promise.all` because order matters.
} }
} }
private static getAllBootstrappers(): IVueBootstrapper[] { private static getAllBootstrappers(): Bootstrapper[] {
return [ return [
new IconBootstrapper(),
new VueBootstrapper(),
new RuntimeSanityValidator(), new RuntimeSanityValidator(),
new DependencyBootstrapper(),
new AppInitializationLogger(), new AppInitializationLogger(),
]; ];
} }

View File

@@ -0,0 +1,5 @@
import type { App } from 'vue';
export interface Bootstrapper {
bootstrap(app: App): Promise<void>;
}

View File

@@ -1,7 +0,0 @@
import { VueConstructor } from 'vue';
export interface IVueBootstrapper {
bootstrap(vue: VueConstructor): void;
}
export { VueConstructor };

View File

@@ -1,13 +1,13 @@
import { ILogger } from '@/infrastructure/Log/ILogger'; import { ILogger } from '@/infrastructure/Log/ILogger';
import { IVueBootstrapper } from '../IVueBootstrapper'; import { Bootstrapper } from '../Bootstrapper';
import { ClientLoggerFactory } from '../ClientLoggerFactory'; import { ClientLoggerFactory } from '../ClientLoggerFactory';
export class AppInitializationLogger implements IVueBootstrapper { export class AppInitializationLogger implements Bootstrapper {
constructor( constructor(
private readonly logger: ILogger = ClientLoggerFactory.Current.logger, private readonly logger: ILogger = ClientLoggerFactory.Current.logger,
) { } ) { }
public bootstrap(): void { public async bootstrap(): Promise<void> {
// Do not remove [APP_INIT]; it's a marker used in tests. // Do not remove [APP_INIT]; it's a marker used in tests.
this.logger.info('[APP_INIT] Application is initialized.'); this.logger.info('[APP_INIT] Application is initialized.');
} }

View File

@@ -0,0 +1,20 @@
import { inject, type App } from 'vue';
import { buildContext } from '@/application/Context/ApplicationContextFactory';
import { provideDependencies } from '@/presentation/bootstrapping/DependencyProvider';
import { Bootstrapper } from '../Bootstrapper';
export class DependencyBootstrapper implements Bootstrapper {
constructor(
private readonly contextFactory = buildContext,
private readonly dependencyProvider = provideDependencies,
private readonly injector = inject,
) { }
public async bootstrap(app: App): Promise<void> {
const context = await this.contextFactory();
this.dependencyProvider(context, {
provide: app.provide,
inject: this.injector,
});
}
}

View File

@@ -1,38 +0,0 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faGithub } from '@fortawesome/free-brands-svg-icons';
/** BRAND ICONS (PREFIX: fab) */
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
/** REGULAR ICONS (PREFIX: far) */
import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons';
/** SOLID ICONS (PREFIX: fas (default)) */
import {
faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop, faTag, faGlobe,
faSave, faBatteryFull, faBatteryHalf, faPlay, faArrowsAltH,
} from '@fortawesome/free-solid-svg-icons';
import { IVueBootstrapper, VueConstructor } from '../IVueBootstrapper';
export class IconBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void {
library.add(
faGithub,
faUserSecret,
faSmile,
faDesktop,
faGlobe,
faTag,
faFolderOpen,
faFolder,
faTimes,
faFileDownload,
faSave,
faCopy,
faPlay,
faSearch,
faBatteryFull,
faBatteryHalf,
faInfoCircle,
faArrowsAltH,
);
vue.component('font-awesome-icon', FontAwesomeIcon);
}
}

View File

@@ -1,12 +1,12 @@
import { validateRuntimeSanity } from '@/infrastructure/RuntimeSanity/SanityChecks'; import { validateRuntimeSanity } from '@/infrastructure/RuntimeSanity/SanityChecks';
import { IVueBootstrapper } from '../IVueBootstrapper'; import { Bootstrapper } from '../Bootstrapper';
export class RuntimeSanityValidator implements IVueBootstrapper { export class RuntimeSanityValidator implements Bootstrapper {
constructor(private readonly validator = validateRuntimeSanity) { constructor(private readonly validator = validateRuntimeSanity) {
} }
public bootstrap(): void { public async bootstrap(): Promise<void> {
this.validator({ this.validator({
validateEnvironmentVariables: true, validateEnvironmentVariables: true,
validateWindowVariables: true, validateWindowVariables: true,

View File

@@ -1,8 +0,0 @@
import { VueConstructor, IVueBootstrapper } from '../IVueBootstrapper';
export class VueBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void {
const { config } = vue;
config.productionTip = false;
}
}

View File

@@ -7,20 +7,21 @@
<TheCodeButtons class="app__row app__code-buttons" /> <TheCodeButtons class="app__row app__code-buttons" />
<TheFooter /> <TheFooter />
</div> </div>
<OptionalDevToolkit />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineAsyncComponent, defineComponent } from 'vue';
import TheHeader from '@/presentation/components/TheHeader.vue'; import TheHeader from '@/presentation/components/TheHeader.vue';
import TheFooter from '@/presentation/components/TheFooter/TheFooter.vue'; import TheFooter from '@/presentation/components/TheFooter/TheFooter.vue';
import TheCodeButtons from '@/presentation/components/Code/CodeButtons/TheCodeButtons.vue'; import TheCodeButtons from '@/presentation/components/Code/CodeButtons/TheCodeButtons.vue';
import TheScriptArea from '@/presentation/components/Scripts/TheScriptArea.vue'; import TheScriptArea from '@/presentation/components/Scripts/TheScriptArea.vue';
import TheSearchBar from '@/presentation/components/TheSearchBar.vue'; import TheSearchBar from '@/presentation/components/TheSearchBar.vue';
import { buildContext } from '@/application/Context/ApplicationContextFactory';
import { provideDependencies } from '../bootstrapping/DependencyProvider';
const singletonAppContext = await buildContext(); const OptionalDevToolkit = process.env.NODE_ENV !== 'production'
? defineAsyncComponent(() => import('@/presentation/components/DevToolkit/DevToolkit.vue'))
: null;
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -29,10 +30,9 @@ export default defineComponent({
TheScriptArea, TheScriptArea,
TheSearchBar, TheSearchBar,
TheFooter, TheFooter,
OptionalDevToolkit,
}, },
setup() { setup() { },
provideDependencies(singletonAppContext); // In Vue 3.0 we can move it to main.ts
},
}); });
</script> </script>
@@ -59,5 +59,4 @@ export default defineComponent({
} }
} }
} }
</style> </style>

View File

@@ -4,30 +4,30 @@
type="button" type="button"
@click="onClicked" @click="onClicked"
> >
<font-awesome-icon <AppIcon
class="button__icon" class="button__icon"
:icon="[iconPrefix, iconName]" :icon="iconName"
size="2x"
/> />
<div class="button__text">{{text}}</div> <div class="button__text">{{text}}</div>
</button> </button>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, PropType } from 'vue';
import { IconName } from '@/presentation/components/Shared/Icon/IconName';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
export default defineComponent({ export default defineComponent({
components: {
AppIcon,
},
props: { props: {
text: { text: {
type: String, type: String,
required: true, required: true,
}, },
iconPrefix: {
type: String,
required: true,
},
iconName: { iconName: {
type: String, type: String as PropType<IconName>,
required: true, required: true,
}, },
}, },
@@ -64,6 +64,10 @@ export default defineComponent({
box-shadow: 0 3px 9px $color-primary-darkest; box-shadow: 0 3px 9px $color-primary-darkest;
border-radius: 4px; border-radius: 4px;
&__icon {
font-size: 2em;
}
@include clickable; @include clickable;
width: 10%; width: 10%;

View File

@@ -1,11 +1,11 @@
<template> <template>
<span class="code-wrapper"> <span class="code-wrapper">
<span class="dollar">$</span> <span class="dollar">$</span>
<code><slot /></code> <code ref="codeElement"><slot /></code>
<TooltipWrapper> <TooltipWrapper>
<font-awesome-icon <AppIcon
class="copy-button" class="copy-button"
:icon="['fas', 'copy']" icon="copy"
@click="copyCode" @click="copyCode"
/> />
<template v-slot:tooltip> <template v-slot:tooltip>
@@ -16,24 +16,34 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, useSlots } from 'vue'; import { defineComponent, shallowRef } from 'vue';
import { Clipboard } from '@/infrastructure/Clipboard'; import { Clipboard } from '@/infrastructure/Clipboard';
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue'; import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
TooltipWrapper, TooltipWrapper,
AppIcon,
}, },
setup() { setup() {
const slots = useSlots(); const codeElement = shallowRef<HTMLElement | undefined>();
function copyCode() { function copyCode() {
const code = slots.default()[0].text; const element = codeElement.value;
if (!element) {
throw new Error('Code element could not be found.');
}
const code = element.textContent;
if (!code) {
throw new Error('Code element does not contain any text.');
}
Clipboard.copyText(code); Clipboard.copyText(code);
} }
return { return {
copyCode, copyCode,
codeElement,
}; };
}, },
}); });

View File

@@ -14,7 +14,7 @@
<p> <p>
<strong>2. The hard (manual) alternative</strong>. This requires you to do additional manual <strong>2. The hard (manual) alternative</strong>. This requires you to do additional manual
steps. If you are unsure how to follow the instructions, hover on information steps. If you are unsure how to follow the instructions, hover on information
(<font-awesome-icon :icon="['fas', 'info-circle']" />) (<AppIcon icon="circle-info" />)
icons near the steps, or follow the easy alternative described above. icons near the steps, or follow the easy alternative described above.
</p> </p>
<p> <p>
@@ -27,9 +27,9 @@
<div class="step__action"> <div class="step__action">
<span>{{ step.action.instruction }}</span> <span>{{ step.action.instruction }}</span>
<TooltipWrapper v-if="step.action.details"> <TooltipWrapper v-if="step.action.details">
<font-awesome-icon <AppIcon
class="explanation" class="explanation"
:icon="['fas', 'info-circle']" icon="circle-info"
/> />
<template v-slot:tooltip> <template v-slot:tooltip>
<div v-html="step.action.details" /> <div v-html="step.action.details" />
@@ -39,9 +39,9 @@
<div v-if="step.code" class="step__code"> <div v-if="step.code" class="step__code">
<CodeInstruction>{{ step.code.instruction }}</CodeInstruction> <CodeInstruction>{{ step.code.instruction }}</CodeInstruction>
<TooltipWrapper v-if="step.code.details"> <TooltipWrapper v-if="step.code.details">
<font-awesome-icon <AppIcon
class="explanation" class="explanation"
:icon="['fas', 'info-circle']" icon="circle-info"
/> />
<template v-slot:tooltip> <template v-slot:tooltip>
<div v-html="step.code.details" /> <div v-html="step.code.details" />
@@ -62,6 +62,7 @@ import {
import { InjectionKeys } from '@/presentation/injectionSymbols'; import { InjectionKeys } from '@/presentation/injectionSymbols';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue'; import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import CodeInstruction from './CodeInstruction.vue'; import CodeInstruction from './CodeInstruction.vue';
import { IInstructionListData } from './InstructionListData'; import { IInstructionListData } from './InstructionListData';
@@ -69,6 +70,7 @@ export default defineComponent({
components: { components: {
CodeInstruction, CodeInstruction,
TooltipWrapper, TooltipWrapper,
AppIcon,
}, },
props: { props: {
data: { data: {

View File

@@ -4,19 +4,16 @@
v-if="canRun" v-if="canRun"
text="Run" text="Run"
v-on:click="executeCode" v-on:click="executeCode"
icon-prefix="fas"
icon-name="play" icon-name="play"
/> />
<IconButton <IconButton
:text="isDesktopVersion ? 'Save' : 'Download'" :text="isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCode" v-on:click="saveCode"
icon-prefix="fas" :icon-name="isDesktopVersion ? 'floppy-disk' : 'file-arrow-down'"
:icon-name="isDesktopVersion ? 'save' : 'file-download'"
/> />
<IconButton <IconButton
text="Copy" text="Copy"
v-on:click="copyCode" v-on:click="copyCode"
icon-prefix="fas"
icon-name="copy" icon-name="copy"
/> />
<ModalDialog v-if="instructions" v-model="areInstructionsVisible"> <ModalDialog v-if="instructions" v-model="areInstructionsVisible">

View File

@@ -185,14 +185,16 @@ function getDefaultCode(language: ScriptingLanguage): string {
<style scoped lang="scss"> <style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *; @use "@/presentation/assets/styles/main" as *;
::v-deep .code-area { :deep() {
min-height: 200px; .code-area {
width: 100%; min-height: 200px;
height: 100%; width: 100%;
overflow: auto; height: 100%;
&__highlight { overflow: auto;
background-color: $color-secondary-light; &__highlight {
position: absolute; background-color: $color-secondary-light;
position: absolute;
}
} }
} }
</style> </style>

View File

@@ -0,0 +1,71 @@
<template>
<div class="dev-toolkit">
<div class="title">
Tools
</div>
<hr />
<button
v-for="action in devActions"
@click="action.handler"
:key="action.name"
type="button">
{{ action.name }}
</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { dumpNames } from './DumpNames';
export default defineComponent({
setup() {
const devActions: readonly DevAction[] = [
{
name: 'Log script/category names',
handler: async () => {
const names = await dumpNames();
console.log(names);
},
},
];
return {
devActions,
};
},
});
interface DevAction {
readonly name: string;
readonly handler: () => void | Promise<void>;
}
</script>
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.dev-toolkit {
position: fixed;
top: 0;
right: 0;
background-color: rgba($color-on-surface, 0.5);
color: $color-on-primary;
padding: 10px;
z-index: 10000;
.title {
font-weight: bold;
text-align: center;
}
button {
display: block;
margin-bottom: 10px;
padding: 5px 10px;
background-color: $color-primary;
color: $color-on-primary;
border: none;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,35 @@
import { IApplication } from '@/domain/IApplication';
import { ApplicationFactory } from '@/application/ApplicationFactory';
export async function dumpNames(): Promise<string> {
const application = await ApplicationFactory.Current.getApp();
const names = collectNames(application);
const output = names.join('\n');
return output;
}
function collectNames(application: IApplication): string[] {
const { collections } = application;
const allNames = [
...collections.flatMap((collection) => collection.getAllCategories().map((c) => c.name)),
...collections.flatMap((collection) => collection.getAllScripts().map((c) => c.name)),
];
const uniqueNames = [...new Set(allNames)];
return shuffle(uniqueNames);
}
/*
Shuffle an array of strings, returning a new array with elements in random order.
Uses the Fisher-Yates (or Durstenfeld) algorithm.
*/
function shuffle(array: readonly string[]): string[] {
const shuffledArray = [...array];
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
}
return shuffledArray;
}

View File

@@ -28,8 +28,8 @@ $gap: 0.25rem;
font-family: $font-normal; font-family: $font-normal;
display: flex; display: flex;
align-items: center; align-items: center;
.items { :deep(.items) {
* + *::before { > * + *::before {
content: '|'; content: '|';
padding-right: $gap; padding-right: $gap;
padding-left: $gap; padding-left: $gap;

View File

@@ -1,5 +1,9 @@
<template> <template>
<span> <!-- Parent wrapper allows adding content inside with CSS without making it clickable --> <span>
<!--
Parent wrapper allows `MenuOptionList` to safely add content inside
such as adding content in `::before` block without making it clickable.
-->
<span <span
v-bind:class="{ v-bind:class="{
disabled: !enabled, disabled: !enabled,

View File

@@ -19,7 +19,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'; import { defineComponent, shallowRef } from 'vue';
import SliderHandle from './SliderHandle.vue'; import SliderHandle from './SliderHandle.vue';
export default defineComponent({ export default defineComponent({
@@ -45,7 +45,7 @@ export default defineComponent({
}, },
}, },
setup() { setup() {
const firstElement = ref<HTMLElement>(); const firstElement = shallowRef<HTMLElement>();
function onResize(displacementX: number): void { function onResize(displacementX: number): void {
const leftWidth = firstElement.value.offsetWidth + displacementX; const leftWidth = firstElement.value.offsetWidth + displacementX;

View File

@@ -4,9 +4,9 @@
:style="{ cursor: cursorCssValue }" :style="{ cursor: cursorCssValue }"
@mousedown="startResize"> @mousedown="startResize">
<div class="line" /> <div class="line" />
<font-awesome-icon <AppIcon
class="icon" class="icon"
:icon="['fas', 'arrows-alt-h']" icon="left-right"
/> />
<div class="line" /> <div class="line" />
</div> </div>
@@ -14,8 +14,12 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, onUnmounted } from 'vue'; import { defineComponent, onUnmounted } from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
export default defineComponent({ export default defineComponent({
components: {
AppIcon,
},
emits: { emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
resized: (displacementX: number) => true, resized: (displacementX: number) => true,

View File

@@ -17,18 +17,18 @@
</span> </span>
<span v-else>Oh no 😢</span> <span v-else>Oh no 😢</span>
<!-- Expand icon --> <!-- Expand icon -->
<font-awesome-icon <AppIcon
class="card__inner__expand-icon" class="card__inner__expand-icon"
:icon="['far', isExpanded ? 'folder-open' : 'folder']" :icon="isExpanded ? 'folder-open' : 'folder'"
/> />
<!-- Indeterminate and full states --> <!-- Indeterminate and full states -->
<div class="card__inner__state-icons"> <div class="card__inner__state-icons">
<font-awesome-icon <AppIcon
:icon="['fa', 'battery-half']" icon="battery-half"
v-if="isAnyChildSelected && !areAllChildrenSelected" v-if="isAnyChildSelected && !areAllChildrenSelected"
/> />
<font-awesome-icon <AppIcon
:icon="['fa', 'battery-full']" icon="battery-full"
v-if="areAllChildrenSelected" v-if="areAllChildrenSelected"
/> />
</div> </div>
@@ -38,8 +38,8 @@
<ScriptsTree :categoryId="categoryId" /> <ScriptsTree :categoryId="categoryId" />
</div> </div>
<div class="card__expander__close-button"> <div class="card__expander__close-button">
<font-awesome-icon <AppIcon
:icon="['fas', 'times']" icon="xmark"
v-on:click="collapse()" v-on:click="collapse()"
/> />
</div> </div>
@@ -50,8 +50,9 @@
<script lang="ts"> <script lang="ts">
import { import {
defineComponent, ref, watch, computed, defineComponent, ref, watch, computed,
inject, inject, shallowRef,
} from 'vue'; } from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import { InjectionKeys } from '@/presentation/injectionSymbols'; import { InjectionKeys } from '@/presentation/injectionSymbols';
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue'; import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
import { sleep } from '@/infrastructure/Threading/AsyncSleep'; import { sleep } from '@/infrastructure/Threading/AsyncSleep';
@@ -59,6 +60,7 @@ import { sleep } from '@/infrastructure/Threading/AsyncSleep';
export default defineComponent({ export default defineComponent({
components: { components: {
ScriptsTree, ScriptsTree,
AppIcon,
}, },
props: { props: {
categoryId: { categoryId: {
@@ -93,7 +95,7 @@ export default defineComponent({
const isAnyChildSelected = ref(false); const isAnyChildSelected = ref(false);
const areAllChildrenSelected = ref(false); const areAllChildrenSelected = ref(false);
const cardElement = ref<HTMLElement>(); const cardElement = shallowRef<HTMLElement>();
const cardTitle = computed<string | undefined>(() => { const cardTitle = computed<string | undefined>(() => {
if (!props.categoryId || !currentState.value) { if (!props.categoryId || !currentState.value) {

View File

@@ -11,7 +11,7 @@ export function hasDirective(el: Element): boolean {
} }
export const NonCollapsing: ObjectDirective<HTMLElement> = { export const NonCollapsing: ObjectDirective<HTMLElement> = {
inserted(el: HTMLElement) { // In Vue 3, use "mounted" mounted(el: HTMLElement) {
el.setAttribute(attributeName, ''); el.setAttribute(attributeName, '');
}, },
}; };

View File

@@ -18,7 +18,7 @@
class="search__query__close-button" class="search__query__close-button"
v-on:click="clearSearchQuery()" v-on:click="clearSearchQuery()"
> >
<font-awesome-icon :icon="['fas', 'times']" /> <AppIcon icon="xmark" />
</div> </div>
</div> </div>
<div v-if="!searchHasMatches" class="search-no-matches"> <div v-if="!searchHasMatches" class="search-no-matches">
@@ -41,6 +41,7 @@ import {
defineComponent, PropType, ref, computed, defineComponent, PropType, ref, computed,
inject, inject,
} from 'vue'; } from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import { InjectionKeys } from '@/presentation/injectionSymbols'; import { InjectionKeys } from '@/presentation/injectionSymbols';
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue'; import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
import CardList from '@/presentation/components/Scripts/View/Cards/CardList.vue'; import CardList from '@/presentation/components/Scripts/View/Cards/CardList.vue';
@@ -52,6 +53,7 @@ export default defineComponent({
components: { components: {
ScriptsTree, ScriptsTree,
CardList, CardList,
AppIcon,
}, },
props: { props: {
currentView: { currentView: {

View File

@@ -6,14 +6,18 @@
v-on:click.stop v-on:click.stop
v-on:click="toggle()" v-on:click="toggle()"
> >
<font-awesome-icon :icon="['fas', 'info-circle']" /> <AppIcon icon="circle-info" />
</a> </a>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
export default defineComponent({ export default defineComponent({
components: {
AppIcon,
},
emits: [ emits: [
'show', 'show',
'hide', 'hide',
@@ -52,5 +56,4 @@ export default defineComponent({
color: $color-primary-light; color: $color-primary-light;
} }
} }
</style> </style>

View File

@@ -20,7 +20,7 @@ import { defineComponent, computed } from 'vue';
export default defineComponent({ export default defineComponent({
props: { props: {
value: Boolean, modelValue: Boolean,
label: { label: {
type: String, type: String,
required: true, required: true,
@@ -32,19 +32,19 @@ export default defineComponent({
}, },
emits: { emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
input: (isChecked: boolean) => true, 'update:modelValue': (isChecked: boolean) => true,
/* eslint-enable @typescript-eslint/no-unused-vars */ /* eslint-enable @typescript-eslint/no-unused-vars */
}, },
setup(props, { emit }) { setup(props, { emit }) {
const isChecked = computed({ const isChecked = computed({
get() { get() {
return props.value; return props.modelValue;
}, },
set(value: boolean) { set(value: boolean) {
if (value === props.value) { if (value === props.modelValue) {
return; return;
} }
emit('input', value); emit('update:modelValue', value);
}, },
}); });

View File

@@ -2,14 +2,6 @@ import type { ReadOnlyTreeNode } from '../Node/TreeNode';
export interface TreeViewFilterEvent { export interface TreeViewFilterEvent {
readonly action: TreeViewFilterAction; readonly action: TreeViewFilterAction;
/**
* A simple numeric value to ensure uniqueness of each event.
*
* This property is used to guarantee that the watch function will trigger
* even if the same filter action value is emitted consecutively.
*/
readonly timestamp: Date;
readonly predicate?: TreeViewFilterPredicate; readonly predicate?: TreeViewFilterPredicate;
} }
@@ -25,7 +17,6 @@ export function createFilterTriggeredEvent(
): TreeViewFilterEvent { ): TreeViewFilterEvent {
return { return {
action: TreeViewFilterAction.Triggered, action: TreeViewFilterAction.Triggered,
timestamp: new Date(),
predicate, predicate,
}; };
} }
@@ -33,6 +24,5 @@ export function createFilterTriggeredEvent(
export function createFilterRemovedEvent(): TreeViewFilterEvent { export function createFilterRemovedEvent(): TreeViewFilterEvent {
return { return {
action: TreeViewFilterAction.Removed, action: TreeViewFilterAction.Removed,
timestamp: new Date(),
}; };
} }

View File

@@ -184,10 +184,7 @@ export default defineComponent({
transform: translateX(0); transform: translateX(0);
} }
.#{$name}-enter,
// Vue 2.X compatibility
.#{$name}-enter-from, .#{$name}-enter-from,
// Vue 3.X compatibility
.#{$name}-leave-to { .#{$name}-leave-to {
opacity: 0; opacity: 0;
transform: translateX(-2em); transform: translateX(-2em);

View File

@@ -1,5 +1,5 @@
import { import {
WatchSource, inject, ref, watch, WatchSource, inject, shallowRef, watch,
} from 'vue'; } from 'vue';
import { InjectionKeys } from '@/presentation/injectionSymbols'; import { InjectionKeys } from '@/presentation/injectionSymbols';
import { ReadOnlyTreeNode } from './TreeNode'; import { ReadOnlyTreeNode } from './TreeNode';
@@ -10,7 +10,7 @@ export function useNodeState(
) { ) {
const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)(); const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)();
const state = ref<TreeNodeStateDescriptor>(); const state = shallowRef<TreeNodeStateDescriptor>();
watch(nodeWatcher, (node: ReadOnlyTreeNode) => { watch(nodeWatcher, (node: ReadOnlyTreeNode) => {
if (!node) { if (!node) {

View File

@@ -1,29 +0,0 @@
import { ReadOnlyTreeNode } from '../../Node/TreeNode';
import { RenderQueueOrderer } from './RenderQueueOrderer';
export class CollapseDepthOrderer implements RenderQueueOrderer {
public orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[] {
return orderNodes(nodes);
}
}
function orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[] {
return [...nodes]
.sort((a, b) => {
const [aCollapseStatus, bCollapseStatus] = [isNodeCollapsed(a), isNodeCollapsed(b)];
if (aCollapseStatus !== bCollapseStatus) {
return (aCollapseStatus ? 1 : 0) - (bCollapseStatus ? 1 : 0);
}
return a.hierarchy.depthInTree - b.hierarchy.depthInTree;
});
}
function isNodeCollapsed(node: ReadOnlyTreeNode): boolean {
if (!node.state.current.isExpanded) {
return true;
}
if (node.hierarchy.parent) {
return isNodeCollapsed(node.hierarchy.parent);
}
return false;
}

View File

@@ -0,0 +1,35 @@
import { ReadOnlyTreeNode } from '../../Node/TreeNode';
import { RenderQueueOrderer } from './RenderQueueOrderer';
export class CollapsedParentOrderer implements RenderQueueOrderer {
public orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[] {
return orderNodes(nodes);
}
}
function orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[] {
return [...nodes]
.map((node, index) => ({ node, index }))
.sort((a, b) => {
const [
isANodeOfCollapsedParent,
isBNodeOfCollapsedParent,
] = [isParentCollapsed(a.node), isParentCollapsed(b.node)];
if (isANodeOfCollapsedParent !== isBNodeOfCollapsedParent) {
return (isANodeOfCollapsedParent ? 1 : 0) - (isBNodeOfCollapsedParent ? 1 : 0);
}
return a.index - b.index;
})
.map(({ node }) => node);
}
function isParentCollapsed(node: ReadOnlyTreeNode): boolean {
const parentNode = node.hierarchy.parent;
if (parentNode) {
if (!parentNode.state.current.isExpanded) {
return true;
}
return isParentCollapsed(parentNode);
}
return false;
}

View File

@@ -9,7 +9,7 @@ import { NodeRenderingStrategy } from './Scheduling/NodeRenderingStrategy';
import { DelayScheduler } from './DelayScheduler'; import { DelayScheduler } from './DelayScheduler';
import { TimeoutDelayScheduler } from './Scheduling/TimeoutDelayScheduler'; import { TimeoutDelayScheduler } from './Scheduling/TimeoutDelayScheduler';
import { RenderQueueOrderer } from './Ordering/RenderQueueOrderer'; import { RenderQueueOrderer } from './Ordering/RenderQueueOrderer';
import { CollapseDepthOrderer } from './Ordering/CollapseDepthOrderer'; import { CollapsedParentOrderer } from './Ordering/CollapsedParentOrderer';
/** /**
* Renders tree nodes gradually to prevent UI freeze when loading large amounts of nodes. * Renders tree nodes gradually to prevent UI freeze when loading large amounts of nodes.
@@ -21,7 +21,7 @@ export function useGradualNodeRendering(
scheduler: DelayScheduler = new TimeoutDelayScheduler(), scheduler: DelayScheduler = new TimeoutDelayScheduler(),
initialBatchSize = 30, initialBatchSize = 30,
subsequentBatchSize = 5, subsequentBatchSize = 5,
orderer: RenderQueueOrderer = new CollapseDepthOrderer(), orderer: RenderQueueOrderer = new CollapsedParentOrderer(),
): NodeRenderingStrategy { ): NodeRenderingStrategy {
const nodesToRender = new Set<ReadOnlyTreeNode>(); const nodesToRender = new Set<ReadOnlyTreeNode>();
const nodesBeingRendered = shallowRef(new Set<ReadOnlyTreeNode>()); const nodesBeingRendered = shallowRef(new Set<ReadOnlyTreeNode>());

View File

@@ -14,7 +14,7 @@
<script lang="ts"> <script lang="ts">
import { import {
defineComponent, onMounted, watch, defineComponent, onMounted, watch,
ref, PropType, shallowRef, PropType,
} from 'vue'; } from 'vue';
import { TreeRootManager } from './TreeRoot/TreeRootManager'; import { TreeRootManager } from './TreeRoot/TreeRootManager';
import TreeRoot from './TreeRoot/TreeRoot.vue'; import TreeRoot from './TreeRoot/TreeRoot.vue';
@@ -53,7 +53,7 @@ export default defineComponent({
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const treeContainerElement = ref<HTMLElement | undefined>(); const treeContainerElement = shallowRef<HTMLElement | undefined>();
const tree = new TreeRootManager(); const tree = new TreeRootManager();

View File

@@ -1,5 +1,5 @@
import { import {
WatchSource, watch, inject, readonly, ref, WatchSource, watch, inject, shallowReadonly, shallowRef,
} from 'vue'; } from 'vue';
import { InjectionKeys } from '@/presentation/injectionSymbols'; import { InjectionKeys } from '@/presentation/injectionSymbols';
import { TreeRoot } from './TreeRoot/TreeRoot'; import { TreeRoot } from './TreeRoot/TreeRoot';
@@ -8,8 +8,8 @@ import { QueryableNodes } from './TreeRoot/NodeCollection/Query/QueryableNodes';
export function useCurrentTreeNodes(treeWatcher: WatchSource<TreeRoot>) { export function useCurrentTreeNodes(treeWatcher: WatchSource<TreeRoot>) {
const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)(); const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)();
const tree = ref<TreeRoot | undefined>(); const tree = shallowRef<TreeRoot | undefined>();
const nodes = ref<QueryableNodes | undefined>(); const nodes = shallowRef<QueryableNodes | undefined>();
watch(treeWatcher, (newTree) => { watch(treeWatcher, (newTree) => {
tree.value = newTree; tree.value = newTree;
@@ -22,6 +22,6 @@ export function useCurrentTreeNodes(treeWatcher: WatchSource<TreeRoot>) {
}, { immediate: true }); }, { immediate: true });
return { return {
nodes: readonly(nodes), nodes: shallowReadonly(nodes),
}; };
} }

View File

@@ -1,5 +1,5 @@
import { import {
WatchSource, inject, watch, ref, WatchSource, inject, watch, shallowRef,
} from 'vue'; } from 'vue';
import { InjectionKeys } from '@/presentation/injectionSymbols'; import { InjectionKeys } from '@/presentation/injectionSymbols';
import { IEventSubscription } from '@/infrastructure/Events/IEventSource'; import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
@@ -17,7 +17,7 @@ export function useNodeStateChangeAggregator(
const { nodes } = useTreeNodes(treeWatcher); const { nodes } = useTreeNodes(treeWatcher);
const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)(); const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)();
const onNodeChangeCallback = ref<NodeStateChangeEventCallback>(); const onNodeChangeCallback = shallowRef<NodeStateChangeEventCallback>();
watch([ watch([
() => nodes.value, () => nodes.value,

View File

@@ -1,5 +1,5 @@
import { import {
computed, inject, readonly, ref, computed, inject, shallowReadonly, shallowRef,
} from 'vue'; } from 'vue';
import { InjectionKeys } from '@/presentation/injectionSymbols'; import { InjectionKeys } from '@/presentation/injectionSymbols';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
@@ -15,7 +15,7 @@ export function useSelectedScriptNodeIds(scriptNodeIdParser = getScriptNodeId) {
}); });
return { return {
selectedScriptNodeIds: readonly(selectedNodeIds), selectedScriptNodeIds: shallowReadonly(selectedNodeIds),
}; };
} }
@@ -23,7 +23,7 @@ function useSelectedScripts() {
const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)(); const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)();
const { onStateChange } = inject(InjectionKeys.useCollectionState)(); const { onStateChange } = inject(InjectionKeys.useCollectionState)();
const selectedScripts = ref<readonly SelectedScript[]>([]); const selectedScripts = shallowRef<readonly SelectedScript[]>([]);
onStateChange((state) => { onStateChange((state) => {
selectedScripts.value = state.selection.selectedScripts; selectedScripts.value = state.selection.selectedScripts;
@@ -35,6 +35,6 @@ function useSelectedScripts() {
}, { immediate: true }); }, { immediate: true });
return { return {
selectedScripts: readonly(selectedScripts), selectedScripts: shallowReadonly(selectedScripts),
}; };
} }

View File

@@ -1,5 +1,5 @@
import { import {
Ref, inject, readonly, ref, Ref, inject, shallowReadonly, shallowRef,
} from 'vue'; } from 'vue';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
@@ -21,7 +21,7 @@ export function useTreeViewFilterEvent() {
const { onStateChange } = inject(InjectionKeys.useCollectionState)(); const { onStateChange } = inject(InjectionKeys.useCollectionState)();
const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)(); const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)();
const latestFilterEvent = ref<TreeViewFilterEvent | undefined>(undefined); const latestFilterEvent = shallowRef<TreeViewFilterEvent | undefined>(undefined);
const treeNodePredicate: TreeNodeFilterResultPredicate = (node, filterResult) => filterMatches( const treeNodePredicate: TreeNodeFilterResultPredicate = (node, filterResult) => filterMatches(
getNodeMetadata(node), getNodeMetadata(node),
@@ -36,7 +36,7 @@ export function useTreeViewFilterEvent() {
}, { immediate: true }); }, { immediate: true });
return { return {
latestFilterEvent: readonly(latestFilterEvent), latestFilterEvent: shallowReadonly(latestFilterEvent),
}; };
} }

View File

@@ -1,6 +1,4 @@
import { import { shallowRef, shallowReadonly } from 'vue';
ref, computed, readonly,
} from 'vue';
import { IApplicationContext, IReadOnlyApplicationContext } from '@/application/Context/IApplicationContext'; import { IApplicationContext, IReadOnlyApplicationContext } from '@/application/Context/IApplicationContext';
import { ICategoryCollectionState, IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState, IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IEventSubscriptionCollection } from '@/infrastructure/Events/IEventSubscriptionCollection'; import { IEventSubscriptionCollection } from '@/infrastructure/Events/IEventSubscriptionCollection';
@@ -16,7 +14,7 @@ export function useCollectionState(
throw new Error('missing events'); throw new Error('missing events');
} }
const currentState = ref<ICategoryCollectionState>(context.state); const currentState = shallowRef<IReadOnlyCategoryCollectionState>(context.state);
events.register([ events.register([
context.contextChanged.on((event) => { context.contextChanged.on((event) => {
currentState.value = event.newState; currentState.value = event.newState;
@@ -66,8 +64,7 @@ export function useCollectionState(
modifyCurrentContext, modifyCurrentContext,
onStateChange, onStateChange,
currentContext: context as IReadOnlyApplicationContext, currentContext: context as IReadOnlyApplicationContext,
currentState: readonly(computed<IReadOnlyCategoryCollectionState>(() => currentState.value)), currentState: shallowReadonly(currentState),
events: events as IEventSubscriptionCollection,
}; };
} }

View File

@@ -0,0 +1,27 @@
import { onMounted } from 'vue';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
// AsyncLazy ensures single load of the ResizeObserver polyfill,
// even when multiple calls are made simultaneously.
const polyfillLoader = new AsyncLazy(async () => {
if ('ResizeObserver' in window) {
return window.ResizeObserver;
}
const module = await import('@juggle/resize-observer');
globalThis.window.ResizeObserver = module.ResizeObserver;
return module.ResizeObserver;
});
async function polyfillResizeObserver(): Promise<typeof ResizeObserver> {
return polyfillLoader.getValue();
}
export function useResizeObserverPolyfill() {
const resizeObserverReady = new Promise<void>((resolve) => {
onMounted(async () => {
await polyfillResizeObserver();
resolve();
});
});
return { resizeObserverReady };
}

View File

@@ -0,0 +1,40 @@
<template>
<div v-html="svgContent" class="inline-icon" />
</template>
<script lang="ts">
import {
defineComponent,
PropType,
inject,
} from 'vue';
import { useSvgLoader } from './UseSvgLoader';
import { IconName } from './IconName';
export default defineComponent({
props: {
icon: {
type: String as PropType<IconName>,
required: true,
},
},
setup(props) {
const useSvgLoaderHook = inject('useSvgLoaderHook', useSvgLoader);
const { svgContent } = useSvgLoaderHook(() => props.icon);
return { svgContent };
},
});
</script>
<style lang="scss" scoped>
.inline-icon {
display: inline-block;
:deep(svg) { // using :deep because when v-html is used the content doesn't go through Vue's template compiler.
display: inline-block;
height: 1em;
overflow: visible;
vertical-align: -0.125em;
}
}
</style>

View File

@@ -0,0 +1,22 @@
export const IconNames = [
'magnifying-glass',
'copy',
'circle-info',
'user-secret',
'tag',
'github',
'face-smile',
'globe',
'desktop',
'xmark',
'battery-half',
'battery-full',
'folder',
'folder-open',
'left-right',
'file-arrow-down',
'floppy-disk',
'play',
] as const;
export type IconName = typeof IconNames[number];

View File

@@ -0,0 +1,92 @@
import {
WatchSource, shallowReadonly, ref, watch,
} from 'vue';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { IconName } from './IconName';
export function useSvgLoader(
iconWatcher: WatchSource<IconName>,
loaders: FileLoaders = RawSvgLoaders,
) {
const svgContent = ref<string>('');
watch(iconWatcher, async (iconName) => {
svgContent.value = await lazyLoadSvg(iconName, loaders);
}, { immediate: true });
return {
svgContent: shallowReadonly(svgContent),
};
}
export function clearIconCache() {
LazyIconCache.clear();
}
export type FileLoaders = Record<string, () => Promise<string>>;
const LazyIconCache = new Map<IconName, AsyncLazy<string>>();
async function lazyLoadSvg(name: IconName, loaders: FileLoaders): Promise<string> {
let iconLoader = LazyIconCache.get(name);
if (!iconLoader) {
iconLoader = new AsyncLazy<string>(() => loadSvg(name, loaders));
LazyIconCache.set(name, iconLoader);
}
const icon = await iconLoader.getValue();
return icon;
}
async function loadSvg(name: IconName, loaders: FileLoaders): Promise<string> {
const iconPath = `/assets/icons/${name}.svg`;
const loader = loaders[iconPath];
if (!loader) {
throw new Error(`missing icon for "${name}" in "${iconPath}"`);
}
const svgContent = await loader();
const modifiedContent = modifySvg(svgContent);
return modifiedContent;
}
const RawSvgLoaders = import.meta.glob('@/presentation/assets/icons/**/*.svg', {
as: 'raw', // This will load the SVG file content as a string.
/*
Using `eager: true` to preload all icons.
Pros:
- Speed: Icons are instantly accessible post-initial load.
Cons:
- Increased initial load time due to preloading of all icons.
- Increased bundle size.
*/
eager: false,
});
function modifySvg(svgSource: string): string {
const parser = new window.DOMParser();
const doc = parser.parseFromString(svgSource, 'image/svg+xml');
let svgRoot = doc.documentElement;
svgRoot = removeSvgComments(svgRoot);
svgRoot = fillSvgCurrentColor(svgRoot);
return new XMLSerializer()
.serializeToString(svgRoot);
}
function removeSvgComments(svgRoot: HTMLElement): HTMLElement {
const comments = Array.from(svgRoot.childNodes).filter(
(node) => node.nodeType === Node.COMMENT_NODE,
);
for (const comment of comments) {
svgRoot.removeChild(comment);
}
Array.from(svgRoot.children).forEach((child) => {
removeSvgComments(child as HTMLElement);
});
return svgRoot;
}
function fillSvgCurrentColor(svgRoot: HTMLElement): HTMLElement {
svgRoot.querySelectorAll('path').forEach((el: Element) => {
el.setAttribute('fill', 'currentColor');
});
return svgRoot;
}

View File

@@ -36,11 +36,11 @@ export default defineComponent({
}, },
emits: { emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
input: (isOpen: boolean) => true, 'update:modelValue': (isOpen: boolean) => true,
/* eslint-enable @typescript-eslint/no-unused-vars */ /* eslint-enable @typescript-eslint/no-unused-vars */
}, },
props: { props: {
value: { modelValue: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
@@ -67,13 +67,13 @@ export default defineComponent({
onModalFullyTransitionedOut(() => { onModalFullyTransitionedOut(() => {
isRendered.value = false; isRendered.value = false;
resetTransitionStatus(); resetTransitionStatus();
if (props.value) { if (props.modelValue) {
emit('input', false); emit('update:modelValue', false);
} }
}); });
watchEffect(() => { watchEffect(() => {
if (props.value) { if (props.modelValue) {
open(); open();
} else { } else {
close(); close();
@@ -99,8 +99,8 @@ export default defineComponent({
isOpen.value = false; isOpen.value = false;
if (props.value) { if (props.modelValue) {
emit('input', false); emit('update:modelValue', false);
} }
} }
@@ -115,8 +115,8 @@ export default defineComponent({
isOpen.value = true; isOpen.value = true;
}); });
if (!props.value) { if (!props.modelValue) {
emit('input', true); emit('update:modelValue', true);
} }
} }

View File

@@ -18,7 +18,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'; import { defineComponent, shallowRef } from 'vue';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -31,7 +31,7 @@ export default defineComponent({
'transitionedOut', 'transitionedOut',
], ],
setup(_, { emit }) { setup(_, { emit }) {
const modalElement = ref<HTMLElement>(); const modalElement = shallowRef<HTMLElement>();
function onAfterTransitionLeave() { function onAfterTransitionLeave() {
emit('transitionedOut'); emit('transitionedOut');

View File

@@ -10,9 +10,7 @@
class="dialog__close-button" class="dialog__close-button"
@click="hide" @click="hide"
> >
<font-awesome-icon <AppIcon icon="xmark" />
:icon="['fas', 'times']"
/>
</div> </div>
</div> </div>
</ModalContainer> </ModalContainer>
@@ -20,29 +18,31 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import ModalContainer from './ModalContainer.vue'; import ModalContainer from './ModalContainer.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
ModalContainer, ModalContainer,
AppIcon,
}, },
emits: { emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
input: (isOpen: boolean) => true, 'update:modelValue': (isOpen: boolean) => true,
/* eslint-enable @typescript-eslint/no-unused-vars */ /* eslint-enable @typescript-eslint/no-unused-vars */
}, },
props: { props: {
value: { modelValue: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const showDialog = computed({ const showDialog = computed({
get: () => props.value, get: () => props.modelValue,
set: (value) => { set: (value) => {
if (value !== props.value) { if (value !== props.modelValue) {
emit('input', value); emit('update:modelValue', value);
} }
}, },
}); });

View File

@@ -1,13 +1,14 @@
<template> <template>
<div ref="containerElement" class="container"> <div ref="containerElement" class="container">
<slot ref="containerElement" /> <slot />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import {
defineComponent, ref, onMounted, onBeforeUnmount, defineComponent, shallowRef, onMounted, onBeforeUnmount,
} from 'vue'; } from 'vue';
import { useResizeObserverPolyfill } from '@/presentation/components/Shared/Hooks/UseResizeObserverPolyfill';
export default defineComponent({ export default defineComponent({
emits: { emits: {
@@ -18,18 +19,22 @@ export default defineComponent({
/* eslint-enable @typescript-eslint/no-unused-vars */ /* eslint-enable @typescript-eslint/no-unused-vars */
}, },
setup(_, { emit }) { setup(_, { emit }) {
const containerElement = ref<HTMLElement>(); const { resizeObserverReady } = useResizeObserverPolyfill();
const containerElement = shallowRef<HTMLElement>();
let width = 0; let width = 0;
let height = 0; let height = 0;
let observer: ResizeObserver; let observer: ResizeObserver;
onMounted(async () => { onMounted(() => {
width = containerElement.value.offsetWidth; width = containerElement.value.offsetWidth;
height = containerElement.value.offsetHeight; height = containerElement.value.offsetHeight;
observer = await initializeResizeObserver(updateSize); resizeObserverReady.then(() => {
observer.observe(containerElement.value); observer = new ResizeObserver(updateSize);
observer.observe(containerElement.value);
});
fireChangeEvents(); fireChangeEvents();
}); });
@@ -38,16 +43,6 @@ export default defineComponent({
observer?.disconnect(); observer?.disconnect();
}); });
async function initializeResizeObserver(
callback: ResizeObserverCallback,
): Promise<ResizeObserver> {
if ('ResizeObserver' in window) {
return new window.ResizeObserver(callback);
}
const module = await import('@juggle/resize-observer');
return new module.ResizeObserver(callback);
}
function updateSize() { function updateSize() {
let sizeChanged = false; let sizeChanged = false;
if (isWidthChanged()) { if (isWidthChanged()) {

View File

@@ -24,10 +24,11 @@
<script lang="ts"> <script lang="ts">
import { import {
useFloating, arrow, shift, flip, Placement, offset, Side, Coords, useFloating, arrow, shift, flip, Placement, offset, Side, Coords, autoUpdate,
} from '@floating-ui/vue'; } from '@floating-ui/vue';
import { defineComponent, ref, computed } from 'vue'; import { defineComponent, shallowRef, computed } from 'vue';
import type { CSSProperties } from 'vue/types/jsx'; // In Vue 3.0 import from 'vue' import { useResizeObserverPolyfill } from '@/presentation/components/Shared/Hooks/UseResizeObserverPolyfill';
import type { CSSProperties } from 'vue';
const GAP_BETWEEN_TOOLTIP_AND_TRIGGER_IN_PX = 2; const GAP_BETWEEN_TOOLTIP_AND_TRIGGER_IN_PX = 2;
const ARROW_SIZE_IN_PX = 4; const ARROW_SIZE_IN_PX = 4;
@@ -35,16 +36,18 @@ const MARGIN_FROM_DOCUMENT_EDGE_IN_PX = 2;
export default defineComponent({ export default defineComponent({
setup() { setup() {
const tooltipDisplayElement = ref<HTMLElement | undefined>(); const tooltipDisplayElement = shallowRef<HTMLElement | undefined>();
const triggeringElement = ref<HTMLElement | undefined>(); const triggeringElement = shallowRef<HTMLElement | undefined>();
const arrowElement = ref<HTMLElement | undefined>(); const arrowElement = shallowRef<HTMLElement | undefined>();
const placement = ref<Placement>('top'); const placement = shallowRef<Placement>('top');
useResizeObserverPolyfill();
const { floatingStyles, middlewareData } = useFloating( const { floatingStyles, middlewareData } = useFloating(
triggeringElement, triggeringElement,
tooltipDisplayElement, tooltipDisplayElement,
{ {
placement: ref(placement), placement,
middleware: [ middleware: [
offset(ARROW_SIZE_IN_PX + GAP_BETWEEN_TOOLTIP_AND_TRIGGER_IN_PX), offset(ARROW_SIZE_IN_PX + GAP_BETWEEN_TOOLTIP_AND_TRIGGER_IN_PX),
/* Shifts the element along the specified axes in order to keep it in view. */ /* Shifts the element along the specified axes in order to keep it in view. */
@@ -56,6 +59,7 @@ export default defineComponent({
flip(), flip(),
arrow({ element: arrowElement }), arrow({ element: arrowElement }),
], ],
whileElementsMounted: autoUpdate,
}, },
); );
const arrowStyles = computed<CSSProperties>(() => { const arrowStyles = computed<CSSProperties>(() => {
@@ -101,9 +105,8 @@ function getArrowPositionStyles(
} else if (y) { // either X or Y is calculated } else if (y) { // either X or Y is calculated
style.top = `${y}px`; style.top = `${y}px`;
} }
const oppositeSide = getCounterpartBoxOffsetProperty(placement) as never; const oppositeSide = getCounterpartBoxOffsetProperty(placement);
// Cast to `never` due to ts(2590) from JSX import. Remove after migrating to Vue 3.0. style[oppositeSide.toString()] = `-${ARROW_SIZE_IN_PX}px`;
style[oppositeSide] = `-${ARROW_SIZE_IN_PX}px`;
return style; return style;
} }

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