Compare commits

..

62 Commits

Author SHA1 Message Date
undergroundwires
a14929a13c Migrate to ES6 modules
Configure project to use ES6 modules to enable top-level await
capabilities. This change helps project to align well with modern JS
standards.

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

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

Benefits with this approach include:

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

Other changes:

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

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

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

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

Other changes:

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

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

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

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

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

The reasons include:

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

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

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

`rel="noopener"`:

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

`rel="noreferrer"`:

  It implies features of `noopener`, and also prevents `Referer` header
  from being sent to the new page. Referer headers may include
  sensitive data, because they tell the new page the URL of the page
  the request is coming from.
2023-08-06 02:09:11 +02:00
undergroundwires
ff84f5676e Transition to eslint-config-airbnb-with-typescript
- Migrate to newer `eslint-config-airbnb-with-typescript` from
  `eslint-config-airbnb`.
- Add also `rushstack/eslint-patch` as per instructed by
  `eslint-config-airbnb-with-typescript` docs.
- Update codebase to align with new linting standards.
- Add script to configure VS Code for effective linting for project
  developers, move it to `scripts` directory along with clean npm
  install script for better organization.
2023-08-04 16:39:36 +02:00
undergroundwires-bot
4d0ce12c96 ⬆️ bump everywhere to 0.12.0 2023-08-03 18:22:36 +00:00
undergroundwires
298b058e5c win: add new scripts to disable more telemetry
- Add new scripts under "Disable Windows telemetry and data collection".
- Update script names and documentations to align with Microsoft's
  latest branding for telemetry.
- Introduce broader configurability to minimize data collection.
- Add missing revert code to allow the reversion of changes, increasing
  flexibility and safety.
- Include comprehensive documentation to provide more context and
  understanding for users.
2023-08-03 17:17:56 +02:00
undergroundwires
1e80ee1fb0 Change subtitle heading to new slogan
- Unify reading subtitle/slogan throughout the application.
- Refactor related unit tests for easier future changes.
- Add typed constants for Vue app environment variables.
2023-08-01 17:50:36 +02:00
undergroundwires
5901dc5f11 Fix macOS desktop build failure in CI
The GitHub-hosted runners began experiencing issues while building macOS
desktop distributions, exclusively affecting the macOS environment.
The Ubuntu and Windows environments remained unaffected.

The logs highlighted the absence of Python in the macOS environment, which
resulted in build failure:

```sh
  Error: Exit code: ENOENT. spawn /usr/bin/python ENOENT
```

Since the `electron-builder` package uses Python scripts to create DMG
disk images for macOS distributions, Python is needed for building the
application. However, electron-builder uses Python 2.X meanwhile modern
macOS versions have removed Python 2.X from the operating system on
default installation.

Although this issue was resolved in `electron-builder` version 23,,
`vue-cli-plugin-electron-builder` continues to use version 22. Due to a
lack of maintenance, the package is unlikely to receive updates.

This commit forces `vue-cli-plugin-electron-builder` to use the latest
`electron-builder` which resolves the macOS distribution build failure.

In CI process, GitHub-hosted runners start to fail when building macOS
desktop distributions. It is only observered in the macOS environment
while the application is built successfully in both the Ubuntu and
Windows environments.

The error message in the logs indicated that Python was not found in the
macOS environment:

```sh
Error: Exit code: ENOENT. spawn /usr/bin/python ENOENT
```

`electron-builder` package uses Python scripts for certain operations,
specifically for creating DMG disk images for macOS distributions. As a
result, Python is a necessary dependency when building the application
for macOS.

`electron-builder` has fixed this starting from version 23, but
vue-cli-plugin-electron-builder still refers to version 22 and it is
unmaintained and not likely to get updates.

The solution is to add a step in the GitHub Actions workflow to set up
Python in the macOS environment. `actions/setup-python` sets up the
Python environment in the runner if the OS is macOS.

This change does not impact the Ubuntu and Windows environments as the
setup-python step is conditionally executed only for macOS. The addition
of Python to the macOS environment in CI process has resolved the build
failure issue for the macOS distribution.

See also:

- electron-userland/electron-builder#6606
- electron-userland/electron-builder#6726
- electron-userland/electron-builder#6732
- nklayman/vue-cli-plugin-electron-builder#1691
- nklayman/vue-cli-plugin-electron-builder#1701
2023-08-01 01:42:53 +02:00
undergroundwires
5721796378 Update dependencies and add npm setup script
- Introduce `fresh-npm-install.sh` to automate clean npm environment
  setup.
- Revert workaround 924b326244, resolved
  by updating Font Awesome.
- Remove `vue-template-compiler` and `@vue/test-utils` from
  dependencies, they're obsolete in 2.7.
- Update anchor references to start with lower case in line with
  MD051/link-fragments, introduced by updated `markdownlint`.
- Upgrade cypress to > 10, which includes:
  - Change spec extensions from `*.spec.js` to `*.cy.js`.
  - Change configuration file from `cypress.json` to
    `cypress.config.ts`.
  - Remove most configurations from `cypress/plugins/index.js`. These
    configurations were initially generated by Vue CLI but obsoleted in
    newer cypress versions.
- Lock Typescript version to 4.6.x due to lack of support in
  unmaintained Vue CLI TypeScript plugin (see vuejs/vue-cli#7401).
- Use `setWindowOpenHandler` on Electron, replacing deprecated
  `new-event` event.
- Document inability to upgrade `typescript-eslint` dependencies because
  `@vue/eslint-config-typescript` does not support them. See
   vuejs/eslint-config-typescript#60, vuejs/eslint-config-typescript#59,
   vuejs/eslint-config-typescript#57.
- Fix `typescript` version to 4.6.X and `tslib` version to 2.4.x,
  unit tests exit with a maximum call stack size exceeded error:

  ```
    ...
    MOCHA  Testing...
    RUNTIME EXCEPTION  Exception occurred while loading your tests
    [=========================] 100% (completed)
    RangeError: Maximum call stack size exceeded
      at RegExp.exec (<anonymous>)
      at retrieveSourceMapURL (/project/node_modules/source-map-support/source-map-support.js:174:21)
      at Array.<anonymous> (/project/node_modules/source-map-support/source-map-support.js:186:26)
      at /project/node_modules/source-map-support/source-map-support.js:85:24
      at mapSourcePosition (/project/node_modules/source-map-support/source-map-support.js:216:21)
    ...
  ```

  Issue has been reported but not fixed, suggested solutions did not
  work, see evanw/node-source-map-support#252.
- Update `vue-cli-plugin-electron-builder` to latest alpha version. This
  allows upgrading `ts-loader` to latest and using latest
  `electron-builder`. Change `main` property value in `package.json` to
  `index.js` for successful electron builds (see
  nklayman/vue-cli-plugin-electron-builder#188).
2023-08-01 00:24:06 +02:00
undergroundwires
8b374a37b4 mac: add script to disable personalized ads 2023-07-31 17:59:23 +02:00
undergroundwires
c404dfebe2 Add initial Linux support #150
Key features of Linux support:
- It supports python 3 scripts execution.
- It supports Flatpak and Snap installation for software
  clean-up/configurations.
- Extensive documentation.
2023-07-30 22:54:24 +02:00
undergroundwires
e8199932b4 Relax and improve code validation
Rework code validation to be bound to a context and not
context-independent. It means that the generated code is validated based
on different phases during the compilation. This is done by moving
validation from `ScriptCode` constructor to a different callable
function.

It removes duplicate detection for function calls once a call is fully
compiled, but still checks for duplicates inside each function body that
has inline code. This allows for having duplicates in final scripts
(thus relaxing the duplicate detection), e.g., when multiple calls to
the same function is made.

It fixes non-duplicates (when using common syntax) being misrepresented
as duplicate lines.

It improves the output of errors, such as printing valid lines, to give
more context. This improvement also fixes empty line validation not
showing the right empty lines in the error output. Empty line validation
shows tabs and whitespaces more clearly.

Finally, it adds more tests including tests for existing logic, such as
singleton factories.
2022-10-29 20:03:06 +02:00
undergroundwires
f4a7ca76b8 Rework icon with higher quality and new color
Change icon color to match the primary color of the theme (i.e.,
`#3a65ab`). The new color looks good on both dark and light surfaces
which solves #155.

Introduce SVG logo instead of PNG for better quality and scalability.

Improve icon creation. Introduce an automated script to create different
logo formats in different sizes enabling easier update of logo from
single place.
2022-10-13 21:26:30 +02:00
undergroundwires
64cca1d9b8 mac: add scripts to configure Parallels Desktop 2022-10-12 21:43:17 +02:00
undergroundwires
68a5d698a2 Add support for nested templates
Add support for expressions inside expressions.

Add support for templating where the output of one expression results in
another template part with expressions.

E.g., this did not work before, but compilation will now evaluate both
with expression with `$condition` and parameter substitution with
`$text`:

```
{{ with $condition }}
  echo '{{ $text }}'
{{ end }}
```

Add also more sanity checks (validation logic) when compiling
expressions to reveal problems quickly.
2022-10-11 20:42:38 +02:00
undergroundwires
bf0c55fa60 Drop support for dead browsers
It reduces distribution size to half of the size (50%) from 17.4 MB to
8.75 MB.

It follows new Vue default configuration vuejs/vue-cli#5232.

It drops support for `baidu 13.18`, `ie 11`, `ie 10`,  `bb 10`, `bb 7`,
`ie_mob 11`, `ie_mob 10`, `op_mob 12.1` as reported by
`npx browserslist`.
2022-10-09 22:38:35 +02:00
undergroundwires
e7b816d156 win: add scripts to downloaded file handling #153 2022-10-05 22:11:18 +02:00
Brice Dobson
a2e092190d win: add script to increase RSA key exchange #165
Add script to increase RSA key exchange to 2048-bit for ISS

Co-authored-by: undergroundwires <git@undergroundwires.dev>
2022-10-04 20:27:02 +02:00
undergroundwires
c1c2f2925f Break line in inline codes in documentation
It fixes the behavior where the whole text goes outside of the screen
(overflows) due to inline code not breaking.
2022-10-03 21:25:57 +02:00
undergroundwires
e8d06e0f3e Add multiline support for with expression
Improve templating support for block rendering for `with` expression
that has multiline code. This improves templating support to render
multiline code conditionally.

This did not work before but works now:

```
{{ with $middleLine }}
  first line
  second line
{{ end }}
```
2022-10-02 20:12:49 +02:00
undergroundwires
7d3670c26d Improve manual execution instructions
- Simplify alternatives to run the script.
- Change style of code parts to be easier to the eye:
  - Use the same font size as other texts in body.
  - Add vertical padding.
  - Align the contents (the code, copy button and dollar sign) in the
    middle.
  - Align information icon to center of context next to it.
- Fix minor typos and punctations.
- Refactor instruction list to be more generic to able to be used by
  other operating systems.
- Make dialogs scrollable so instruction list on smaller screens can be
  read until the end.
2022-10-01 19:50:45 +02:00
undergroundwires
430537f704 Use lowercase in script names and search text
Remove uppercase text transformation from node titles (script and
categories) and "no results found" text when searching. It increases the
readability giving it a clean look.
2022-09-30 19:50:46 +02:00
undergroundwires
58ed7b456b win: improve OneDrive removal
- Improve documentation for OneDrive removal scripts.
- Add support for deleting OneDrive icon from the navigation pane.
- Do not revert OneDrive install code on Windows 11 as it does not exist
  by default.
- Remove "Prevent automatic OneDrive install for new users" script as
  HKU scripts are not really supported elsewhere and makes the code
  harder to maintain.
- Do not print errors when the behavior is as expected. Surpress errors
  on registry key deletion, ensure re-running script does not cause any
  errors with proper checks.
- Change revert logic to match default Windows state.
- Hardcode service names for OneDrive to avoid side-effects.
- Rerruning OneDrive now runs it in background.
- Add Windows 11 support for running the installer/uninstaller.
- Rename scripts to simpler and easier-to-understand names
2022-09-29 23:27:46 +02:00
undergroundwires
6b3f4659df Use line endings based on script language #88
Use CRLF in batchfile and LF in shellscript.
2022-09-28 23:09:23 +02:00
undergroundwires
bbc6156281 win: add script to remove Widgets 2022-09-27 17:36:14 +02:00
undergroundwires
df533ad3b1 win: add more Visual Studio scripts, support 2022
Improve documentation for Visual Studio scripts.

Add different keys reported by community for deleting Visual Studio 2022
licenses, see beatcracker/VSCELicense#14 for the key reports.

Add cleanup for SQM files that Visual Studio generates when it is unable
to connect to internet, to send the data when online. Improve cleanup
for Visual Studio logs.

Change revert behavior of the scripts to match default state of clean
Visual Studio installation.
2022-09-26 21:37:32 +02:00
undergroundwires
6067bdb24e Improve documentation support with markdown
Rework documentation URLs as inline markdown.

Redesign documentations with markdown text.

Redesign way to document scripts/categories and present the
documentation.

Documentation is showed in an expandable box instead of tooltip. This is
to allow writing longer documentation (tooltips are meant to be used for
short text) and have better experience on mobile.

If a node (script/category) has documentation it's now shown with single
information icon (ℹ) aligned to right.

Add support for rendering documentation as markdown. It automatically
converts plain URLs to URLs with display names (e.g.
https://docs.microsoft.com/..) will be rendered automatically like
"docs.microsoft.com - Windows 11 Privacy...".
2022-09-25 23:25:43 +02:00
undergroundwires
924b326244 Fix broken npm installation and builds
This commit fixes two issues:
a. Fix `npm install` not working
b. Fix building not working after npm install fix.

npm install fails with dependency resolution issue due to Vue CLI as
following:

```txt
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: cache-loader@4.1.0
...
```txt

As suggested in Vue issues regenerating packages-lock.json solves the
issue, see: vuejs/vue-cli#6793, vuejs/vue-cli#7095.

However with the new package-lock.json a Font Awesome dependency issue
breaks the builds such as the following:

```txt
ERROR in src/presentation/bootstrapping/Modules/IconBootstrapper.ts:17:7
TS2345: Argument of type 'IconDefinition' is not assignable to parameter of type 'IconDefinitionOrPack'.
  Type 'IconDefinition' is not assignable to type 'IconPack'.
    Index signature for type 'string' is missing in type 'IconDefinition'.
    15 |   public bootstrap(vue: VueConstructor): void {
    16 |     library.add(
  > 17 |       faGithub,
```

This is solved by adding a patch in `tsconfig.json`. This issue was
discussed in FortAwesome/Font-Awesome#12575 where the workaround was
recommended.
2022-09-22 20:13:20 +02:00
undergroundwires
8608072bfb Align card icons vertically in cards view
Align folder and battery icons to be on same vertical line on every
card.
2022-03-14 18:30:05 +01:00
undergroundwires
3233d9b802 Improve click/touch without unintended interaction
Disable selecting clickables as text. Selecting buttons leads to
unintended selection. This is seen when touching on clickables using
mobile devices.

Prevent blue highlight when touching on clickables. This is seen on
mobile webkit browsers. It looks ugly and the visual clue provided is
not needed beacuse all clickables on mobile already  have visual clues.
2022-03-13 12:45:17 +01:00
undergroundwires
99e24b4134 Improve touch like hover on devices without mouse
This commit improves mobile support. `:hover` CSS selector is not mobile
friendly because there is typically no mouse support on mobile. This
commit make hover behavior to become active during touch on mobile.

`:hover` selector is emulated on mobile devices. But this emulated
behavior is not desired. When emulated, the CSS style gets attached when
starting touching but does not get removed after stopping touching. This
sticky behavior is undesired.

This commit solve this issue by using Saas mixing that uses `:active`
selector instead of `:hover` when `:hover` is not really supported but
emulated.
2022-03-12 10:59:28 +01:00
undergroundwires
b210aaddf2 Improve script/category name validation
- Use better error messages with more context.
- Unify their validation logic and share tests.
- Validate also type of the name.
- Refactor node (Script/Category) parser tests for easier future
  changes and cleaner test code (using `TestBuilder` to do dirty work in
  unified way).
- Add more tests. Custom `Error` properties are compared manually due to
  `chai` not supporting deep equality checks (chaijs/chai#1065,
  chaijs/chai#1405).
2022-03-11 09:56:50 +01:00
undergroundwires-bot
65902e5b72 ⬆️ bump everywhere to 0.11.4 2022-03-08 17:32:34 +00:00
undergroundwires
efd63ff85d Bump dependencies to latest
Purge unused dependencies.

Update dependencies to latest except:

- ts-lint. Keep locked to 9.0.1 because that's the latest version that
  works with Webpack 4 that's still used by
  vue-cli-plugin-electron-builder.
- Keep eslint at version 7 because tests cannot be run/compiled with
  version 7, see eslint/eslint#15678, vuejs/vue-cli#6759.

Newer versions of ESLint modules do not allow linebreak after or before
= operator (operator-linebreak). This commit also changes files to
comply with it.

Closes #116, #119, #122, #130.
2022-03-08 18:03:19 +01:00
undergroundwires
242a497e7d Bump node environment to 16.x
- Bump setup-node action to v2.
- Use composite actions to reuse same setting. This is preferred over
  reusable templates because reusable templates are on job-level but
  setting up node should be a step.
2022-03-07 21:38:30 +01:00
undergroundwires
05a6a84c37 Add donation information 2022-03-05 13:10:27 +01:00
undergroundwires
112e79a64c Fix Microsoft Defender alert for uninstaller #114
Microsoft Defender considers the uninstaller virus. It's a
false-psoitive caused by `electron-builder` used to build NSIS package.

This commit solves the issue by explicitly adding `electron-builder` as
dependency. This way, `vue-cli-plugin-electron-builder` always resolves
to the desired version. Now the version used for `electron-builder` is
more controlled. New uninstaller generated by latest specified patch
does not trigger an alert, which solves the false-positive issue.

See also:
  - electron-userland/electron-builder#4793,
  - electron-userland/electron-builder#4878.
2022-02-27 14:55:11 +01:00
undergroundwires
eeb1d5b0c4 Refactor to use version object #59
Enable writing safer version aware logic.
2022-02-26 17:15:30 +01:00
undergroundwires
d6bc33ec86 Fix Windows 11 being detected as Windows 10
The logic was expecting major version in Windows 11 to be 11. However,
in Windows 11, major version is not changed and it is till 10. This
commit corrects logic to check build number that's guaranteed to be
higher in Windows 11.
2022-02-23 06:52:46 +01:00
undergroundwires
956052c8ff Fix error when reverting Windows Defender setting 2022-02-21 17:20:34 +01:00
undergroundwires
3785e410db Document WpnService breaking on Windows 10 #110 2022-02-20 15:44:08 +01:00
undergroundwires
481a02afd5 Refactor to remove hardcoding of aliases
Unify definition of  aliases in single place.

Make TypeScript configuration file (`tsconfig.json`) source of truth
regarding aliases.

Both webpack (through `vue.config.js`) and ESLint (through
`.eslintrc.js`) now reads the alias configuration from `tsconfig.json`.
2022-02-02 19:28:12 +01:00
undergroundwires
5bbbb9cecc Refactor to remove code coupling with Webpack
Remove using Webpack import syntax such as: `js-yaml-loader!@/..`. It's
a non-standard syntax that couples the code to Webpack.

Configure instead by specifying Webpack loader in Vue configuration
file.

Enable related ESLint rules.

Remove unused dependency `raw-loader` and refactor
`NoUnintendedInlining` test to load files using file system (dropping
webpack dependency).

Refactor to use `import type` for type imports to show the indent
clearly and satisfy failing ESLint rules.
2022-01-31 17:22:34 +01:00
undergroundwires
db47440d47 Improve existing documentation
Improve documentation for different markdown documentation files. Goal
is to simplify and make the language and content more clear.
2022-01-30 13:15:34 +01:00
undergroundwires
1bcc6c8b2b Improve documentation for architecture
- Simplify `README.md` by creating and moving some documentation to
`architecture.md`.
- Add more documentation for state handling between layers.
- Improve some documentation to use clearer language.
2022-01-29 15:47:44 +01:00
undergroundwires
3c3ec80525 Improve documentation for developing
Move existing documentation to `docs/development.md` to have simpler
`README.md` but more dedicated and extensive documentation for
development.

Improve existing documentation for different commands for the project.

Document VSCode recommendations in `extensions.json` file and add
exception in `.gitignore` to be able to add it to repository.
2022-01-26 08:14:25 +01:00
undergroundwires
803ef2bb3e Move stubs from ./stubs to ./shared/Stubs
Gathers all shared test code in single place.
2022-01-25 08:37:03 +01:00
undergroundwires
43ce834750 Fix Windows DoSvc not being disabled #115
- Disable DoSvc using registry to support newer Windows versions.
- Add more documentation for DoSvc.
2022-01-22 23:57:57 +01:00
undergroundwires
44d79e2c9a Add more and unify tests for absent object cases
- Unify test data for nonexistence of an object/string and collection.
- Introduce more test through adding missing test data to existing tests.
- Improve logic for checking absence of values to match tests.
- Add missing tests for absent value validation.
- Update documentation to include shared test functionality.
2022-01-21 22:34:11 +01:00
undergroundwires
0e52a99efa Transpile dependencies for wider browser support
This commit ensures that all dependencies in `node_modules` will be
transpiled by Babel.

Dependencies are not transpiled by babel as default. `babel-loader`
ignores all files inside `node_modules`.

Not using it may allow packages using newer JavaScript (such as ES6) to
cause unintended crashed on older browsers.

This configuration is the default in projects created by newer versions
of Vue CLI.
2022-01-20 20:07:49 +01:00
undergroundwires
834ce8cf9e Add AirBnb TypeScript overrides for linting
AirBnb only imports JavaScript rules and some fail for TypeScript files.
This commit overrides those rules with TypeScript equivalents.

Changes here can be mostly replaced when Vue natively support TypeScript
for Airbnb (vuejs/eslint-config-airbnb#23).

Enables @typescript-eslint/indent even though it's broken and it will
not be fixed typescript-eslint/typescript-eslint#1824 until prettifier
is used, because it is still useful.

Change broken rules with TypeScript variants:
  - `no-useless-constructor`
      eslint/eslint#14118
      typescript-eslint/typescript-eslint#873
  - `no-shadow`
      eslint/eslint#13044
      typescript-eslint/typescript-eslint#2483
      typescript-eslint/typescript-eslint#325
      typescript-eslint/typescript-eslint#2552
      typescript-eslint/typescript-eslint#2484
      typescript-eslint/typescript-eslint#2466
2022-01-19 22:28:33 +01:00
undergroundwires
2354f0ba9f Fix reverting of Windows NVIDIA telemetry service
- Fix revert logic deleting the service instead of enabling it.
- Use unified "DisableService" function to improve enable/disable logic.
- Separate disabling of service from opting out.
- Add documentation reference.
2022-01-18 21:23:30 +01:00
undergroundwires
8e96c19126 Improve performance of selecting scripts
Increase performance by only notifying GUI about changes in selection
when there really is a change. It removes extra processing from all
event listeners that act on selection state change.
2022-01-07 22:06:05 +01:00
undergroundwires-bot
99fb4c73f5 ⬆️ bump everywhere to 0.11.3 2022-01-06 20:53:52 +00:00
405 changed files with 33800 additions and 23887 deletions

View File

@@ -1,2 +1,3 @@
> 1%
last 2 versions
not dead

View File

@@ -1,4 +1,4 @@
[*.{js,jsx,ts,tsx,vue}]
[*.{js,jsx,ts,tsx,vue,sh}]
indent_style = space
indent_size = 2
end_of_line = lf

1
.eslintignore Normal file
View File

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

View File

@@ -1,4 +1,6 @@
const { rules: baseStyleRules } = require('eslint-config-airbnb-base/rules/style');
const tsconfigJson = require('./tsconfig.json');
require('@rushstack/eslint-patch/modern-module-resolution');
module.exports = {
root: true,
@@ -11,9 +13,7 @@ module.exports = {
'plugin:vue/essential',
// Extends eslint-config-airbnb
// Added by Vue CLI
// Here until https://github.com/vuejs/eslint-config-airbnb/issues/23 is done
'@vue/airbnb',
'@vue/eslint-config-airbnb-with-typescript',
// Extends @typescript-eslint/recommended
// Uses the recommended rules from the @typescript-eslint/eslint-plugin
@@ -21,7 +21,14 @@ module.exports = {
'@vue/typescript/recommended',
],
parserOptions: {
ecmaVersion: 'latest',
ecmaVersion: 12, // ECMA 2021
/*
Having 'latest' leads to:
```
Parsing error: ecmaVersion must be a number. Received value of type string instead
```
For .js files in the project
*/
},
rules: {
...getOwnRules(),
@@ -59,9 +66,8 @@ function getOwnRules() {
groups: [ // Enforce more strict order than AirBnb
'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
pathGroups: [ // Fix manually configured paths being incorrectly grouped as "external"
'@/**', // @/..
'@tests/**', // @tests/.. (not matching anything after @** because there can be third parties as well)
'js-yaml-loader!@/**', // E.g. js-yaml-loader!@/..
...getAliasesFromTsConfig(),
'js-yaml-loader!@/**',
].map((pattern) => ({ pattern, group: 'internal' })),
},
],
@@ -71,10 +77,6 @@ function getOwnRules() {
function getTodoRules() { // Should be worked on separate future commits
return {
'import/no-extraneous-dependencies': 'off',
// Requires webpack configuration change with import '..yaml' files.
'import/no-webpack-loader-syntax': 'off',
'import/extensions': 'off',
'import/no-unresolved': 'off',
// Accessibility improvements:
'vuejs-accessibility/form-control-has-label': 'off',
'vuejs-accessibility/click-events-have-key-events': 'off',
@@ -95,6 +97,7 @@ function getOpinionatedRuleOverrides() {
return {
// https://erkinekici.com/articles/linting-trap#no-use-before-define
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
// https://erkinekici.com/articles/linting-trap#arrow-body-style
'arrow-body-style': 'off',
// https://erkinekici.com/articles/linting-trap#no-plusplus
@@ -113,3 +116,8 @@ function getOpinionatedRuleOverrides() {
],
};
}
function getAliasesFromTsConfig() {
return Object.keys(tsconfigJson.compilerOptions.paths)
.map((path) => `${path}*`);
}

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: undergroundwires

View File

@@ -21,8 +21,9 @@ A clear and concise description of what the bug is.
<!--
Which OS are you using? What version of OS you were using?
On Windows you can find it using "Start button" > "Settings" > "System" > "About".
On macOS you can find it using "Apple menu (top left corner)" > "About This Mac".
On Windows: Open "Start button" > "Settings" > "System" > "About".
On macOS: Open "Apple menu (top left corner)" > "About This Mac".
On Linux: Open terminal > type: lsb_release -a > copy paste the result.
-->
### Reproduction steps

View File

@@ -14,7 +14,7 @@ You could alternatively send a PR directly (see CONTRIBUTING.md).
<!--
Which OS will the new script configure?
Either "Windows" or "macOS".
One of the supported OSes: "Windows", "macOS" or "Linux".
-->
### Name
@@ -30,10 +30,12 @@ E.g. "Disable webcam telemetry"
<!--
Code that will be executed when script is selected.
Try to keep it as simple and backwards-compatible as possible.
Allowed languages:
- macOS: bash (sh)
Allowed languages:
- Windows: PowerShell (ps1) or batchfile
- 💡 Prioritize the one that's simpler, batchfile if similar.
- macOS: bash (sh)
- Linux: bash (sh) or Python 3
- 💡 Prioritize the one that's simpler, bash if similar.
-->
### Revert code

8
.github/actions/setup-node/action.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
runs:
using: composite
steps:
-
name: Setup node
uses: actions/setup-node@v2
with:
node-version: 16.x

View File

@@ -18,9 +18,7 @@ jobs:
uses: actions/checkout@v2
-
name: Setup node
uses: actions/setup-node@v1
with:
node-version: 15.x
uses: ./.github/actions/setup-node
-
name: Install dependencies
run: npm ci
@@ -42,9 +40,7 @@ jobs:
uses: actions/checkout@v2
-
name: Setup node
uses: actions/setup-node@v1
with:
node-version: 15.x
uses: ./.github/actions/setup-node
-
name: Install dependencies
run: npm ci
@@ -57,3 +53,23 @@ jobs:
run: |-
cross-env-shell NODE_ENV=${{ matrix.mode }}
npm run electron:build -- --publish never --mode ${{ matrix.mode }}
create-icons:
strategy:
matrix:
os: [ macos, ubuntu, windows ]
fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Setup node
uses: ./.github/actions/setup-node
-
name: Install dependencies
run: npm ci
-
name: Create icons
run: npm run create-icons

View File

@@ -19,9 +19,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: 15.x
uses: ./.github/actions/setup-node
- name: Install dependencies
run: npm ci
- name: Lint

View File

@@ -16,9 +16,7 @@ jobs:
uses: actions/checkout@v2
-
name: Setup node
uses: actions/setup-node@v1
with:
node-version: 15.x
uses: ./.github/actions/setup-node
-
name: NPM audit
run: exit "$(npm audit)" # Since node 15.x, it does not fail with error if we don't explicitly exit
run: npm audit --omit=dev

View File

@@ -20,9 +20,7 @@ jobs:
- name: Checkout to bump commit
run: git checkout "$(git rev-list "${{ github.event.release.tag_name }}"..master | tail -1)"
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: 15.x
uses: ./.github/actions/setup-node
- name: Install dependencies
run: npm ci
- name: Run unit tests
@@ -31,4 +29,4 @@ jobs:
run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EP_GH_IGNORE_TIME: true # Otherwise publishing fails if GitHub release is more than 2 hours old https://github.com/electron-userland/electron-builder/issues/2074
EP_GH_IGNORE_TIME: true # Otherwise publishing fails if GitHub release is more than 2 hours old https://github.com/electron-userland/electron-builder/issues/2074

View File

@@ -1,8 +1,8 @@
name: release-site
on:
release:
types: [created] # will be triggered when a NON-draft release is created and published.
release:
types: [created] # will be triggered when a NON-draft release is created and published.
jobs:
aws-deploy: # see: https://github.com/undergroundwires/aws-static-site-with-cd
@@ -77,30 +77,28 @@ jobs:
name: "App: Checkout"
uses: actions/checkout@v2
with:
path: site
path: app
ref: master # otherwise we don't get version bump commit
-
name: "App: Setup node"
uses: actions/setup-node@v1
with:
node-version: 15.x
uses: ./app/.github/actions/setup-node
-
name: "App: Install dependencies"
run: npm ci
working-directory: site
working-directory: app
-
name: "App: Run unit tests"
run: npm run test:unit
working-directory: site
working-directory: app
-
name: "App: Build"
run: npm run build
working-directory: site
working-directory: app
-
name: "App: Deploy to S3"
run: >-
bash "aws/scripts/deploy/deploy-to-s3.sh" \
--folder site/dist \
--folder app/dist \
--web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \
--storage-class ONEZONE_IA \
--role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \

View File

@@ -17,9 +17,7 @@ jobs:
uses: actions/checkout@v2
-
name: Setup node
uses: actions/setup-node@v1
with:
node-version: 15.x
uses: ./.github/actions/setup-node
-
name: Install dependencies
run: npm ci

View File

@@ -19,9 +19,7 @@ jobs:
uses: actions/checkout@v2
-
name: Setup node
uses: actions/setup-node@v1
with:
node-version: 15.x
uses: ./.github/actions/setup-node
-
name: Install dependencies
run: npm ci

View File

@@ -16,10 +16,8 @@ jobs:
name: Checkout
uses: actions/checkout@v2
-
name: Setup node
uses: actions/setup-node@v1
with:
node-version: 15.x
name: Set-up node
uses: ./.github/actions/setup-node
-
name: Install dependencies
run: npm ci

3
.gitignore vendored
View File

@@ -1,7 +1,8 @@
node_modules
dist/
.vs
.vscode
.vscode/**/*
!.vscode/extensions.json
#Electron-builder output
/dist_electron
# Cypress

23
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"recommendations": [
// Common
"editorconfig.editorconfig", // Applies .editorconfig to follow project style.
"wengerk.highlight-bad-chars", // Highlights bad chars.
"wayou.vscode-todo-highlight", // Highlights TODO.
"wix.vscode-import-cost", // Shows in KB how much a require include in code.
// Documentation
"davidanson.vscode-markdownlint", // Lints markdown.
// TypeScript / JavaScript
"dbaeumer.vscode-eslint", // Lints JavaScript/TypeScript.
"pmneo.tsimporter", // Provides better auto-complete for TypeScripts imports.
// Vue
"jcbuisson.vue", // Highlights syntax.
"octref.vetur", // Adds Vetur, Vue tooling support.
// Scripting
"timonwong.shellcheck", // Lints bash files.
"ms-vscode.powershell", // Lints PowerShell files.
"ms-python.python", // Lints Python files.
// Distribution
"ms-azuretools.vscode-docker" // Adds Docker support.
]
}

View File

@@ -1,5 +1,84 @@
# Changelog
## 0.12.0 (2023-08-03)
* Improve script/category name validation | [b210aad](https://github.com/undergroundwires/privacy.sexy/commit/b210aaddf26629179f77fe19f62f65d8a0ca2b87)
* Improve touch like hover on devices without mouse | [99e24b4](https://github.com/undergroundwires/privacy.sexy/commit/99e24b4134c461c336f6d08f49d193d853325d31)
* Improve click/touch without unintended interaction | [3233d9b](https://github.com/undergroundwires/privacy.sexy/commit/3233d9b8024dd59600edddef6d017e0089f59a9d)
* Align card icons vertically in cards view | [8608072](https://github.com/undergroundwires/privacy.sexy/commit/8608072bfb52d10a843a86d3d89b14e8b9776779)
* Fix broken npm installation and builds | [924b326](https://github.com/undergroundwires/privacy.sexy/commit/924b326244a175428175e0df3a50685ee5ac2ec6)
* Improve documentation support with markdown | [6067bdb](https://github.com/undergroundwires/privacy.sexy/commit/6067bdb24e6729d2249c9685f4f1c514c3167d91)
* win: add more Visual Studio scripts, support 2022 | [df533ad](https://github.com/undergroundwires/privacy.sexy/commit/df533ad3b19cebdf3454895aa2182bd4184e0360)
* win: add script to remove Widgets | [bbc6156](https://github.com/undergroundwires/privacy.sexy/commit/bbc6156281fb3fd4b66c63dec3f765780fafa855)
* Use line endings based on script language #88 | [6b3f465](https://github.com/undergroundwires/privacy.sexy/commit/6b3f4659df0afe1c99a8af6598df44a33c1f863a)
* win: improve OneDrive removal | [58ed7b4](https://github.com/undergroundwires/privacy.sexy/commit/58ed7b456b3cf11774c83c8c1c04db37ef3058c2)
* Use lowercase in script names and search text | [430537f](https://github.com/undergroundwires/privacy.sexy/commit/430537f70411756bbcaae837964c0223f78581e8)
* Improve manual execution instructions | [7d3670c](https://github.com/undergroundwires/privacy.sexy/commit/7d3670c26d0151ddc43303e8ed5e47715f0e0f00)
* Add multiline support for with expression | [e8d06e0](https://github.com/undergroundwires/privacy.sexy/commit/e8d06e0f3e178a69861e0197f9d1cce9af3958f1)
* Break line in inline codes in documentation | [c1c2f29](https://github.com/undergroundwires/privacy.sexy/commit/c1c2f2925fe88ec1f56bf7655b6b9a10aa3ea024)
* win: add script to increase RSA key exchange #165 | [a2e0921](https://github.com/undergroundwires/privacy.sexy/commit/a2e092190d8eb0fc9ceb8533572f04fff52f097b)
* win: add scripts to downloaded file handling #153 | [e7b816d](https://github.com/undergroundwires/privacy.sexy/commit/e7b816d1564afa98c63291f9d7fd6f3fee92f4ec)
* Drop support for dead browsers | [bf0c55f](https://github.com/undergroundwires/privacy.sexy/commit/bf0c55fa60bf2be070678ba27db14baf13fec511)
* Add support for nested templates | [68a5d69](https://github.com/undergroundwires/privacy.sexy/commit/68a5d698a2ce644ce25754016fb9e9bb642e41a7)
* mac: add scripts to configure Parallels Desktop | [64cca1d](https://github.com/undergroundwires/privacy.sexy/commit/64cca1d9b8946b92e21e86deb6db5612570befb1)
* Rework icon with higher quality and new color | [f4a7ca7](https://github.com/undergroundwires/privacy.sexy/commit/f4a7ca76b885b8346d8a9c32e6269eabc2d8139f)
* Relax and improve code validation | [e819993](https://github.com/undergroundwires/privacy.sexy/commit/e8199932b462380741d9f2d8b6b55485ab16af02)
* Add initial Linux support #150 | [c404dfe](https://github.com/undergroundwires/privacy.sexy/commit/c404dfebe2908bb165279f8279f3f5e805b647d7)
* mac: add script to disable personalized ads | [8b374a3](https://github.com/undergroundwires/privacy.sexy/commit/8b374a37b401699d5056bfd6b735b6a26c395ae0)
* Update dependencies and add npm setup script | [5721796](https://github.com/undergroundwires/privacy.sexy/commit/57217963787a8ab0c71d681c6b1673c484c88226)
* Fix macOS desktop build failure in CI | [5901dc5](https://github.com/undergroundwires/privacy.sexy/commit/5901dc5f11dd29be14c2616fc0ceb45196a43224)
* Change subtitle heading to new slogan | [1e80ee1](https://github.com/undergroundwires/privacy.sexy/commit/1e80ee1fb0208d92943619468dc427853cbe8de7)
* win: add new scripts to disable more telemetry | [298b058](https://github.com/undergroundwires/privacy.sexy/commit/298b058e5c89397db6f759b275442ba05499ac8c)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.11.4...0.12.0)
## 0.11.4 (2022-03-08)
* Improve performance of selecting scripts | [8e96c19](https://github.com/undergroundwires/privacy.sexy/commit/8e96c19126aa4cba6418de5ccaa9e2dcf8faab78)
* Fix reverting of Windows NVIDIA telemetry service | [2354f0b](https://github.com/undergroundwires/privacy.sexy/commit/2354f0ba9fed3aa23569b5ea6391a7119fe1ab53)
* Add AirBnb TypeScript overrides for linting | [834ce8c](https://github.com/undergroundwires/privacy.sexy/commit/834ce8cf9e8e46934dfa604526360870d109765b)
* Transpile dependencies for wider browser support | [0e52a99](https://github.com/undergroundwires/privacy.sexy/commit/0e52a99efa2b02d1aba10885a76e03aa6f9be7f8)
* Add more and unify tests for absent object cases | [44d79e2](https://github.com/undergroundwires/privacy.sexy/commit/44d79e2c9a97639bbd188a8fdfd740f1a5a1d6ee)
* Fix Windows DoSvc not being disabled #115 | [43ce834](https://github.com/undergroundwires/privacy.sexy/commit/43ce834750ddf471636d1ece4324d02357947f9f)
* Move stubs from `./stubs` to `./shared/Stubs` | [803ef2b](https://github.com/undergroundwires/privacy.sexy/commit/803ef2bb3eea68306377e40e326c791402998650)
* Improve documentation for developing | [3c3ec80](https://github.com/undergroundwires/privacy.sexy/commit/3c3ec80525b97e8a24db4c44bbf42a7b4e089056)
* Improve documentation for architecture | [1bcc6c8](https://github.com/undergroundwires/privacy.sexy/commit/1bcc6c8b2b923b4d4b1662f990d86b190ce73342)
* Improve existing documentation | [db47440](https://github.com/undergroundwires/privacy.sexy/commit/db47440d470ea6a6e100b620b10d078c01314992)
* Refactor to remove code coupling with Webpack | [5bbbb9c](https://github.com/undergroundwires/privacy.sexy/commit/5bbbb9cecca0a3828036e7fc34dcd66970ce334a)
* Refactor to remove hardcoding of aliases | [481a02a](https://github.com/undergroundwires/privacy.sexy/commit/481a02afd5190eb77a37fa450e50816b2268e99c)
* Document WpnService breaking on Windows 10 #110 | [3785e41](https://github.com/undergroundwires/privacy.sexy/commit/3785e410db461f667a834e0b388d81e4baa028e4)
* Fix error when reverting Windows Defender setting | [956052c](https://github.com/undergroundwires/privacy.sexy/commit/956052c8fff042812fe84fe4d7fa5c579365ff9b)
* Fix Windows 11 being detected as Windows 10 | [d6bc33e](https://github.com/undergroundwires/privacy.sexy/commit/d6bc33ec865d50efc6b8d4ccc2f789edd874fcee)
* Refactor to use version object #59 | [eeb1d5b](https://github.com/undergroundwires/privacy.sexy/commit/eeb1d5b0c40a55675921af3f67f366b2ff658acf)
* Fix Microsoft Defender alert for uninstaller #114 | [112e79a](https://github.com/undergroundwires/privacy.sexy/commit/112e79a64c6153f4ce3b48c27a09639e7647aebc)
* Add donation information | [05a6a84](https://github.com/undergroundwires/privacy.sexy/commit/05a6a84c3739ec900343591ac1f7a9f310cd73f2)
* Bump node environment to 16.x | [242a497](https://github.com/undergroundwires/privacy.sexy/commit/242a497e7debb351da19b20b63a3554f0cca4b5c)
* Bump dependencies to latest | [efd63ff](https://github.com/undergroundwires/privacy.sexy/commit/efd63ff85dea4c9a9c033c54bc1be378742de351)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.11.3...0.11.4)
## 0.11.3 (2022-01-05)
* Fix double backlashes in Windows vscode scripts | [5f091bb](https://github.com/undergroundwires/privacy.sexy/commit/5f091bb6abed878271e2321cd784f34436c677bd)
* Fix OS desktop detection tests and edge cases | [a8358b8](https://github.com/undergroundwires/privacy.sexy/commit/a8358b8e7a93214f3d22a4488007ded5f623d845)
* Fix clearing Windows product key showing dialog | [9b6636e](https://github.com/undergroundwires/privacy.sexy/commit/9b6636e21a922a4750dc19f4854f8ae679187926)
* Document and unrecommend Cloud Experience Host | [9b5e0b0](https://github.com/undergroundwires/privacy.sexy/commit/9b5e0b0591fee56af52d83334a1f19180a49516f)
* Add initial e2e testing with cypress | [ddd2e70](https://github.com/undergroundwires/privacy.sexy/commit/ddd2e704dbd361cbd219f3dfe644b983ad254095)
* Restructure pipelines and badges | [5a2c263](https://github.com/undergroundwires/privacy.sexy/commit/5a2c263af35b8785e75ead6c43c3f17186dc15c8)
* Fix failing of functions without revert code | [87de017](https://github.com/undergroundwires/privacy.sexy/commit/87de017afd6e08acbd2deea150c6af9c7ee778fc)
* Fix typos in privacy modal #109 | [a1871a2](https://github.com/undergroundwires/privacy.sexy/commit/a1871a2982c9e3192193f836b97b1a6ccda5a2ab)
* Refactor to add readonly interfaces | [c3c5b89](https://github.com/undergroundwires/privacy.sexy/commit/c3c5b897f308f613c252182a02cdd4cfa7150fa3)
* Document and unrecommend AAD app removal #24, #54 | [455084c](https://github.com/undergroundwires/privacy.sexy/commit/455084c17b32d11d046515e8dc1447adf4bea4c3)
* Migrate from TSLint to ESLint | [61b475f](https://github.com/undergroundwires/privacy.sexy/commit/61b475fa8de433cdada2efa7eac197683aacd956)
* Add build checks and improve existing CI/CD checks | [17298f0](https://github.com/undergroundwires/privacy.sexy/commit/17298f0b2c51cb9becc0eb2ffe0d93d6a4c503a6)
* Upgrade to Vue CLI 5 (and webpack 5) | [96265b7](https://github.com/undergroundwires/privacy.sexy/commit/96265b75deafb85978b16460138fb4a814c07cfe)
* Refactor code to comply with ESLint rules | [5b1fbe1](https://github.com/undergroundwires/privacy.sexy/commit/5b1fbe1e2fb1354a5f060f8c8e3794ce756e16a7)
* Fix mutated line endings on Windows | [bd23faa](https://github.com/undergroundwires/privacy.sexy/commit/bd23faa28f6d781581a33d5b780f4b33f7e2cd8b)
* Refactor to improve iterations | [31f7091](https://github.com/undergroundwires/privacy.sexy/commit/31f70913a2f30baf5a9d6690f192e6a63da50114)
* win: unrecommend and document Live ID service #100 | [d11a674](https://github.com/undergroundwires/privacy.sexy/commit/d11a674a3c4ad8f4972a870c2f0977ac53297273)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.11.2...0.11.3)
## 0.11.2 (2021-12-03)
* Fix Windows TrustedInstaller session errors | [20a0071](https://github.com/undergroundwires/privacy.sexy/commit/20a0071c0d3d769a8f31218abdbfc4cafa25c6ff)

View File

@@ -1,34 +1,51 @@
# Contributing
- Love your input! Contributing to this project should be as easy and transparent as possible, whether it's:
- Reporting a bug
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer
Love your input! Contributing to this project should be as easy and transparent as possible, whether it's:
- reporting a bug,
- discussing the current state of the code,
- submitting a fix,
- proposing new features,
- or becoming a maintainer.
As a small open source project with small community, it can sometimes take a long time to address the issues so please be patient.
## Pull request process
- [GitHub flow](https://guides.github.com/introduction/flow/index.html) with [GitOps](./img/architecture/gitops.png) is used
- Your pull requests are actively welcomed.
- The steps:
1. Fork the repo and create your branch from master.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. Issue that pull request!
- 🙏 DO
- Document your changes in the pull request
- ❗ DON'T
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
Your pull requests are actively welcomed. We collaborate using [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow).
The steps:
1. Fork the repo and create your branch from master.
2. If you've added code that requires testing, add tests. See [tests.md](./docs/tests.md).
3. If you've done a major change, update the documentation. See [docs/](./docs/).
4. Ensure the test suite passes. See [development.md | Testing](./docs/development.md#testing) for commands.
5. Make sure your code lints.See [development.md | Linting](./docs/development.md#linting) for commands.
6. Issue that pull request!
**🙏 DO:**
- Document why (what you're trying to solve) rather than what in the pull request.
**❗ DON'T:**
- Do not update the versions, current version is [set by the maintainer](./docs/ci-cd.md#gitops) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere).
Automated pipelines will run to control your PR and they will publish your code once the maintainer merges your PR.
📖 You can read more in [ci-cd.md](./docs/ci-cd.md).
## Extend scripts
Here's quick information for you who want to add more scripts.
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.
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).
- 📖 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).
## License
By contributing, you agree that your contributions will be licensed under its [GNU General Public License v3.0](./LICENSE).
## Read more
- See [tests](./docs/tests.md) for testing
- See [extend script](./README.md#extend-scripts) for quick steps to extend scripts
- See [architecture overview](./README.md#architecture-overview) to deep dive into privacy.sexy codebase
By contributing, you agree that your [GNU General Public License v3.0](./LICENSE) will be the license for your contributions.

122
README.md
View File

@@ -1,10 +1,16 @@
# privacy.sexy
# privacy.sexy — Now you have the choice
> Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆
> Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆
<!-- markdownlint-disable MD033 -->
<p align="center">
<a href="https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md">
<a href="https://undergroundwires.dev/donate?project=privacy.sexy" target="_blank" rel="noopener noreferrer">
<img
alt="donation badge"
src="https://undergroundwires.dev/img/badges/donate/flat.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">
<img
alt="contributions are welcome"
src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"
@@ -12,13 +18,13 @@
</a>
<!-- Code quality -->
<br />
<a href="https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript">
<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">
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability" target="_blank" rel="noopener noreferrer">
<img
alt="Maintainability"
src="https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability"
@@ -26,19 +32,19 @@
</a>
<!-- Tests -->
<br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.unit.yaml">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.unit.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Unit tests status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/unit-tests/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.integration.yaml">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.integration.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Integration tests status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/integration-tests/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.e2e.yaml">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/tests.e2e.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="E2E tests status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg"
@@ -46,19 +52,19 @@
</a>
<!-- Checks -->
<br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Quality checks status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.yaml">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.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">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Build checks status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/build-checks/badge.svg"
@@ -66,19 +72,19 @@
</a>
<!-- Release -->
<br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.git.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Git release status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-git/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.site.yaml">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.site.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Site release status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-site/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.desktop.yaml">
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/release.desktop.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Desktop application release status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/release-desktop/badge.svg"
@@ -86,7 +92,7 @@
</a>
<!-- Others -->
<br />
<a href="https://github.com/undergroundwires/bump-everywhere">
<a href="https://github.com/undergroundwires/bump-everywhere" target="_blank" rel="noopener noreferrer">
<img
alt="Auto-versioned by bump-everywhere"
src="https://github.com/undergroundwires/bump-everywhere/blob/master/badge.svg?raw=true"
@@ -97,79 +103,39 @@
## Get started
- Online version at [https://privacy.sexy](https://privacy.sexy)
- 💡 No need to run any compiled software on your computer.
- Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-Setup-0.11.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.AppImage).
- 💡 Single click to execute your script.
- ❗ Come back regularly to apply latest version for stronger privacy and security.
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
- 🖥️ **Offline**: Check [releases page](https://github.com/undergroundwires/privacy.sexy/releases), or download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-Setup-0.11.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.dmg), [Linux](https://github.com/undergroundwires/pr.vacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.AppImage).
[![privacy.sexy application](img/screenshot.png?raw=true)](https://privacy.sexy)
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
## Why
💡 You should apply your configuration from time to time (more than once). It would strengthen your privacy and security control because privacy.sexy and its scripts get better and stronger in every new version.
- Rich tweak pool to harden security & privacy of the OS and other software on it
- Free (both free as in beer and free as in speech)
- No need to run any compiled software that has access to your system, just run the generated scripts
- Have full visibility into what the tweaks do as you enable them
- Ability to revert (undo) applied scripts
- Everything is transparent: both application and its infrastructure are open-source and automated
- Easily extendable with [own powerful templating language](./docs/templating.md)
- Each script is independently executable without cross-dependencies
[![privacy.sexy application](img/screenshot.png?raw=true )](https://privacy.sexy)
## Extend scripts
## Features
- You can either [create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose)
- Or send a PR:
1. Fork the repository
2. Add more scripts in respective script collection in [collections](src/application/collections/) folder.
- 📖 If you're unsure about the syntax you can refer to the [collection files | documentation](docs/collection-files.md).
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
3. Send a pull request 👌
- **Rich**: Hundreds of scripts that aims to give you control of your data.
- **Free**: Both free as in "beer" and free as in "speech".
- **Transparent**. Have full visibility into what the tweaks do as you enable them.
- **Reversible**. Revert if something feels wrong.
- **Accessible**. No need to run any compiled software on your computer with web version.
- **Open**. What you see as code in this repository is what you get. The application itself, its infrastructure and deployments are open-source and automated thanks to [bump-everywhere](https://github.com/undergroundwires/bump-everywhere).
- **Tested**. A lot of tests. Automated and manual. Community-testing and verification. Stability improvements comes before new features.
- **Extensible**. Effortlessly [extend scripts](./CONTRIBUTING.md#extend-scripts) with a custom designed [templating language](./docs/templating.md).
- **Portable and simple**. Every script is independently executable without cross-dependencies.
## Commands
## Support
- Project setup: `npm install`
- Testing
- Run unit tests: `npm run test:unit`
- Run integration tests: `npm run test:integration`
- Run e2e (end-to-end) tests
- Interactive mode with GUI: `npm run test:e2e`
- Headless mode without GUI: `npm run test:e2e -- --headless`
- Lint: `npm run lint`
- **Desktop app**
- Development: `npm run electron:serve`
- Production: `npm run electron:build` to build an executable
- **Webpage**
- Development: `npm run serve` to compile & hot-reload for development.
- Production: `npm run build` to prepare files for distribution.
- Or run using Docker:
1. Build: `docker build -t undergroundwires/privacy.sexy:0.11.2 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.11.2 undergroundwires/privacy.sexy:0.11.2`
**Sponsor 💕**. Consider sponsoring on [GitHub Sponsors](https://github.com/sponsors/undergroundwires), or you can donate using [other ways such as crypto or a coffee](https://undergroundwires.dev/donate).
## Architecture overview
**Star 🤩**. Feel free to give it a star ⭐ .
### Application
**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).
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
- Application uses highly decoupled models & services in different DDD layers.
- 📖 Read more on • [Presentation](./docs/presentation.md) • [Application](./docs/application.md)
## Development
![DDD + vue.js](img/architecture/app-ddd.png)
Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment.
### AWS Infrastructure
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.
[![AWS solution](img/architecture/aws-solution.png)](https://github.com/undergroundwires/aws-static-site-with-cd)
- It uses infrastructure from the following repository: [aws-static-site-with-cd](https://github.com/undergroundwires/aws-static-site-with-cd)
- Runs on AWS 100% serverless and automatically provisioned using [GitHub Actions](.github/workflows/).
- Maximum security & automation and minimum AWS costs are the highest priorities of the design.
#### GitOps: CI/CD to AWS
- CI/CD is fully automated for this repo using different GIT events & GitHub actions.
- Versioning, tagging, creation of `CHANGELOG.md` and releasing is automated using [bump-everywhere](https://github.com/undergroundwires/bump-everywhere) action
- Everything that's merged in the master goes directly to production.
- 📖 Read more on [CI/CD pipelines](./docs/ci-cd.md)
[![CI/CD to AWS with GitHub Actions](img/architecture/gitops.png)](.github/workflows/)
[docs/](./docs/) folder includes all other documentation.

View File

@@ -1,5 +1,5 @@
# build
- These are the file that are used by electron.
- Logos are created by from the [PNG icon](./../public/icon.png)
- by running `npx electron-icon-builder --input=./public/icon.png --output=build --flatten`
This folder contains files that are used by Electron to serve the desktop version.
Icons are created from the main logo file and should not be changed manually, see [related documentation](./../img/README.md).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 353 KiB

15
cypress.config.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from 'cypress';
import setupPlugins from './tests/e2e/plugins/index.js';
export default defineConfig({
fixturesFolder: 'tests/e2e/fixtures',
screenshotsFolder: 'tests/e2e/screenshots',
videosFolder: 'tests/e2e/videos',
e2e: {
setupNodeEvents(on, config) {
return setupPlugins(on, config);
},
specPattern: 'tests/e2e/specs/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'tests/e2e/support/index.js',
},
});

View File

@@ -1,3 +0,0 @@
{
"pluginsFile": "tests/e2e/plugins/index.js"
}

View File

@@ -1,44 +1,45 @@
# Application
- It's mainly responsible for
- creating and event based [application state](#application-state)
- [parsing](#parsing) and [compiling](#compiling) [application data](#application-data)
- Consumed by [presentation layer](./presentation.md)
Application layer is mainly responsible for:
- creating an event-based and mutable [application state](#application-state),
- [parsing and compiling](#parsing-and-compiling) the [application data](#application-data).
📖 Refer to [architecture.md | Layered Application](./architecture.md#layered-application) to read more about the layered architecture.
## Structure
- [`/src/` **`application/`**](./../src/application/): Contains all application related code.
- [**`collections/`**](./../src/application/collections/): Holds [collection files](./collection-files.md)
- [**`Common/`**](./../src/application/Common/): Contains common functionality that is shared in application layer.
- `..`: other classes are categorized using folders-by-feature structure
Application layer code exists in [`/src/application`](./../src/application/) and includes following structure:
- [**`collections/`**](./../src/application/collections/): Holds [collection files](./collection-files.md).
- [**`Common/`**](./../src/application/Common/): Contains common functionality in application layer.
- `...`: rest of the application layer source code organized using folders-by-feature structure.
## Application state
- [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) holds the [CategoryCollectionState](./../src/application/Context/State/CategoryCollectionState.ts) for each OS
- Uses [state pattern](https://en.wikipedia.org/wiki/State_pattern)
- Same instance is shared throughout the application to ensure consistent state
- 📖 See [Application State | Presentation layer](./presentation.md#application-state) to read more about how the state should be managed by the presentation layer.
- 📖 See [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) to start diving into the state code.
It uses [state pattern](https://en.wikipedia.org/wiki/State_pattern) with context and state objects. [`ApplicationContext.ts`](./../src/application/Context/ApplicationContext.ts) the "Context" of state pattern provides an instance of [`CategoryCollectionState.ts`](./../src/application/Context/State/CategoryCollectionState.ts) (the "State" of the state pattern) for every supported collection.
Presentation layer uses a singleton (same instance of) [`ApplicationContext.ts`](./../src/application/Context/ApplicationContext.ts) throughout the application to ensure consistent state.
📖 Refer to [architecture.md | Application State](./architecture.md#application-state) to get an overview of event handling and [presentation.md | Application State](./presentation.md#application-state) for deeper look into how the presentation layer manages state.
## Application data
- Compiled to [`Application`](./../src/domain/Application.ts) domain object.
- The scripts are defined and controlled in different data files per OS
- Enables [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming) and easier contributions
- Application data is defined in collection files and
- 📖 See [Application data | Presentation layer](./presentation.md#application-data) to read how the application data is read by the presentation layer.
- 📖 See [collection files documentation](./collection-files.md) to read more about how the data files are structured/defined and see [collection yaml files](./../src/application/collections/) to directly check the code.
Application data is collection files using YAML. You can refer to [collection-files.md](./collection-files.md) to read more about the scheme and structure of application data files. You can also check the source code [collection yaml files](./../src/application/collections/) to directly see the application data using that scheme.
## Parsing
Application layer [parses and compiles](#parsing-and-compiling) application data into [`Application`](./../src/domain/Application.ts)). Once parsed, application layer provides the necessary functionality to presentation layer based on the application data. You can read more about how presentation layer consumes the application data in [presentation.md | Application Data](./presentation.md#application-data).
- Application data is parsed to domain object [`Application.ts`](./../src/domain/Application.ts)
- Steps
1. (Compile time) Load application data from [collection yaml files](./../src/application/collections/) using webpack loader
2. (Runtime) Parse and compile application and make it available to presentation layer by [`ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts)
Application layer enables [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming) by leveraging the data to the rest of the source code. It makes it easy for community to contribute on the project by using a declarative language used in collection files.
### Compiling
### Parsing and compiling
- Parsing the application files includes compiling scripts using [collection file defined functions](./collection-files.md#function)
- To extend the syntax:
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts)
Application layer parses the application data to compile the domain object [`Application.ts`](./../src/domain/Application.ts).
A webpack loader loads (or injects) application data ([collection yaml files](./../src/application/collections/)) into the application layer in compile time. Application layer ([`ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts)) parses and compiles this data in runtime.
Application layer compiles templating syntax during parsing to create the end scripts. You can read more about templating syntax in [templating.md](./templating.md) and how application data uses them through functions in [collection-files.md | Function](./collection-files.md#function).
The steps to extend the templating syntax:
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts).

68
docs/architecture.md Normal file
View File

@@ -0,0 +1,68 @@
# Architecture overview
This repository consists of:
- A [layered application](#layered-application).
- [AWS infrastructure](#aws-infrastructure) as code and instructions to host the website.
- [GitOps](#gitops) practices for development, maintenance and deployment.
## Layered application
Application is
- powered by **TypeScript**, **Vue.js** and **Electron** 💪,
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
Application uses highly decoupled models & services in different DDD layers:
- presentation layer (see [presentation.md](./presentation.md)),
- application layer (see [application.md](./application.md)),
- and domain layer.
Application layer depends on and consumes domain layer. [Presentation layer](./presentation.md) consumes and depends on application layer along with domain layer. Application and presentation layers can communicate through domain model.
![DDD + vue.js](./../img/architecture/app-ddd.png)
### Application state
State handling uses an event-driven subscription model to signal state changes and special functions to register changes. It does not depend on third party packages.
The presentation layer can read and modify state through the context. State changes trigger events that components can subscribe to for reactivity.
Each layer treat application layer differently.
![State](./../img/architecture/app-state.png)
*[Presentation layer](./presentation.md)*:
- Each component holds their own state about presentation-related data.
- Components register shared state changes into application state using functions.
- Components listen to shared state changes using event subscriptions.
- 📖 Read more: [presentation.md | Application state](./presentation.md#application-state).
*[Application layer](./application.md)*:
- Stores the application-specific state.
- The state it exposed for read with getter functions and set using setter functions, setter functions also fire application events that allows other parts of application and the view in presentation layer to react.
- So state is mutable, and fires related events when mutated.
- 📖 Read more: [application.md | Application state](./application.md#application-state).
It's comparable with `flux`, `vuex`, and `pinia`. A difference is that mutable application layer state in privacy.sexy is mutable and lies in single "store" that holds app state and logic. The "actions" mutate the state directly which in turns act as dispatcher to notify its own event subscriptions (callbacks).
## AWS infrastructure
The web-site runs on serverless AWS infrastructure. Infrastructure is open-source and deployed as code. [aws-static-site-with-cd](https://github.com/undergroundwires/aws-static-site-with-cd) project includes the source code.
[![AWS solution](../img/architecture/aws-solution.png)](https://github.com/undergroundwires/aws-static-site-with-cd)
The design priorities highest security then minimizing cloud infrastructure costs.
This project includes [GitHub Actions](../.github/workflows/) to automatically provision the infrastructure with zero-touch and without any "hidden" steps, ensuring everything is open-source and transparent. Git repositories includes all necessary instructions and automation with [GitOps](#gitops) practices.
## GitOps
CI/CD pipelines automate operational tasks based on different Git events. [bump-everywhere](https://github.com/undergroundwires/bump-everywhere) enables this automation.
📖 Read more in [`ci-cd.md`](./ci-cd.md#gitops).
[![CI/CD using GitHub Actions](../img/architecture/gitops.png)](../.github/workflows/)

View File

@@ -1,10 +1,26 @@
# Pipelines
# CI/CD overview
Pipelines are found under [`.github/workflows`](./../.github/workflows).
## GitOps
CI/CD is fully automated using different Git events and GitHub actions. This repository uses [bump-everywhere](https://github.com/undergroundwires/bump-everywhere) to automate versioning, tagging, creation of `CHANGELOG.md` and GitHub releases. A dedicated workflow [release.desktop.yaml](./../.github/workflows/release.desktop.yaml) creates desktop installers and executables and attaches them into GitHub releases.
Everything that's merged in the master goes directly to production.
[![CI/CD using GitHub Actions](./../img/architecture/gitops.png)](../.github/workflows/)
## Pipeline files
privacy.sexy uses [GitHub actions](https://github.com/features/actions) to define and run pipelines as code.
GitHub workflows i.e. pipelines exist in [`/.github/workflows/`](./../.github/workflows/) folder without any subfolders due to GitHub actions requirements [1] .
Local GitHub actions are defined in [`/.github/actions/`](./../.github/actions/) and used to reuse same workflow steps.
## Pipeline types
They are categorized based on their type:
We categorize pipelines into different categories. We use these names in convention when naming files and actions, see [naming conventions](#naming-conventions).
The categories consist of:
- `tests`: Different types of tests to verify functionality.
- `checks`: Other controls such as vulnerability scans or styling checks.
@@ -12,8 +28,18 @@ They are categorized based on their type:
## Naming conventions
Pipeline files are named using: **`<type>.<name>.yaml`**.
Convention for naming pipeline files: **`<type>.<name>.yaml`**.
**`type`**: Sub-folders do not work for GitHub workflows so that's why `<type>.` prefix is used. See also [pipeline types](#pipeline-types).
**`type`**:
**`name`**: Pipeline themselves are named using kebab case. It allows for easier URL references for their status badges. E.g. file name `tests.unit.yaml`, pipeline name: `name: unit-tests`
- Sub-folders do not work for GitHub workflows [1] so we use `<type>.` prefix to organize them.
- See also [pipeline types](#pipeline-types) for list of all usable types.
**`name`**:
- We name workflows using kebab-case.
- E.g. file name `tests.unit.yaml`, pipeline file should set the naem as: `name: unit-tests`.
- Kebab-case allows to have better URL references to them.
- [README.md](./../README.md) uses URL references to show status badges for actions.
[1]: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#about-yaml-syntax-for-workflows

View File

@@ -2,8 +2,8 @@
- privacy.sexy is a data-driven application where it reads the necessary OS-specific logic from yaml files in [`application/collections`](./../src/application/collections/)
- 💡 Best practices
- If you repeat yourself, try to utilize [YAML-defined functions](#Function)
- Always try to add documentation and a way to revert a tweak in [scripts](#Script)
- If you repeat yourself, try to utilize [YAML-defined functions](#function)
- Always try to add documentation and a way to revert a tweak in [scripts](#script)
- 📖 Types in code: [`collection.yaml.d.ts`](./../src/application/collections/collection.yaml.d.ts)
## Objects
@@ -13,19 +13,19 @@
- A collection simply defines:
- different categories and their scripts in a tree structure
- OS specific details
- Also allows defining common [function](#Function)s to be used throughout the collection if you'd like different scripts to share same code.
- Also allows defining common [function](#function)s to be used throughout the collection if you'd like different scripts to share same code.
#### `Collection` syntax
- `os:` *`string`* (**required**)
- Operating system that the [Collection](#collection) is written for.
- 📖 See [OperatingSystem.ts](./../src/domain/OperatingSystem.ts) enumeration for allowed values.
- `actions: [` ***[`Category`](#Category)*** `, ... ]` **(required)**
- `actions: [` ***[`Category`](#category)*** `, ... ]` **(required)**
- Each [category](#category) is rendered as different cards in card presentation.
- ❗ A [Collection](#collection) must consist of at least one category.
- `functions: [` ***[`Function`](#Function)*** `, ... ]`
- `functions: [` ***[`Function`](#function)*** `, ... ]`
- Functions are optionally defined to re-use the same code throughout different scripts.
- `scripting:` ***[`ScriptingDefinition`](#ScriptingDefinition)*** **(required)**
- `scripting:` ***[`ScriptingDefinition`](#scriptingdefinition)*** **(required)**
- Defines the scripting language that the code of other action uses.
### `Category`
@@ -38,9 +38,12 @@
- `category:` *`string`* (**required**)
- Name of the category
- ❗ Must be unique throughout the [Collection](#collection)
- `children: [` ***[`Category`](#Category)*** `|` [***`Script`***](#Script) `, ... ]` (**required**)
- `children: [` ***[`Category`](#category)*** `|` [***`Script`***](#script) `, ... ]` (**required**)
- ❗ Category must consist of at least one subcategory or script.
- Children can be combination of scripts and subcategories.
- `docs`: *`string`* | `[`*`string`*`, ... ]`
- Documentation pieces related to the category.
- Rendered as markdown.
### `Script`
@@ -67,12 +70,12 @@
- E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1`
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
- ❗ Do not define if `call` is defined.
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
- `call`: ***[`FunctionCall`](#functioncall)*** | `[` ***[`FunctionCall`](#functioncall)*** `, ... ]` (may be **required**)
- A shared function or sequence of functions to call (called in order)
- ❗ If not defined `code` must be defined
- `docs`: *`string`* | `[`*`string`*`, ... ]`
- Single documentation URL or list of URLs for those who wants to learn more about the script
- E.g. `https://docs.microsoft.com/en-us/windows-server/`
- Documentation pieces related to the script.
- Rendered as markdown.
- `recommend`: `"standard"` | `"strict"` | `undefined` (default)
- If not defined then the script will not be recommended
- If defined it can be either
@@ -120,7 +123,7 @@
- Convention is to use camelCase, and be verbs.
- E.g. `uninstallStoreApp`
- ❗ Function names must be unique
- `parameters`: `[` ***[`FunctionParameter`](#FunctionParameter)*** `, ... ]`
- `parameters`: `[` ***[`FunctionParameter`](#functionparameter)*** `, ... ]`
- List of parameters that function code refers to.
- ❗ Must be defined to be able use in [`FunctionCall`](#functioncall) or [expressions (templating)](./templating.md#expressions)
`code`: *`string`* (**required** if `call` is undefined)
@@ -133,7 +136,7 @@
- E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1`
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
- 💡 [Expressions (templating)](./templating.md#expressions) can be used in code
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
- `call`: ***[`FunctionCall`](#functioncall)*** | `[` ***[`FunctionCall`](#functioncall)*** `, ... ]` (may be **required**)
- A shared function or sequence of functions to call (called in order)
- The parameter values that are sent can use [expressions (templating)](./templating.md#expressions)
- ❗ If not defined `code` must be defined
@@ -141,7 +144,7 @@
### `FunctionParameter`
- Defines a parameter that function requires optionally or mandatory.
- Its arguments are provided by a [Script](#script) through a [FunctionCall](#FunctionCall).
- Its arguments are provided by a [Script](#script) through a [FunctionCall](#functioncall).
#### `FunctionParameter` syntax

61
docs/development.md Normal file
View File

@@ -0,0 +1,61 @@
# Development
Before your commit, a good practice is to:
1. [Run unit tests](#testing)
2. [Lint your code](#linting)
You could run other types of tests as well, but they may take longer time and overkill for your changes. Automated actions executes the tests for a pull request or change in the main branch. See [ci-cd.md](./ci-cd.md) for more information.
## Commands
### Prerequisites
- Install node >15.x.
- Install dependencies using `npm install`.
### Testing
- Run unit tests: `npm run test:unit`
- Run integration tests: `npm run test:integration`
- Run e2e (end-to-end) tests
- Interactive mode with GUI: `npm run test:e2e`
- Headless mode without GUI: `npm run test:e2e -- --headless`
📖 Read more about testing in [tests](./tests.md).
### Linting
- Lint all (recommended 💡): `npm run lint`
- Markdown: `npm run lint:md`
- Markdown consistency `npm run lint:md:consistency`
- Markdown relative URLs: `npm run lint:md:relative-urls`
- JavaScript/TypeScript: `npm run lint:eslint`
- Yaml: `npm run lint:yaml`
### Running
- Run in local server: `npm run serve`
- 💡 Meant for local development with features such as hot-reloading.
- Run using Docker:
1. Build: `docker build -t undergroundwires/privacy.sexy:latest .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest`
### Building
- Build web application: `npm run build`
- Build desktop application: `npm run electron:build`
- (Re)create icons (see [documentation](../img/README.md)): `npm run create-icons`
### Utility Scripts
- Run fresh NPM install: [`./scripts/fresh-npm-install.sh`](../scripts/fresh-npm-install.sh)
- This script provides a clean NPM install, removing existing node modules and optionally the package-lock.json (when run with -n), then installs dependencies and runs unit tests.
- Configure VSCode: [`./scripts/configure-vscode.sh`](../scripts/configure-vscode.sh)
- This script checks and sets the necessary configurations for VSCode in `settings.json` file.
## Recommended extensions
You should use EditorConfig to follow project style.
For Visual Studio Code, [`.vscode/extensions.json`](./../.vscode/extensions.json) includes list of recommended extensions.

View File

@@ -1,72 +1,102 @@
# Presentation layer
- Consists of Vue.js components and other UI-related code.
- Desktop application is created using [Electron](https://www.electronjs.org/).
- Event driven as in components simply listens to events from the state and act accordingly.
The presentation layer handles UI concerns using Vue as JavaScript framework and Electron to provide desktop functionality.
It reflects the [application state](./application.md#application-state) and allows user interactions to modify it. Components manage their own local UI state.
The presentation layer uses an event-driven architecture for bidirectional reactivity between the application state and UI. State change events flow bottom-up to trigger UI updates, while user events flow top-down through components, some ultimately modifying the application state.
📖 Refer to [architecture.md (Layered Application)](./architecture.md#layered-application) to read more about the layered architecture.
## Structure
- [`/src/` **`presentation/`**](./../src/presentation/): Contains all presentation related code including Vue and Electron configurations
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue global objects including components and plugins.
- [**`components/`**](./../src/presentation/components/): Contains all Vue components and their helper classes.
- [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that are shared across other components.
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets that will be processed by webpack.
- [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that other components share.
- [**`hooks`**](../src/presentation/components/Shared/Hooks): Shared hooks for state access
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets that webpack will process.
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles used throughout different components.
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles that are reusable and tightly coupled a Vue/HTML component.
- [**`components/`**](./../src/presentation/assets/styles/components): Contains reusable styles coupled to a Vue/HTML component.
- [**`vendors-extensions/`**](./../src/presentation/assets/styles/third-party-extensions): Contains styles that override third-party components used.
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Primary Sass file, passes along all other styles, should be the only file used from other components.
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Primary Sass file, passes along all other styles, should be the single file used from other components.
- [**`main.ts`**](./../src/presentation/main.ts): Application entry point that mounts and starts Vue application.
- [**`electron/`**](./../src/presentation/electron/): Electron configuration for the desktop application.
- [**`main.ts`**](./../src/presentation/main.ts): Main process of Electron, started as first thing when app starts.
- [**`/public/`**](./../public/): Contains static assets that will directly be copied and not go through webpack.
- [**`/vue.config.js`**](./../vue.config.js): Global Vue CLI configurations loaded by `@vue/cli-service`
- [**`/postcss.config.js`**](./../postcss.config.js): PostCSS configurations that are used by Vue CLI internally
- [**`/babel.config.js`**](./../babel.config.js): Babel configurations for polyfills used by `@vue/cli-plugin-babel`
- [**`/public/`**](./../public/): Contains static assets that are directly copied and do not go through webpack.
- [**`/vue.config.cjs`**](./../vue.config.cjs): Global Vue CLI configurations loaded by `@vue/cli-service`.
- [**`/postcss.config.cjs`**](./../postcss.config.cjs): PostCSS configurations used by Vue CLI internally.
- [**`/babel.config.cjs`**](./../babel.config.cjs): Babel configurations for polyfills used by `@vue/cli-plugin-babel`.
## Visual design best-practices
Add visual clues for clickable items. It should be as clear as possible that they're interactable at first look without hovering. They should also have different visual state when hovering/touching on them that indicates that they are being clicked, which helps with accessibility.
## Application data
- Components and should use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain.
- [Application.ts](../src/domain/Application.ts) domain model is the stateless application representation including
- available scripts, collections as defined in [collection files](./collection-files.md)
- package information as defined in [`package.json`](./../package.json)
- 📖 See [Application data | Application layer](./presentation.md#application-data) where application data is parsed and compiled.
Components (should) use [`UseApplication`](./../src/presentation/components/Shared/Hooks/UseApplication.ts) to reach the application domain to avoid [parsing and compiling](./application.md#parsing-and-compiling) the application again.
[Application.ts](../src/domain/Application.ts) is an immutable domain model that represents application state. It includes:
- available scripts, collections as defined in [collection files](./collection-files.md),
- package information as defined in [`package.json`](./../package.json).
You can read more about how application layer provides application data to he presentation in [application.md | Application data](./application.md#application-data).
## Application state
- Stateful components mutate or/and react to state changes in [ApplicationContext](./../src/application/Context/ApplicationContext.ts).
- Stateless components that does not handle state extends `Vue`
- Stateful components that depends on the collection state such as user selection, search queries and more extends [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts)
- The single source of truth is a singleton of the state created and made available to presentation layer by [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts)
- `StatefulVue` includes abstract `handleCollectionState` that is fired once the component is loaded and also each time [collection](./collection-files.md) is changed.
- Do not forget to subscribe from events when component is destroyed or if needed [collection](./collection-files.md) is changed.
- 💡 `events` in base class [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts) makes lifecycling easier
- 📖 See [Application state | Application layer](./presentation.md#application-state) where the state is implemented using using state pattern.
This project uses a singleton instance of the application state, making it available to all Vue components.
## Modals
The decision to not use third-party state management libraries like [`vuex`](https://web.archive.org/web/20230801191617/https://vuex.vuejs.org/) or [`pinia`](https://web.archive.org/web/20230801191743/https://pinia.vuejs.org/) was made to promote code independence and enhance portability.
- [Dialog.vue](./../src/presentation/components/Shared/Dialog.vue) is a shared component that can be used to show modal windows
- Simply wrap the content inside of its slot and call `.show()` method on its reference.
- Example:
Stateful components can mutate and/or react to state changes (e.g., user selection, search queries) in the [ApplicationContext](./../src/application/Context/ApplicationContext.ts). Vue components import [`CollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) to access both the application context and the state.
```html
<Dialog ref="testDialog">
<div>Hello world</div>
</Dialog>
<div @click="$refs.testDialog.show()">Show dialog</div>
```
[`UseCollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) provides several functionalities including:
- **Singleton State Instance**: It creates a singleton instance of the state, which is shared across the presentation layer. The singleton instance ensures that there's a single source of truth for the application's state.
- **State Change Callback and Lifecycle Management**: It offers a mechanism to register callbacks, which will be invoked when the state initializes or mutates. It ensures that components unsubscribe from state events when they are no longer in use or when [ApplicationContext](./../src/application/Context/ApplicationContext.ts) switches the active [collection](./collection-files.md).
- **State Access and Modification**: It provides functions to read and mutate for accessing and modifying the state, encapsulating the details of these operations.
- **Event Subscription Lifecycle Management**: Includes an `events` member that simplifies state subscription lifecycle events. This ensures that components unsubscribe from state events when they are no longer in use, or when [ApplicationContext](./../src/application/Context/ApplicationContext.ts) switches the active [collection](./collection-files.md).
📖 Refer to [architecture.md | Application State](./architecture.md#application-state) for an overview of event handling and [application.md | Application State](./presentation.md#application-state) for an in-depth understanding of state management in the application layer.
## Dependency injections
The presentation layer uses Vue's native dependency injection system to increase testability and decouple components.
To add a new dependency:
1. **Define its symbol**: Define an associated symbol for every dependency in [`injectionSymbols.ts`](./../src/presentation/injectionSymbols.ts). Symbols are grouped into:
- **Singletons**: Shared across components, instantiated once.
- **Transients**: Factories yielding a new instance on every access.
2. **Provide the dependency**: Modify the [`provideDependencies`](./../src/presentation/bootstrapping/DependencyProvider.ts) function to include the new dependency. [`App.vue`](./../src/presentation/components/App.vue) calls this function within its `setup()` hook to register the dependencies.
3. **Inject the dependency**: Use Vue's `inject` method alongside the defined symbol to incorporate the dependency into components.
- For singletons, invoke the factory method: `inject(symbolKey)()`.
- For transients, directly inject: `inject(symbolKey)`.
## Shared UI components
Shared UI components promote consistency and simplifies the creation of the front-end.
In order to maintain portability and easy maintainability, the preference is towards using homegrown components over third-party ones or comprehensive UI frameworks like Quasar.
Shared components include:
- [ModalDialog.vue](./../src/presentation/components/Shared/Modal/ModalDialog.vue) is utilized for rendering modal windows.
- [TooltipWrapper.vue](./../src/presentation/components/Shared/TooltipWrapper.vue) acts as a wrapper for rendering tooltips.
## Sass naming convention
- Use lowercase for variables/functions/mixins e.g.
- Use lowercase for variables/functions/mixins, e.g.:
- Variable: `$variable: value;`
- Function: `@function function() {}`
- Mixin: `@mixin mixin() {}`
- Use - for a phrase/compound word e.g.
- Use - for a phrase/compound word, e.g.:
- Variable: `$some-variable: value;`
- Function: `@function some-function() {}`
- Mixin: `@mixin some-mixin() {}`
- Grouping and name variables from generic to specific e.g.
- Grouping and name variables from generic to specific, e.g.:
-`$border-blue`, `$border-blue-light`, `$border-blue-lightest`, `$border-red`
-`$blue-border`, `$light-blue-border`, `$lightest-blue-border`, `$red-border`

View File

@@ -3,16 +3,26 @@
## Benefits of templating
- Generating scripts by sharing code to increase best-practice usage and maintainability.
- Creating self-contained scripts without depending on each other that can be easily shared.
- Creating self-contained scripts without cross-dependencies.
- Use of pipes for writing cleaner code and letting pipes do dirty work.
## Expressions
- Expressions in the language are defined inside mustaches (double brackets, `{{` and `}}`).
- Expression syntax is inspired mainly by [Go Templates](https://pkg.go.dev/text/template).
- Expressions are used in and enabled by functions where they can be used.
- Expressions start and end with mustaches (double brackets, `{{` and `}}`).
- 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
{{ with $condition }}
echo {{ $text }}
{{ end }}
```
### Parameter substitution
@@ -55,34 +65,58 @@ A function can call other functions such as:
### with
- Skips the block if the variable is absent or empty.
- Binds its context (`.`) value of provided argument for the parameter if provided one.
- A block is defined as `{{ with $parameterName }} Parameter value is {{ . }} here {{ end }}`.
- The parameters used for `with` condition should be declared as optional, otherwise `with` block becomes redundant.
- Example:
Skips its "block" if the variable is absent or empty. Its "block" is between `with` start (`{{ with .. }}`) and end (`{{ end }`}) expressions.
E.g. `{{ with $parameterName }} Hi, I'm a block! {{ end }}` would only output `Hi, I'm a block!` if `parameterName` has any value..
```yaml
function: FunctionThatOutputsConditionally
parameters:
- name: 'argument'
optional: true
code: |-
{{ with $argument }}
Value is: {{ . }}
{{ end }}
```
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:
```go
{{ with $parameterName }}Parameter value is {{ . }} here {{ end }}
```
It supports multiline text inside the block. You can have something like:
```go
{{ with $argument }}
First line
Second line
{{ end }}
```
You can also use other expressions inside its block, such as [parameter substitution](#parameter-substitution):
```go
{{ with $condition }}
This is a different parameter: {{ $text }}
{{ end }}
```
💡 Declare parameters used for `with` condition as optional. Set `optional: true` for the argument if you use it like `{{ with $argument }} .. {{ end }}`.
Example:
```yaml
function: FunctionThatOutputsConditionally
parameters:
- name: 'argument'
optional: true
code: |-
{{ with $argument }}
Value is: {{ . }}
{{ end }}
```
### Pipes
- Pipes are set of functions available for handling text in privacy.sexy.
- Pipes are functions available for handling text.
- Allows stacking actions one after another also known as "chaining".
- Just 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.
- Pipes are provided and defined by the compiler and consumed by collection files.
- Pipes can be combined with [parameter substitution](#parameter-substitution) and [with](#with).
- Like [Unix pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)), the concept is simple: each pipeline's output becomes the input of the following pipe.
- You cannot create pipes. [A dedicated compiler](./application.md#parsing-and-compiling) provides pre-defined pipes to consume in collection files.
- You can combine pipes with other expressions such as [parameter substitution](#parameter-substitution) and [with](#with) syntax.
- ❗ Pipe names must be camelCase without any space or special characters.
- **Existing pipes**
- `inlinePowerShell`: Converts a multi-lined PowerShell script to a single line.
- `escapeDoubleQuotes`: Escapes `"` characters to be used inside double quotes (`"`)
- `escapeDoubleQuotes`: Escapes `"` characters, allows you to use them inside double quotes (`"`).
- **Example usages**
- `{{ with $code }} echo "{{ . | inlinePowerShell }}" {{ end }}`
- `{{ with $code }} echo "{{ . | inlinePowerShell | escapeDoubleQuotes }}" {{ end }}`

View File

@@ -1,61 +1,82 @@
# Tests
- There are two different types of tests executed:
1. [Unit tests](#unit-tests)
2. [Integration tests](#integration-tests)
3. [End-to-end (E2E) tests](#e2e-tests)
- All tests
- Uses [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/).
- Are written in files that includes `.spec` extension.
- 💡 You can use path/module alias `@/tests` in import statements.
There are different types of tests executed:
1. [Unit tests](#unit-tests)
2. [Integration tests](#integration-tests)
3. [End-to-end (E2E) tests](#e2e-tests)
Common aspects for all tests:
- They use [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/).
- Their files end with `.spec.{ts|js}` suffix.
💡 You can use path/module alias `@/tests` in import statements.
## Unit tests
- Tests each component in isolation.
- Defined in [`./tests/unit`](./../tests/unit).
- They follow same folder structure as [`./src`](./../src).
- Unit tests test each component in isolation.
- All unit tests goes under [`./tests/unit`](./../tests/unit).
- They rely on [stubs](./../tests/unit/shared/Stubs) for isolation.
- Unit tests include also Vue component tests using `@vue/test-utils`.
### Naming
### Unit tests structure
- [`./src/`](./../src/)
- Includes source code that unit tests will test.
- [`./tests/unit/`](./../tests/unit/)
- Includes test code.
- Tests follow same folder structure as [`./src/`](./../src).
- E.g. if system under test lies in [`./src/application/ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts) then its tests would be in test would be at [`./tests/unit/application/ApplicationFactory.spec.ts`](./../tests/unit/application/ApplicationFactory.spec.ts).
- [`shared/`](./../tests/unit/shared/)
- Includes common functionality that's shared across unit tests.
- [`Assertions/`](./../tests/unit/shared/Assertions):
- Common assertions that extend [Chai Assertion Library](https://www.chaijs.com/).
- Asserting functions should start with `expect` prefix.
- [`TestCases/`](./../tests/unit/shared/TestCases/)
- Shared test cases.
- Functions that calls `it()` from [Mocha test framework](https://mochajs.org/) should have `it` prefix.
- E.g. `itEachAbsentCollectionValue()`.
- [`Stubs/`](./../tests/unit/shared/Stubs)
- Includes stubs to be able to test components in isolation.
- Stubs have minimal and dummy behavior to be functional, they may also have spying or mocking functions.
### Unit tests naming
- Each test suite first describe the system under test.
- E.g. tests for class `Application` is categorized under `Application`.
- Tests for specific methods are categorized under method name (if applicable).
- E.g. test for `run()` is categorized under `run`.
- E.g. tests for class `Application.ts` are all inside `Application.spec.ts`.
- `describe` blocks tests for same function (if applicable).
- E.g. test for `run()` are inside `describe('run', () => ..)`.
### Act, arrange, assert
- Tests use act, arrange and assert (AAA) pattern when applicable.
- **Arrange**
- Should set up the test case.
- Sets up the test case.
- Starts with comment line `// arrange`.
- **Act**
- Should cover the main thing to be tested.
- Executes the actual test.
- Starts with comment line `// act`.
- **Assert**
- Should elicit some sort of response.
- Elicit some sort of expectation.
- Starts with comment line `// assert`.
### Stubs
- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs).
- They implement dummy behavior to be functional.
## Integration tests
- Tests functionality of a component in combination with others (not isolated).
- Ensure dependencies to third parties work as expected.
- Defined in [`./tests/integration`](./../tests/integration).
- Defined in [./tests/integration](./../tests/integration).
## E2E tests
- Test the functionality and performance of a running application.
- E2E tests are configured by vue plugin [`e2e-cypress`](https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-plugin-e2e-cypress#readme) for Vue CLI.
- Names and folders are structured logically based on tests.
- Vue CLI plugin [`e2e-cypress`](https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-plugin-e2e-cypress#readme) configures E2E tests.
- Test names and folders have logical structure based on tests executed.
- The structure is following:
- [`cypress.json`](./../cypress.json): Cypress configuration file.
- [`cypress.config.ts`](./../cypress.config.ts): Cypress configuration file.
- [`./tests/e2e/`](./../tests/e2e/): Base Cypress folder.
- [`/specs/`](./../tests/e2e/specs/): Test files, test are named with `.spec.js` extension.
- [`/plugins/index.js`](./../tests/e2e/plugins/index.js): Plugin file executed before project is loaded.
- [`/specs/`](./../tests/e2e/specs/): Test files named with `.spec.js` extension.
- [`/plugins/index.js`](./../tests/e2e/plugins/index.js): Plugin file executed before loading project.
- [`/support/index.js`](./../tests/e2e/support/index.js): Support file, runs before every single spec file.
- *(Ignored)* `/videos`: Asset folder for videos taken during tests.
- *(Ignored)* `/screenshots`: Asset folder for Screenshots taken during tests.

12
img/README.md Normal file
View File

@@ -0,0 +1,12 @@
# img
This folder contains image files and other resources related to images.
## logo.svg
[logo.svg](./logo.svg) is the master logo from which all other icons or images are created from.
It should be the only file that will be changed manually.
[`logo-update.mjs`](./logo-update.mjs) script in this folder updates all the logo files.
It should be executed everytime the logo is changed.
It automates recreation of logo files in different formats.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

127
img/logo-update.mjs Normal file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/env bash
import { resolve, join } from 'path';
import { rm, mkdtemp, stat } from 'fs/promises';
import { spawn } from 'child_process';
import { URL, fileURLToPath } from 'url';
class Paths {
constructor(selfDirectory) {
const projectRoot = resolve(selfDirectory, '../');
this.sourceImage = join(projectRoot, 'img/logo.svg');
this.publicDirectory = join(projectRoot, 'public');
this.electronBuildDirectory = join(projectRoot, 'build');
}
toString() {
return `Source image: ${this.sourceImage}\n`
+ `Public directory: ${this.publicDirectory}\n`
+ `Electron build directory: ${this.electronBuildDirectory}`;
}
}
async function main() {
const paths = new Paths(getCurrentScriptDirectory());
console.log(`Paths:\n\t${paths.toString().replaceAll('\n', '\n\t')}`);
await updateDesktopLauncherAndTrayIcon(paths.sourceImage, paths.publicDirectory);
await updateWebFavicon(paths.sourceImage, paths.publicDirectory);
await updateDesktopIcons(paths.sourceImage, paths.electronBuildDirectory);
console.log('🎉 (Re)created icons successfully.');
}
async function updateDesktopLauncherAndTrayIcon(sourceImage, publicFolder) {
await ensureFileExists(sourceImage);
await ensureFolderExists(publicFolder);
const electronTrayIconFile = join(publicFolder, 'icon.png');
console.log(`Updating desktop launcher and tray icon at ${electronTrayIconFile}.`);
await runCommand(
'npx',
'svgexport',
sourceImage,
electronTrayIconFile,
);
}
async function updateWebFavicon(sourceImage, faviconFolder) {
console.log('Updating favicon');
await ensureFileExists(sourceImage);
await ensureFolderExists(faviconFolder);
await runCommand(
'npx',
'icon-gen',
`--input ${sourceImage}`,
`--output ${faviconFolder}`,
'--ico',
'--ico-name \'favicon\'',
'--report',
);
}
async function updateDesktopIcons(sourceImage, electronIconsDir) {
await ensureFileExists(sourceImage);
await ensureFolderExists(electronIconsDir);
const temporaryDir = await mkdtemp('icon-');
const temporaryPngFile = join(temporaryDir, 'icon.png');
console.log(`Converting from SVG (${sourceImage}) to PNG: ${temporaryPngFile}`); // required by icon-builder
await runCommand(
'npx',
'svgexport',
sourceImage,
temporaryPngFile,
'1024:1024',
);
console.log(`Creating electron icons to ${electronIconsDir}.`);
await runCommand(
'npx',
'electron-icon-builder',
`--input="${temporaryPngFile}"`,
`--output="${electronIconsDir}"`,
'--flatten',
);
console.log('Cleaning up temporary directory.');
await rm(temporaryDir, { recursive: true, force: true });
}
async function ensureFileExists(filePath) {
const path = await stat(filePath);
if (!path.isFile()) {
throw new Error(`Not a file: ${filePath}`);
}
}
async function ensureFolderExists(folderPath) {
const path = await stat(folderPath);
if (!path.isDirectory()) {
throw new Error(`Not a directory: ${folderPath}`);
}
}
async function runCommand(...args) {
const command = args.join(' ');
console.log(`Running command: ${command}`);
await new Promise((resolve, reject) => {
const process = spawn(command, { shell: true });
process.stdout.on('data', (stdout) => {
console.log(stdout.toString());
});
process.stderr.on('data', (stderr) => {
console.error(stderr.toString());
});
process.on('error', (err) => {
reject(err);
});
process.on('close', (exitCode) => {
if (exitCode !== 0) {
reject(new Error(`Process exited with non-zero exit code: ${exitCode}`));
} else {
resolve();
}
process.stdin.end();
});
});
}
function getCurrentScriptDirectory() {
return fileURLToPath(new URL('.', import.meta.url));
}
await main();

56
img/logo.svg Normal file
View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="90.2998mm" height="90.2998mm"
viewBox="0 0 256 256">
<path id="logo"
fill="#3a65ab" stroke="#3a65ab" stroke-width="1"
d="M 128.00,173.00
C 128.00,173.00 102.00,175.00 102.00,175.00
85.39,174.97 64.02,170.31 49.00,163.22
38.46,158.24 28.39,152.17 20.01,143.96
14.88,138.93 10.72,133.32 7.31,127.00
3.36,119.66 1.10,112.37 1.00,104.00
1.00,104.00 1.00,98.00 1.00,98.00
1.29,73.92 24.76,53.44 44.00,42.43
84.66,19.15 129.75,23.12 169.00,47.00
188.74,59.02 207.93,76.23 208.00,101.00
208.00,101.00 188.00,101.00 188.00,101.00
186.46,86.48 168.72,71.84 157.00,64.61
140.76,54.59 121.33,46.78 102.00,47.00
88.42,47.16 72.20,52.52 60.00,58.32
45.30,65.30 19.83,84.81 21.10,103.00
21.39,107.16 22.92,110.33 24.76,114.00
32.70,129.78 48.16,140.02 64.00,146.72
75.16,151.44 92.90,155.26 105.00,154.99
113.45,154.79 121.81,152.84 130.00,151.00
130.00,151.00 128.00,173.00 128.00,173.00 Z
M 136.00,79.00
C 142.71,81.35 144.84,93.60 144.99,100.00
145.51,122.74 130.31,140.73 107.00,141.00
83.63,141.26 67.43,126.52 66.09,103.00
64.82,80.73 85.85,58.90 104.00,64.00
100.18,69.73 95.45,74.53 96.20,82.00
97.29,92.87 110.06,102.98 121.00,99.03
129.92,95.81 134.61,87.96 136.00,79.00 Z
M 186.00,113.46
C 206.11,110.69 225.57,114.92 239.91,130.01
252.85,143.63 255.21,157.09 255.00,175.00
254.76,195.49 241.26,214.25 223.00,222.88
213.06,227.58 204.72,228.12 194.00,228.00
150.34,227.49 126.71,178.85 146.32,142.00
154.93,125.82 168.55,117.23 186.00,113.46 Z
M 233.00,181.00
C 242.24,158.78 221.84,133.54 199.00,133.01
188.40,132.77 182.75,135.31 174.00,141.00
178.60,146.85 195.92,157.24 203.00,161.86
209.82,166.32 226.61,178.55 233.00,181.00 Z
M 221.00,200.00
C 216.39,194.15 206.42,188.61 200.00,184.33
192.31,179.21 168.77,162.59 162.00,160.00
159.67,165.03 159.94,166.57 160.00,172.00
160.23,190.99 177.11,207.55 196.00,207.99
206.60,208.23 212.25,205.69 221.00,200.00 Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

35688
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,92 +1,107 @@
{
"name": "privacy.sexy",
"version": "0.11.2",
"version": "0.12.0",
"private": true,
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
"slogan": "Now you have the choice",
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
"author": "undergroundwires",
"type": "module",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:unit": "vue-cli-service test:unit --include ./tests/bootstrap/setup.ts",
"test:e2e": "vue-cli-service test:e2e",
"lint": "npm run lint:md && npm run lint:md:consistency && npm run lint:md:relative-urls && npm run lint:eslint && npm run lint:yaml",
"create-icons": "node img/logo-update.mjs",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"lint:eslint": "vue-cli-service lint --no-fix --mode production",
"lint:md": "markdownlint **/*.md --ignore node_modules",
"lint:md:consistency": "remark . --frail --use remark-preset-lint-consistent",
"lint:md:relative-urls": "remark . --frail --use remark-validate-links",
"lint:eslint": "vue-cli-service lint --no-fix --mode production",
"lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"test:integration": "vue-cli-service test:unit \"tests/integration/**/*.spec.ts\""
"test:integration": "vue-cli-service test:unit \"tests/integration/**/*.spec.ts\" --include ./tests/bootstrap/setup.ts"
},
"main": "background.js",
"main": "index.js",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.6",
"@juggle/resize-observer": "^3.3.1",
"ace-builds": "^1.4.13",
"core-js": "^3.18.3",
"cross-fetch": "^3.1.4",
"electron-progressbar": "^2.0.1",
"@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",
"ace-builds": "^1.23.4",
"core-js": "^3.32.0",
"cross-fetch": "^4.0.0",
"electron-progressbar": "^2.1.0",
"file-saver": "^2.0.5",
"install": "^0.13.0",
"liquor-tree": "^0.2.70",
"npm": "^8.1.1",
"markdown-it": "^13.0.1",
"npm": "^9.8.1",
"v-tooltip": "2.1.3",
"vue": "^2.6.14",
"vue-class-component": "^7.2.6",
"vue-js-modal": "^2.0.1",
"vue-property-decorator": "^9.1.2"
"vue": "^2.7.14"
},
"devDependencies": {
"@types/ace": "0.0.47",
"@types/chai": "^4.2.22",
"@types/file-saver": "^2.0.3",
"@types/mocha": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0-rc.1",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-rc.1",
"@vue/cli-plugin-eslint": "~5.0.0-rc.1",
"@vue/cli-plugin-typescript": "~5.0.0-rc.1",
"@vue/cli-plugin-unit-mocha": "~5.0.0-rc.1",
"@vue/cli-service": "~5.0.0-rc.1",
"@vue/eslint-config-airbnb": "^6.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"@vue/test-utils": "1.2.2",
"chai": "^4.3.4",
"cypress": "^8.3.0",
"electron": "^15.3.0",
"@rushstack/eslint-patch": "^1.3.2",
"@types/ace": "^0.0.48",
"@types/chai": "^4.3.5",
"@types/file-saver": "^2.0.5",
"@types/mocha": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-e2e-cypress": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-plugin-typescript": "~5.0.8",
"@vue/cli-plugin-unit-mocha": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"@vue/eslint-config-airbnb-with-typescript": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/test-utils": "^1.3.6",
"chai": "^4.3.7",
"cypress": "^12.17.2",
"electron": "^25.3.2",
"electron-builder": "^24.6.3",
"electron-devtools-installer": "^3.2.0",
"electron-log": "^4.4.1",
"electron-updater": "^4.3.9",
"eslint": "^7.32.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-vue": "^8.0.3",
"eslint-plugin-vuejs-accessibility": "^1.1.0",
"electron-icon-builder": "^2.0.1",
"electron-log": "^4.4.8",
"electron-updater": "^6.1.4",
"eslint": "^8.46.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-vue": "^9.6.0",
"eslint-plugin-vuejs-accessibility": "^1.2.0",
"icon-gen": "^3.0.1",
"js-yaml-loader": "^1.2.2",
"markdownlint-cli": "^0.29.0",
"raw-loader": "^4.0.2",
"remark-cli": "^10.0.0",
"markdownlint-cli": "^0.35.0",
"remark-cli": "^11.0.0",
"remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^5.1.0",
"remark-validate-links": "^11.0.1",
"sass": "^1.43.3",
"sass-loader": "10.2.0",
"ts-loader": "9.0.1",
"tslib": "^2.3.1",
"typescript": "^4.4.4",
"vue-cli-plugin-electron-builder": "^2.1.1",
"vue-template-compiler": "^2.6.14",
"yaml-lint": "^1.2.4"
"remark-preset-lint-consistent": "^5.1.2",
"remark-validate-links": "^12.1.1",
"sass": "^1.64.1",
"sass-loader": "^13.3.2",
"svgexport": "^0.4.2",
"ts-loader": "^9.4.4",
"tslib": "~2.4.0",
"typescript": "~4.6.2",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4",
"yaml-lint": "^1.7.0"
},
"overrides": {
"vue-cli-plugin-electron-builder": {
"electron-builder": "^24.6.3"
}
},
"//devDependencies": {
"ts-loader": "Here as workaround for vue-cli-plugin-electron-builder using older webpack 4"
"typescript": [
"Cannot upgrade to 5.X.X due to unmaintained @vue/cli-plugin-typescript, https://github.com/vuejs/vue-cli/issues/7401",
"Cannot upgrade to > 4.6.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252"
],
"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",
"repository": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -2,9 +2,8 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Privacy is sexy 🍑🍆 - Enforce privacy & security on Windows and macOS</title>
<title>Privacy is sexy 🍑🍆 - Enforce privacy & security on Windows, macOS and Linux</title>
<meta name="robots" content="index,follow" />
<meta name="description" content="Web tool to generate scripts for enforcing privacy & security best-practices such as stopping data collection of Windows and different softwares on it."/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">

74
scripts/configure-vscode.sh Executable file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# This script ensures that the '.vscode/settings.json' file exists and is configured correctly for ESLint validation on Vue and JavaScript files.
# See https://web.archive.org/web/20230801024405/https://eslint.vuejs.org/user-guide/#visual-studio-code
declare -r SETTINGS_FILE='.vscode/settings.json'
declare -ra CONFIG_KEYS=('vue' 'javascript' 'typescript')
declare -r TEMP_FILE="tmp.$$.json"
main() {
ensure_vscode_directory_exists
create_or_update_settings
}
ensure_vscode_directory_exists() {
local dir_name
dir_name=$(dirname "${SETTINGS_FILE}")
if [[ ! -d ${dir_name} ]]; then
mkdir -p "${dir_name}"
echo "🎉 Created directory: ${dir_name}"
fi
}
create_or_update_settings() {
if [[ ! -f ${SETTINGS_FILE} ]]; then
create_default_settings
else
add_or_update_eslint_validate
fi
}
create_default_settings() {
local default_validate
default_validate=$(printf '%s' "${CONFIG_KEYS[*]}" | jq -R -s -c -M 'split(" ")')
echo "{ \"eslint.validate\": ${default_validate} }" | jq '.' > "${SETTINGS_FILE}"
echo "🎉 Created default ${SETTINGS_FILE}"
}
add_or_update_eslint_validate() {
if ! jq -e '.["eslint.validate"]' "${SETTINGS_FILE}" >/dev/null; then
add_default_eslint_validate
else
update_eslint_validate
fi
}
add_default_eslint_validate() {
jq --argjson keys "$(printf '%s' "${CONFIG_KEYS[*]}" \
| jq -R -s -c 'split(" ")')" '. += {"eslint.validate": $keys}' "${SETTINGS_FILE}" > "${TEMP_FILE}"
replace_and_confirm
echo "🎉 Added default 'eslint.validate' to ${SETTINGS_FILE}"
}
update_eslint_validate() {
local existing_keys
existing_keys=$(jq '.["eslint.validate"]' "${SETTINGS_FILE}")
for key in "${CONFIG_KEYS[@]}"; do
if ! echo "${existing_keys}" | jq 'index("'"${key}"'")' >/dev/null; then
jq '.["eslint.validate"] += ["'"${key}"'"]' "${SETTINGS_FILE}" > "${TEMP_FILE}"
mv "${TEMP_FILE}" "${SETTINGS_FILE}"
echo "🎉 Updated 'eslint.validate' in ${SETTINGS_FILE} for ${key}"
else
echo "⏩️ No updated needed for ${key} ${SETTINGS_FILE}."
fi
done
}
replace_and_confirm() {
if mv "${TEMP_FILE}" "${SETTINGS_FILE}"; then
echo "🎉 Updated ${SETTINGS_FILE}"
fi
}
main

95
scripts/fresh-npm-install.sh Executable file
View File

@@ -0,0 +1,95 @@
#!/usr/bin/env bash
# Description:
# This script ensures npm is available, removes existing node modules, optionally
# removes package-lock.json (when -n flag is used), installs dependencies and runs unit tests.
# Usage:
# ./fresh-npm-install.sh # Regular execution
# ./fresh-npm-install.sh -n # Non-deterministic mode (removes package-lock.json)
declare NON_DETERMINISTIC_FLAG=0
main() {
parse_args "$@"
ensure_npm_is_available
ensure_npm_root
remove_existing_modules
if [[ $NON_DETERMINISTIC_FLAG -eq 1 ]]; then
remove_package_lock_json
fi
install_dependencies
run_unit_tests
}
ensure_npm_is_available() {
if ! command -v npm &> /dev/null; then
log::fatal 'npm could not be found, please install it first.'
fi
}
ensure_npm_root() {
if [ ! -f package.json ]; then
log::fatal 'Current directory is not a npm root. Please run the script in a npm root directory.'
fi
}
remove_existing_modules() {
if [ -d ./node_modules ]; then
log::info 'Removing existing node modules...'
if ! rm -rf ./node_modules; then
log::fatal 'Could not remove existing node modules.'
fi
fi
}
install_dependencies() {
log::info 'Installing dependencies...'
if ! npm install; then
log::fatal 'Failed to install dependencies.'
fi
}
remove_package_lock_json() {
if [ -f ./package-lock.json ]; then
log::info 'Removing package-lock.json...'
if ! rm -rf ./package-lock.json; then
log::fatal 'Could not remove package-lock.json.'
fi
fi
}
run_unit_tests() {
log::info 'Running unit tests...'
if ! npm run test:unit; then
pwd
log::fatal 'Failed to run unit tests.'
fi
}
log::info() {
local -r message="$1"
echo "📣 ${message}"
}
log::fatal() {
local -r message="$1"
echo "${message}" >&2
exit 1
}
parse_args() {
while getopts "n" opt; do
case ${opt} in
n)
NON_DETERMINISTIC_FLAG=1
;;
\?)
echo "Invalid option: $OPTARG" 1>&2
exit 1
;;
esac
done
}
main "$1"

View File

@@ -3,17 +3,17 @@ import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { IApplicationFactory } from './IApplicationFactory';
import { parseApplication } from './Parser/ApplicationParser';
export type ApplicationGetter = () => IApplication;
const ApplicationGetter: ApplicationGetter = parseApplication;
export type ApplicationGetterType = () => IApplication;
const ApplicationGetter: ApplicationGetterType = parseApplication;
export class ApplicationFactory implements IApplicationFactory {
public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter);
private readonly getter: AsyncLazy<IApplication>;
protected constructor(costlyGetter: ApplicationGetter) {
protected constructor(costlyGetter: ApplicationGetterType) {
if (!costlyGetter) {
throw new Error('undefined getter');
throw new Error('missing getter');
}
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
}

View File

@@ -1,7 +1,7 @@
// Compares to Array<T> objects for equality, ignoring order
export function scrambledEqual<T>(array1: readonly T[], array2: readonly T[]) {
if (!array1) { throw new Error('undefined first array'); }
if (!array2) { throw new Error('undefined second array'); }
if (!array1) { throw new Error('missing first array'); }
if (!array2) { throw new Error('missing second array'); }
const sortedArray1 = sort(array1);
const sortedArray2 = sort(array2);
return sequenceEqual(sortedArray1, sortedArray2);
@@ -12,8 +12,8 @@ export function scrambledEqual<T>(array1: readonly T[], array2: readonly T[]) {
// Compares to Array<T> objects for equality in same order
export function sequenceEqual<T>(array1: readonly T[], array2: readonly T[]) {
if (!array1) { throw new Error('undefined first array'); }
if (!array2) { throw new Error('undefined second array'); }
if (!array1) { throw new Error('missing first array'); }
if (!array2) { throw new Error('missing second array'); }
if (array1.length !== array2.length) {
return false;
}

View File

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

View File

@@ -21,7 +21,7 @@ function parseEnumValue<T extends EnumType, TEnumValue extends EnumType>(
enumVariable: EnumVariable<T, TEnumValue>,
): TEnumValue {
if (!value) {
throw new Error(`undefined ${enumName}`);
throw new Error(`missing ${enumName}`);
}
if (typeof value !== 'string') {
throw new Error(`unexpected type of ${enumName}: "${typeof value}"`);
@@ -54,8 +54,8 @@ export function assertInRange<T extends EnumType, TEnumValue extends EnumType>(
value: TEnumValue,
enumVariable: EnumVariable<T, TEnumValue>,
) {
if (value === undefined) {
throw new Error('undefined enum value');
if (value === undefined || value === null) {
throw new Error('absent enum value');
}
if (!(value in enumVariable)) {
throw new RangeError(`enum value "${value}" is out of range`);

View File

@@ -20,7 +20,7 @@ export abstract class ScriptingLanguageFactory<T> implements IScriptingLanguageF
protected registerGetter(language: ScriptingLanguage, getter: Getter<T>) {
assertInRange(language, ScriptingLanguage);
if (!getter) {
throw new Error('undefined getter');
throw new Error('missing getter');
}
if (this.getters.has(language)) {
throw new Error(`${ScriptingLanguage[language]} is already registered`);

View File

@@ -27,12 +27,12 @@ export class ApplicationContext implements IApplicationContext {
initialContext: OperatingSystem,
) {
validateApp(app);
assertInRange(initialContext, OperatingSystem);
this.states = initializeStates(app);
this.changeContext(initialContext);
}
public changeContext(os: OperatingSystem): void {
assertInRange(os, OperatingSystem);
if (this.currentOs === os) {
return;
}
@@ -51,7 +51,7 @@ export class ApplicationContext implements IApplicationContext {
function validateApp(app: IApplication) {
if (!app) {
throw new Error('undefined app');
throw new Error('missing app');
}
}

View File

@@ -11,8 +11,8 @@ export async function buildContext(
factory: IApplicationFactory = ApplicationFactory.Current,
environment = Environment.CurrentEnvironment,
): Promise<IApplicationContext> {
if (!factory) { throw new Error('undefined factory'); }
if (!environment) { throw new Error('undefined environment'); }
if (!factory) { throw new Error('missing factory'); }
if (!environment) { throw new Error('missing environment'); }
const app = await factory.getApp();
const os = getInitialOs(app, environment);
return new ApplicationContext(app, os);

View File

@@ -10,8 +10,8 @@ export interface IReadOnlyApplicationContext {
}
export interface IApplicationContext extends IReadOnlyApplicationContext {
readonly state: ICategoryCollectionState;
changeContext(os: OperatingSystem): void;
readonly state: ICategoryCollectionState;
changeContext(os: OperatingSystem): void;
}
export interface IApplicationContextChangedEvent {

View File

@@ -21,9 +21,9 @@ export class ApplicationCode implements IApplicationCode {
private readonly scriptingDefinition: IScriptingDefinition,
private readonly generator: IUserScriptGenerator = new UserScriptGenerator(),
) {
if (!userSelection) { throw new Error('userSelection is null or undefined'); }
if (!scriptingDefinition) { throw new Error('scriptingDefinition is null or undefined'); }
if (!generator) { throw new Error('generator is null or undefined'); }
if (!userSelection) { throw new Error('missing userSelection'); }
if (!scriptingDefinition) { throw new Error('missing scriptingDefinition'); }
if (!generator) { throw new Error('missing generator'); }
this.setCode(userSelection.selectedScripts);
userSelection.changed.on((scripts) => {
this.setCode(scripts);

View File

@@ -1,6 +1,5 @@
import { ICodeBuilder } from './ICodeBuilder';
const NewLine = '\n';
const TotalFunctionSeparatorChars = 58;
export abstract class CodeBuilder implements ICodeBuilder {
@@ -59,10 +58,12 @@ export abstract class CodeBuilder implements ICodeBuilder {
}
public toString(): string {
return this.lines.join(NewLine);
return this.lines.join(this.getNewLineTerminator());
}
protected abstract getCommentDelimiter(): string;
protected abstract writeStandardOut(text: string): string;
protected abstract getNewLineTerminator(): string;
}

View File

@@ -8,6 +8,10 @@ export class BatchBuilder extends CodeBuilder {
protected writeStandardOut(text: string): string {
return `echo ${escapeForEcho(text)}`;
}
protected getNewLineTerminator(): string {
return '\r\n';
}
}
function escapeForEcho(text: string) {

View File

@@ -8,6 +8,10 @@ export class ShellBuilder extends CodeBuilder {
protected writeStandardOut(text: string): string {
return `echo '${escapeForEcho(text)}'`;
}
protected getNewLineTerminator(): string {
return '\n';
}
}
function escapeForEcho(text: string) {

View File

@@ -17,8 +17,8 @@ export class UserScriptGenerator implements IUserScriptGenerator {
selectedScripts: ReadonlyArray<SelectedScript>,
scriptingDefinition: IScriptingDefinition,
): IUserScript {
if (!selectedScripts) { throw new Error('undefined scripts'); }
if (!scriptingDefinition) { throw new Error('undefined definition'); }
if (!selectedScripts) { throw new Error('missing scripts'); }
if (!scriptingDefinition) { throw new Error('missing definition'); }
if (!selectedScripts.length) {
return { code: '', scriptPositions: new Map<SelectedScript, ICodePosition>() };
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -109,6 +109,9 @@ export class UserSelection implements IUserSelection {
.getAllScripts()
.filter((script) => !this.scripts.exists(script.id))
.map((script) => new SelectedScript(script, false));
if (scriptsToSelect.length === 0) {
return;
}
for (const script of scriptsToSelect) {
this.scripts.addItem(script);
}
@@ -116,6 +119,9 @@ export class UserSelection implements IUserSelection {
}
public deselectAll(): void {
if (this.scripts.length === 0) {
return;
}
const selectedScriptIds = this.scripts.getItems().map((script) => script.id);
for (const scriptId of selectedScriptIds) {
this.scripts.removeItem(scriptId);
@@ -127,20 +133,35 @@ export class UserSelection implements IUserSelection {
if (!scripts || scripts.length === 0) {
throw new Error('Scripts are empty. Use deselectAll() if you want to deselect everything');
}
// Unselect from selected scripts
if (this.scripts.length !== 0) {
this.scripts.getItems()
.filter((existing) => !scripts.some((script) => existing.id === script.id))
.map((script) => script.id)
.forEach((scriptId) => this.scripts.removeItem(scriptId));
let totalChanged = 0;
totalChanged += this.unselectMissingWithoutNotifying(scripts);
totalChanged += this.selectNewWithoutNotifying(scripts);
if (totalChanged > 0) {
this.changed.notify(this.scripts.getItems());
}
// Select from unselected scripts
}
private unselectMissingWithoutNotifying(scripts: readonly IScript[]): number {
if (this.scripts.length === 0 || scripts.length === 0) {
return 0;
}
const existingItems = this.scripts.getItems();
const missingIds = existingItems
.filter((existing) => !scripts.some((script) => existing.id === script.id))
.map((script) => script.id);
for (const id of missingIds) {
this.scripts.removeItem(id);
}
return missingIds.length;
}
private selectNewWithoutNotifying(scripts: readonly IScript[]): number {
const unselectedScripts = scripts
.filter((script) => !this.scripts.exists(script.id))
.map((script) => new SelectedScript(script, false));
for (const toSelect of unselectedScripts) {
this.scripts.addItem(toSelect);
for (const newScript of unselectedScripts) {
this.scripts.addItem(newScript);
}
this.changed.notify(this.scripts.getItems());
return unselectedScripts.length;
}
}

View File

@@ -27,7 +27,7 @@ export class DetectorBuilder {
private detect(userAgent: string): OperatingSystem {
if (!userAgent) {
throw new Error('User agent is null or undefined');
throw new Error('missing userAgent');
}
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
return undefined;

View File

@@ -1,9 +1,10 @@
import { CollectionData } from 'js-yaml-loader!@/*';
import type { CollectionData } from '@/application/collections/';
import { IApplication } from '@/domain/IApplication';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import WindowsData from 'js-yaml-loader!@/application/collections/windows.yaml';
import MacOsData from 'js-yaml-loader!@/application/collections/macos.yaml';
import WindowsData from '@/application/collections/windows.yaml';
import MacOsData from '@/application/collections/macos.yaml';
import LinuxData from '@/application/collections/linux.yaml';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { Application } from '@/domain/Application';
import { parseCategoryCollection } from './CategoryCollectionParser';
@@ -28,14 +29,14 @@ const CategoryCollectionParser: CategoryCollectionParserType = (file, info) => {
};
const PreParsedCollections: readonly CollectionData [] = [
WindowsData, MacOsData,
WindowsData, MacOsData, LinuxData,
];
function validateCollectionsData(collections: readonly CollectionData[]) {
if (!collections.length) {
throw new Error('no collection provided');
if (!collections || !collections.length) {
throw new Error('missing collections');
}
if (collections.some((collection) => !collection)) {
throw new Error('undefined collection provided');
throw new Error('missing collection provided');
}
}

View File

@@ -1,4 +1,4 @@
import { CollectionData } from 'js-yaml-loader!@/*';
import type { CollectionData } from '@/application/collections/';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollection } from '@/domain/CategoryCollection';
@@ -29,7 +29,7 @@ export function parseCategoryCollection(
function validate(content: CollectionData): void {
if (!content) {
throw new Error('content is null or undefined');
throw new Error('missing content');
}
if (!content.actions || content.actions.length <= 0) {
throw new Error('content does not define any action');

View File

@@ -1,9 +1,11 @@
import {
import type {
CategoryData, ScriptData, CategoryOrScriptData, InstructionHolder,
} from 'js-yaml-loader!@/*';
} from '@/application/collections/';
import { Script } from '@/domain/Script';
import { Category } from '@/domain/Category';
import { parseDocUrls } from './DocumentationParser';
import { NodeValidator } from '@/application/Parser/NodeValidation/NodeValidator';
import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
import { parseDocs } from './DocumentationParser';
import { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext';
import { parseScript } from './Script/ScriptParser';
@@ -12,35 +14,67 @@ let categoryIdCounter = 0;
export function parseCategory(
category: CategoryData,
context: ICategoryCollectionParseContext,
factory: CategoryFactoryType = CategoryFactory,
): Category {
if (!context) { throw new Error('undefined context'); }
ensureValid(category);
if (!context) { throw new Error('missing context'); }
return parseCategoryRecursively({
categoryData: category,
context,
factory,
});
}
interface ICategoryParseContext {
readonly categoryData: CategoryData,
readonly context: ICategoryCollectionParseContext,
readonly factory: CategoryFactoryType,
readonly parentCategory?: CategoryData,
}
// eslint-disable-next-line consistent-return
function parseCategoryRecursively(context: ICategoryParseContext): Category {
ensureValidCategory(context.categoryData, context.parentCategory);
const children: ICategoryChildren = {
subCategories: new Array<Category>(),
subScripts: new Array<Script>(),
};
for (const data of category.children) {
parseCategoryChild(data, children, category, context);
for (const data of context.categoryData.children) {
parseNode({
nodeData: data,
children,
parent: context.categoryData,
factory: context.factory,
context: context.context,
});
}
try {
return context.factory(
/* id: */ categoryIdCounter++,
/* name: */ context.categoryData.category,
/* docs: */ parseDocs(context.categoryData),
/* categories: */ children.subCategories,
/* scripts: */ children.subScripts,
);
} catch (err) {
new NodeValidator({
type: NodeType.Category,
selfNode: context.categoryData,
parentNode: context.parentCategory,
}).throw(err.message);
}
return new Category(
/* id: */ categoryIdCounter++,
/* name: */ category.category,
/* docs: */ parseDocUrls(category),
/* categories: */ children.subCategories,
/* scripts: */ children.subScripts,
);
}
function ensureValid(category: CategoryData) {
if (!category) {
throw Error('category is null or undefined');
}
if (!category.children || category.children.length === 0) {
throw Error(`category has no children: "${category.category}"`);
}
if (!category.category || category.category.length === 0) {
throw Error('category has no name');
}
function ensureValidCategory(category: CategoryData, parentCategory?: CategoryData) {
new NodeValidator({
type: NodeType.Category,
selfNode: category,
parentNode: parentCategory,
})
.assertDefined(category)
.assertValidName(category.category)
.assert(
() => category.children && category.children.length > 0,
`"${category.category}" has no children.`,
);
}
interface ICategoryChildren {
@@ -48,22 +82,29 @@ interface ICategoryChildren {
subScripts: Script[];
}
function parseCategoryChild(
data: CategoryOrScriptData,
children: ICategoryChildren,
parent: CategoryData,
context: ICategoryCollectionParseContext,
) {
if (isCategory(data)) {
const subCategory = parseCategory(data as CategoryData, context);
children.subCategories.push(subCategory);
} else if (isScript(data)) {
const scriptData = data as ScriptData;
const script = parseScript(scriptData, context);
children.subScripts.push(script);
interface INodeParseContext {
readonly nodeData: CategoryOrScriptData;
readonly children: ICategoryChildren;
readonly parent: CategoryData;
readonly factory: CategoryFactoryType;
readonly context: ICategoryCollectionParseContext;
}
function parseNode(context: INodeParseContext) {
const validator = new NodeValidator({ selfNode: context.nodeData, parentNode: context.parent });
validator.assertDefined(context.nodeData);
if (isCategory(context.nodeData)) {
const subCategory = parseCategoryRecursively({
categoryData: context.nodeData as CategoryData,
context: context.context,
factory: context.factory,
parentCategory: context.parent,
});
context.children.subCategories.push(subCategory);
} else if (isScript(context.nodeData)) {
const script = parseScript(context.nodeData as ScriptData, context.context);
context.children.subScripts.push(script);
} else {
throw new Error(`Child element is neither a category or a script.
Parent: ${parent.category}, element: ${JSON.stringify(data)}`);
validator.throw('Node is neither a category or a script.');
}
}
@@ -73,14 +114,22 @@ function isScript(data: CategoryOrScriptData): data is ScriptData {
}
function isCategory(data: CategoryOrScriptData): data is CategoryData {
const { category } = data as CategoryData;
return category && category.length > 0;
return hasProperty(data, 'category');
}
function hasCode(holder: InstructionHolder): boolean {
return holder.code && holder.code.length > 0;
function hasCode(data: InstructionHolder): boolean {
return hasProperty(data, 'code');
}
function hasCall(holder: InstructionHolder) {
return holder.call !== undefined;
function hasCall(data: InstructionHolder) {
return hasProperty(data, 'call');
}
function hasProperty(object: unknown, propertyName: string) {
return Object.prototype.hasOwnProperty.call(object, propertyName);
}
export type CategoryFactoryType = (
...parameters: ConstructorParameters<typeof Category>) => Category;
const CategoryFactory: CategoryFactoryType = (...parameters) => new Category(...parameters);

View File

@@ -1,64 +1,58 @@
import { DocumentableData, DocumentationUrlsData } from 'js-yaml-loader!@/*';
import type { DocumentableData, DocumentationData } from '@/application/collections/';
export function parseDocUrls(documentable: DocumentableData): ReadonlyArray<string> {
export function parseDocs(documentable: DocumentableData): readonly string[] {
if (!documentable) {
throw new Error('documentable is null or undefined');
throw new Error('missing documentable');
}
const { docs } = documentable;
if (!docs || !docs.length) {
if (!docs) {
return [];
}
let result = new DocumentationUrlContainer();
let result = new DocumentationContainer();
result = addDocs(docs, result);
return result.getAll();
}
function addDocs(
docs: DocumentationUrlsData,
urls: DocumentationUrlContainer,
): DocumentationUrlContainer {
docs: DocumentationData,
container: DocumentationContainer,
): DocumentationContainer {
if (docs instanceof Array) {
urls.addUrls(docs);
if (docs.length > 0) {
container.addParts(docs);
}
} else if (typeof docs === 'string') {
urls.addUrl(docs);
container.addPart(docs);
} else {
throw new Error('Docs field (documentation url) must a string or array of strings');
throwInvalidType();
}
return urls;
return container;
}
class DocumentationUrlContainer {
private readonly urls = new Array<string>();
class DocumentationContainer {
private readonly parts = new Array<string>();
public addUrl(url: string) {
validateUrl(url);
this.urls.push(url);
public addPart(documentation: string) {
if (!documentation) {
throw Error('missing documentation');
}
if (typeof documentation !== 'string') {
throwInvalidType();
}
this.parts.push(documentation);
}
public addUrls(urls: readonly string[]) {
for (const url of urls) {
if (typeof url !== 'string') {
throw new Error('Docs field (documentation url) must be an array of strings');
}
this.addUrl(url);
public addParts(parts: readonly string[]) {
for (const part of parts) {
this.addPart(part);
}
}
public getAll(): ReadonlyArray<string> {
return this.urls;
return this.parts;
}
}
function validateUrl(docUrl: string): void {
if (!docUrl) {
throw new Error('Documentation url is null or empty');
}
if (docUrl.includes('\n')) {
throw new Error('Documentation url cannot be multi-lined.');
}
const validUrlRegex = /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/g;
const res = docUrl.match(validUrlRegex);
if (res == null) {
throw new Error(`Invalid documentation url: ${docUrl}`);
}
function throwInvalidType() {
throw new Error('docs field (documentation) must be an array of strings');
}

View File

@@ -0,0 +1,3 @@
import type { ScriptData, CategoryData } from '@/application/collections/';
export type NodeData = CategoryData | ScriptData;

View File

@@ -0,0 +1,34 @@
import { CustomError } from '@/application/Common/CustomError';
import { NodeType } from './NodeType';
import { NodeData } from './NodeData';
export class NodeDataError extends CustomError {
constructor(message: string, public readonly context: INodeDataErrorContext) {
super(createMessage(message, context));
}
}
export interface INodeDataErrorContext {
readonly type?: NodeType;
readonly selfNode: NodeData;
readonly parentNode?: NodeData;
}
function createMessage(errorMessage: string, context: INodeDataErrorContext) {
let message = '';
if (context.type !== undefined) {
message += `${NodeType[context.type]}: `;
}
message += errorMessage;
message += `\n${dump(context)}`;
return message;
}
function dump(context: INodeDataErrorContext): string {
const printJson = (obj: unknown) => JSON.stringify(obj, undefined, 2);
let output = `Self: ${printJson(context.selfNode)}`;
if (context.parentNode) {
output += `\nParent: ${printJson(context.parentNode)}`;
}
return output;
}

View File

@@ -0,0 +1,4 @@
export enum NodeType {
Script,
Category,
}

View File

@@ -0,0 +1,38 @@
import { INodeDataErrorContext, NodeDataError } from './NodeDataError';
import { NodeData } from './NodeData';
export class NodeValidator {
constructor(private readonly context: INodeDataErrorContext) {
}
public assertValidName(nameValue: string) {
return this
.assert(
() => Boolean(nameValue),
'missing name',
)
.assert(
() => typeof nameValue === 'string',
`Name (${JSON.stringify(nameValue)}) is not a string but ${typeof nameValue}.`,
);
}
public assertDefined(node: NodeData) {
return this.assert(
() => node !== undefined && node !== null && Object.keys(node).length > 0,
'missing node data',
);
}
public assert(validationPredicate: () => boolean, errorMessage: string) {
if (!validationPredicate()) {
this.throw(errorMessage);
}
return this;
}
public throw(errorMessage: string) {
throw new NodeDataError(errorMessage, this.context);
}
}

View File

@@ -1,13 +1,28 @@
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ProjectInformation } from '@/domain/ProjectInformation';
import { Version } from '@/domain/Version';
export function parseProjectInformation(
environment: NodeJS.ProcessEnv,
environment: NodeJS.ProcessEnv | VueAppEnvironment,
): IProjectInformation {
const version = new Version(environment[VueAppEnvironmentKeys.VUE_APP_VERSION]);
return new ProjectInformation(
environment.VUE_APP_NAME,
environment.VUE_APP_VERSION,
environment.VUE_APP_REPOSITORY_URL,
environment.VUE_APP_HOMEPAGE_URL,
environment[VueAppEnvironmentKeys.VUE_APP_NAME],
version,
environment[VueAppEnvironmentKeys.VUE_APP_SLOGAN],
environment[VueAppEnvironmentKeys.VUE_APP_REPOSITORY_URL],
environment[VueAppEnvironmentKeys.VUE_APP_HOMEPAGE_URL],
);
}
export const VueAppEnvironmentKeys = {
VUE_APP_VERSION: 'VUE_APP_VERSION',
VUE_APP_NAME: 'VUE_APP_NAME',
VUE_APP_SLOGAN: 'VUE_APP_SLOGAN',
VUE_APP_REPOSITORY_URL: 'VUE_APP_REPOSITORY_URL',
VUE_APP_HOMEPAGE_URL: 'VUE_APP_HOMEPAGE_URL',
} as const;
export type VueAppEnvironment = {
[K in keyof typeof VueAppEnvironmentKeys]: string;
};

View File

@@ -1,11 +1,11 @@
import { FunctionData } from 'js-yaml-loader!@/*';
import type { FunctionData } from '@/application/collections/';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { ScriptCompiler } from './Compiler/ScriptCompiler';
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
import { SyntaxFactory } from './Syntax/SyntaxFactory';
import { ISyntaxFactory } from './Syntax/ISyntaxFactory';
import { SyntaxFactory } from './Validation/Syntax/SyntaxFactory';
import { ISyntaxFactory } from './Validation/Syntax/ISyntaxFactory';
import { ILanguageSyntax } from './Validation/Syntax/ILanguageSyntax';
export class CategoryCollectionParseContext implements ICategoryCollectionParseContext {
public readonly compiler: IScriptCompiler;
@@ -17,7 +17,7 @@ export class CategoryCollectionParseContext implements ICategoryCollectionParseC
scripting: IScriptingDefinition,
syntaxFactory: ISyntaxFactory = new SyntaxFactory(),
) {
if (!scripting) { throw new Error('undefined scripting'); }
if (!scripting) { throw new Error('missing scripting'); }
this.syntax = syntaxFactory.create(scripting.language);
this.compiler = new ScriptCompiler(functionsData, this.syntax);
}

View File

@@ -8,23 +8,25 @@ import { ExpressionEvaluationContext, IExpressionEvaluationContext } from './Exp
export type ExpressionEvaluator = (context: IExpressionEvaluationContext) => string;
export class Expression implements IExpression {
public readonly parameters: IReadOnlyFunctionParameterCollection;
constructor(
public readonly position: ExpressionPosition,
public readonly evaluator: ExpressionEvaluator,
public readonly parameters
: IReadOnlyFunctionParameterCollection = new FunctionParameterCollection(),
parameters?: IReadOnlyFunctionParameterCollection,
) {
if (!position) {
throw new Error('undefined position');
throw new Error('missing position');
}
if (!evaluator) {
throw new Error('undefined evaluator');
throw new Error('missing evaluator');
}
this.parameters = parameters ?? new FunctionParameterCollection();
}
public evaluate(context: IExpressionEvaluationContext): string {
if (!context) {
throw new Error('undefined context');
throw new Error('missing context');
}
validateThatAllRequiredParametersAreSatisfied(this.parameters, context.args);
const args = filterUnusedArguments(this.parameters, context.args);

View File

@@ -13,7 +13,7 @@ export class ExpressionEvaluationContext implements IExpressionEvaluationContext
public readonly pipelineCompiler: IPipelineCompiler = new PipelineCompiler(),
) {
if (!args) {
throw new Error('undefined args, send empty collection instead');
throw new Error('missing args, send empty collection instead.');
}
}
}

View File

@@ -13,4 +13,22 @@ export class ExpressionPosition {
throw Error(`negative start position: ${start}`);
}
}
public isInInsideOf(potentialParent: ExpressionPosition): boolean {
if (this.isSame(potentialParent)) {
return false;
}
return potentialParent.start <= this.start
&& potentialParent.end >= this.end;
}
public isSame(other: ExpressionPosition): boolean {
return other.start === this.start
&& other.end === this.end;
}
public isIntersecting(other: ExpressionPosition): boolean {
return (other.start < this.end && other.end > this.start)
|| (this.end > other.start && other.start >= this.start);
}
}

View File

@@ -15,26 +15,64 @@ export class ExpressionsCompiler implements IExpressionsCompiler {
args: IReadOnlyFunctionCallArgumentCollection,
): string {
if (!args) {
throw new Error('undefined args, send empty collection instead');
throw new Error('missing args, send empty collection instead.');
}
if (!code) {
return code;
}
const expressions = this.extractor.findExpressions(code);
ensureParamsUsedInCodeHasArgsProvided(expressions, args);
const context = new ExpressionEvaluationContext(args);
const compiledCode = compileExpressions(expressions, code, context);
const compiledCode = compileRecursively(code, context, this.extractor);
return compiledCode;
}
}
function compileRecursively(
code: string,
context: IExpressionEvaluationContext,
extractor: IExpressionParser,
): string {
/*
Instead of compiling code at once and returning we compile expressions from the code.
And recompile expressions from resulting code recursively.
This allows using expressions inside expressions blocks. E.g.:
```
{{ with $condition }}
echo '{{ $text }}'
{{ end }}
```
Without recursing parameter substitution for '{{ $text }}' is skipped once the outer
{{ with $condition }} is rendered.
A more optimized alternative to recursion would be to a parse an expression tree
instead of linear expression lists.
*/
if (!code) {
return code;
}
const expressions = extractor.findExpressions(code);
if (expressions.length === 0) {
return code;
}
const compiledCode = compileExpressions(expressions, code, context);
return compileRecursively(compiledCode, context, extractor);
}
function compileExpressions(
expressions: readonly IExpression[],
code: string,
context: IExpressionEvaluationContext,
) {
ensureValidExpressions(expressions, code, context);
let compiledCode = '';
const sortedExpressions = expressions
const outerExpressions = expressions.filter(
(expression) => expressions
.filter((otherExpression) => otherExpression !== expression)
.every((otherExpression) => !expression.position.isInInsideOf(otherExpression.position)),
);
/*
This logic will only compile outer expressions if there were nested expressions.
So the output of this compilation may result in new uncompiled expressions.
*/
const sortedExpressions = outerExpressions
.slice() // copy the array to not mutate the parameter
.sort((a, b) => b.position.start - a.position.start);
let index = 0;
@@ -65,6 +103,43 @@ function extractRequiredParameterNames(
.filter((name, index, array) => array.indexOf(name) === index); // Remove duplicates
}
function printList(list: readonly string[]): string {
return `"${list.join('", "')}"`;
}
function ensureValidExpressions(
expressions: readonly IExpression[],
code: string,
context: IExpressionEvaluationContext,
) {
ensureParamsUsedInCodeHasArgsProvided(expressions, context.args);
ensureExpressionsDoesNotExtendCodeLength(expressions, code);
ensureNoExpressionsAtSamePosition(expressions);
ensureNoInvalidIntersections(expressions);
}
function ensureExpressionsDoesNotExtendCodeLength(
expressions: readonly IExpression[],
code: string,
) {
const expectedMax = code.length;
const expressionsOutOfRange = expressions
.filter((expression) => expression.position.end > expectedMax);
if (expressionsOutOfRange.length > 0) {
throw new Error(`Expressions out of range:\n${JSON.stringify(expressionsOutOfRange)}`);
}
}
function ensureNoExpressionsAtSamePosition(expressions: readonly IExpression[]) {
const instructionsAtSamePosition = expressions.filter(
(expression) => expressions
.filter((other) => expression.position.isSame(other.position)).length > 1,
);
if (instructionsAtSamePosition.length > 0) {
throw new Error(`Instructions at same position:\n${JSON.stringify(instructionsAtSamePosition)}`);
}
}
function ensureParamsUsedInCodeHasArgsProvided(
expressions: readonly IExpression[],
providedArgs: IReadOnlyFunctionCallArgumentCollection,
@@ -80,6 +155,16 @@ function ensureParamsUsedInCodeHasArgsProvided(
}
}
function printList(list: readonly string[]): string {
return `"${list.join('", "')}"`;
function ensureNoInvalidIntersections(expressions: readonly IExpression[]) {
const intersectingInstructions = expressions.filter(
(expression) => expressions
.filter((other) => expression.position.isIntersecting(other.position))
.filter((other) => !expression.position.isSame(other.position))
.filter((other) => !expression.position.isInInsideOf(other.position))
.filter((other) => !other.position.isInInsideOf(expression.position))
.length > 0,
);
if (intersectingInstructions.length > 0) {
throw new Error(`Instructions intersecting unexpectedly:\n${JSON.stringify(intersectingInstructions)}`);
}
}

View File

@@ -10,8 +10,11 @@ const Parsers = [
export class CompositeExpressionParser implements IExpressionParser {
public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {
if (!leafs) {
throw new Error('missing leafs');
}
if (leafs.some((leaf) => !leaf)) {
throw new Error('undefined leaf');
throw new Error('missing leaf');
}
}

View File

@@ -25,10 +25,10 @@ export class ExpressionRegexBuilder {
.addRawRegex('([^|\\s]+)');
}
public matchAnythingExceptSurroundingWhitespaces() {
public matchMultilineAnythingExceptSurroundingWhitespaces() {
return this
.expectZeroOrMoreWhitespaces()
.addRawRegex('(.+?)')
.addRawRegex('([\\S\\s]+?)')
.expectZeroOrMoreWhitespaces();
}

View File

@@ -16,7 +16,7 @@ export abstract class RegexParser implements IExpressionParser {
private* findRegexExpressions(code: string): Iterable<IExpression> {
if (!code) {
throw new Error('undefined code');
throw new Error('missing code');
}
const matches = code.matchAll(this.regex);
for (const match of matches) {

View File

@@ -4,8 +4,11 @@ export class EscapeDoubleQuotes implements IPipe {
public readonly name: string = 'escapeDoubleQuotes';
public apply(raw: string): string {
return raw?.replaceAll('"', '"^""');
/* eslint-disable max-len */
if (!raw) {
return raw;
}
return raw.replaceAll('"', '"^""');
/* eslint-disable vue/max-len */
/*
"^"" is the most robust and stable choice.
Other options:
@@ -25,6 +28,6 @@ export class EscapeDoubleQuotes implements IPipe {
Works when using "^"": `PowerShell -Command ""^""a& c"^"".length"`
A good explanation: https://stackoverflow.com/a/31413730
*/
/* eslint-enable max-len */
/* eslint-enable vue/max-len */
}
}

View File

@@ -15,8 +15,11 @@ export class PipeFactory implements IPipeFactory {
private readonly pipes = new Map<string, IPipe>();
constructor(pipes: readonly IPipe[] = RegisteredPipes) {
if (!pipes) {
throw new Error('missing pipes');
}
if (pipes.some((pipe) => !pipe)) {
throw new Error('undefined pipe in list');
throw new Error('missing pipe in list');
}
for (const pipe of pipes) {
this.registerPipe(pipe);

View File

@@ -23,8 +23,8 @@ function extractPipeNames(pipeline: string): string[] {
}
function ensureValidArguments(value: string, pipeline: string) {
if (!value) { throw new Error('undefined value'); }
if (!pipeline) { throw new Error('undefined pipeline'); }
if (!value) { throw new Error('missing value'); }
if (!pipeline) { throw new Error('missing pipeline'); }
if (!pipeline.trimStart().startsWith('|')) {
throw new Error('pipeline does not start with pipe');
}

View File

@@ -12,7 +12,7 @@ export class WithParser extends RegexParser {
.matchUntilFirstWhitespace() // First match: parameter name
.expectExpressionEnd()
// ...
.matchAnythingExceptSurroundingWhitespaces() // Second match: Scope text
.matchMultilineAnythingExceptSurroundingWhitespaces() // Second match: Scope text
// {{ end }}
.expectExpressionStart()
.expectCharacters('end')

View File

@@ -8,7 +8,7 @@ export class FunctionCallArgument implements IFunctionCallArgument {
) {
ensureValidParameterName(parameterName);
if (!argumentValue) {
throw new Error(`undefined argument value for "${parameterName}"`);
throw new Error(`missing argument value for "${parameterName}"`);
}
}
}

View File

@@ -6,7 +6,7 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl
public addArgument(argument: IFunctionCallArgument): void {
if (!argument) {
throw new Error('undefined argument');
throw new Error('missing argument');
}
if (this.hasArgument(argument.parameterName)) {
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
@@ -20,14 +20,14 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl
public hasArgument(parameterName: string): boolean {
if (!parameterName) {
throw new Error('undefined parameter name');
throw new Error('missing parameter name');
}
return this.arguments.has(parameterName);
}
public getArgument(parameterName: string): IFunctionCallArgument {
if (!parameterName) {
throw new Error('undefined parameter name');
throw new Error('missing parameter name');
}
const arg = this.arguments.get(parameterName);
if (!arg) {

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