Merge pull request #35 from sourcygen/develop

Upgrading technical stack (Angular/Electron)
This commit is contained in:
Amadou A. DIENE
2022-09-24 15:48:09 +02:00
committed by GitHub
40 changed files with 12702 additions and 15331 deletions

View File

@@ -1,11 +1,13 @@
{ {
"env": { "env": {
"browser": true, "browser": true,
"es6": true, "es2020": true,
"node": true "node": true
}, },
"plugins": ["@typescript-eslint", "import", "unicorn", "sonarjs"],
"extends": [ "extends": [
"eslint:recommended", "plugin:unicorn/recommended",
"plugin:sonarjs/recommended",
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:import/errors", "plugin:import/errors",
@@ -15,8 +17,8 @@
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"settings": { "settings": {
"import/resolver": { "import/resolver": {
"node": { "typescript": {
"extensions": [".js", ".jsx", ".ts", ".tsx"] "project": "workspaces/*/tsconfig.json"
} }
} }
}, },
@@ -33,6 +35,7 @@
{ {
"ignoreRestArgs": true "ignoreRestArgs": true
} }
] ],
"unicorn/prefer-module": "off"
} }
} }

View File

@@ -23,7 +23,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macOS-latest, ubuntu-latest, windows-latest] os: [macOS-latest, ubuntu-latest, windows-latest]
node-version: [12.x, 14.x, 16.x] node-version: [14.x, 16.x, 18.x]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/usr/bin/env sh
if [ -z "$husky_skip_init" ]; then if [ -z "$husky_skip_init" ]; then
debug () { debug () {
if [ "$HUSKY_DEBUG" = "1" ]; then if [ "$HUSKY_DEBUG" = "1" ]; then
@@ -6,7 +6,7 @@ if [ -z "$husky_skip_init" ]; then
fi fi
} }
readonly hook_name="$(basename "$0")" readonly hook_name="$(basename -- "$0")"
debug "starting $hook_name..." debug "starting $hook_name..."
if [ "$HUSKY" = "0" ]; then if [ "$HUSKY" = "0" ]; then
@@ -19,7 +19,8 @@ if [ -z "$husky_skip_init" ]; then
. ~/.huskyrc . ~/.huskyrc
fi fi
export readonly husky_skip_init=1 readonly husky_skip_init=1
export husky_skip_init
sh -e "$0" "$@" sh -e "$0" "$@"
exitCode="$?" exitCode="$?"
@@ -27,5 +28,9 @@ if [ -z "$husky_skip_init" ]; then
echo "husky - $hook_name hook exited with code $exitCode (error)" echo "husky - $hook_name hook exited with code $exitCode (error)"
fi fi
if [ $exitCode = 127 ]; then
echo "husky - command not found in PATH=$PATH"
fi
exit $exitCode exit $exitCode
fi fi

View File

@@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [3.2.0](https://github.com/sourcygen/electron-angular-quick-start/compare/v3.1.0...v3.2.0) (2022-09-24)
### Features
- angular upgrade - 13.3.2 => 14.2.3 ([fa6222a](https://github.com/sourcygen/electron-angular-quick-start/commit/fa6222a1a86c0528a09f901a97f61f374933e2a6))
- electron upgrade - 18.0.3 => 20.2.0 ([f1fce63](https://github.com/sourcygen/electron-angular-quick-start/commit/f1fce63e24fc8c0ca53a97a83e337ea36332e645))
## [3.1.0](https://github.com/sourcygen/electron-angular-quick-start/compare/v3.0.0...v3.1.0) (2022-04-07) ## [3.1.0](https://github.com/sourcygen/electron-angular-quick-start/compare/v3.0.0...v3.1.0) (2022-04-07)
## [3.0.0](https://github.com/sourcygen/electron-angular-quick-start/compare/v2.4.0...v3.0.0) (2022-04-07) ## [3.0.0](https://github.com/sourcygen/electron-angular-quick-start/compare/v2.4.0...v3.0.0) (2022-04-07)

View File

@@ -16,7 +16,7 @@ Depending on your need, putting up [Electron](https://www.electronjs.org/) and [
### Main features : ### Main features :
- This project is based on last [Angular 13](https://angular.io/) version with required dependencies for [Electron 18](https://www.electronjs.org/). - This project is based on last [Angular 14](https://angular.io/) version with required dependencies for [Electron 18](https://www.electronjs.org/).
- This project is also written in [Typescript 4](https://www.typescriptlang.org/) and includes test samples ([WebdriverIO](https://webdriver.io/) and [Jasmine](https://jasmine.github.io/)). - This project is also written in [Typescript 4](https://www.typescriptlang.org/) and includes test samples ([WebdriverIO](https://webdriver.io/) and [Jasmine](https://jasmine.github.io/)).
- The app is runnable `on desktop` (with **live-reload** in `development mode`). - The app is runnable `on desktop` (with **live-reload** in `development mode`).
- The app is also runnable `on browser` but **without Electron features**. - The app is also runnable `on browser` but **without Electron features**.
@@ -50,9 +50,9 @@ Depending on your need, putting up [Electron](https://www.electronjs.org/) and [
To clone and run this repository, you'll need installed on your computer at least : To clone and run this repository, you'll need installed on your computer at least :
- [Git](https://git-scm.com) - [Git](https://git-scm.com)
- [Node 12.20, 14.15 or 16.10](https://nodejs.org/en/download/) - [Node 14.15.0 or later](https://nodejs.org/en/download/)
- [Npm 7+](https://docs.npmjs.com/about-npm) - [Npm 7 or later](https://docs.npmjs.com/about-npm)
- [Angular-CLI 13](https://angular.io/cli) - [Angular-CLI 14 or later](https://angular.io/cli)
Then from your command line: Then from your command line:
@@ -83,11 +83,21 @@ npm start
| `npm run install` | Install dependencies | | `npm run install` | Install dependencies |
| `npm run start` | Run the app on desktop (dev mode) | | `npm run start` | Run the app on desktop (dev mode) |
| `npm run start:angular-app` | Run the app on browser (dev mode) | | `npm run start:angular-app` | Run the app on browser (dev mode) |
| `npm run test:angular-e2e` | Run **angular** end-to-end tests |
| `npm run test:electron-e2e` | Run **electron** end-to-end tests | | `npm run test:electron-e2e` | Run **electron** end-to-end tests |
| `npm run package` | Build and prepare application content | | `npm run package` | Build and prepare application content |
| `npm run make` | Generate platform distributables (./out) | | `npm run make` | Generate platform distributables (./out) |
| `npm run clean` | Delete generated outputs | | `npm run clean` | Delete generated outputs |
## Behind a proxy
After settings **HTTP_PROXY** and **HTTPS_PROXY** environment variables :
```bash
# Install dependencies
npx cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=%HTTPS_PROXY% npm install
```
### Adding dependencies ### Adding dependencies
This project architecture is based on [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces). This allows having different version of the same dependency depending on your workspace : This project architecture is based on [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces). This allows having different version of the same dependency depending on your workspace :

27468
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "electron-angular-app", "name": "electron-angular-app",
"productName": "Electron Angular App", "productName": "Electron Angular App",
"version": "3.1.0", "version": "3.2.0",
"description": "An Electron and Angular Quick-starter", "description": "An Electron and Angular Quick-starter",
"repository": "https://github.com/sourcygen/electron-angular-quick-start.git", "repository": "https://github.com/sourcygen/electron-angular-quick-start.git",
"author": "Sourcygen", "author": "Sourcygen",
@@ -9,7 +9,7 @@
"main": ".webpack/main", "main": ".webpack/main",
"keywords": [ "keywords": [
"angular", "angular",
"angular 13", "angular 14",
"electron", "electron",
"electron 18", "electron 18",
"typescript", "typescript",
@@ -41,8 +41,8 @@
"test:angular-e2e": "npm-run-all -p -r start start:angular-e2e", "test:angular-e2e": "npm-run-all -p -r start start:angular-e2e",
"start:angular-e2e": "wait-on http://localhost:4200 && cd workspaces/angular-app && npm run cypress:run", "start:angular-e2e": "wait-on http://localhost:4200 && cd workspaces/angular-app && npm run cypress:run",
"test:electron-e2e": "npm run package && npm run test:electron-e2e:wdio-only", "test:electron-e2e": "npm run package && npm run test:electron-e2e:wdio-only",
"test:electron-e2e:wdio-only": "cross-env X_NODE_ENV=e2e-test wdio run workspaces/electron-e2e/wdio.conf.ts --autoCompileOpts.tsNodeOpts.project=workspaces/electron-e2e/tsconfig.json", "test:electron-e2e:wdio-only": "cross-env X_NODE_ENV=e2e-test wdio run workspaces/electron-e2e/webdriverio.config.ts --autoCompileOpts.tsNodeOpts.project=workspaces/electron-e2e/tsconfig.json",
"clean": "shx rm -rf .webpack out allure-results node_modules workspaces/shared-lib/.dist workspaces/angular-app/node_modules workspaces/angular-app/.dist", "clean": "shx rm -rf .webpack out allure-results package-lock.json node_modules workspaces/shared-lib/.dist workspaces/angular-app/node_modules workspaces/angular-app/.angular workspaces/angular-app/.dist",
"prepare": "husky install", "prepare": "husky install",
"postinstall": "husky install && shx rm -rf .git/hooks && shx ln -s ../.husky .git/hooks", "postinstall": "husky install && shx rm -rf .git/hooks && shx ln -s ../.husky .git/hooks",
"outdated-deps": "npm run outdated-deps:electron-app && npm run outdated-deps:angular-app && npm run outdated-deps:shared-lib", "outdated-deps": "npm run outdated-deps:electron-app && npm run outdated-deps:angular-app && npm run outdated-deps:shared-lib",
@@ -51,7 +51,7 @@
"outdated-deps:shared-lib": "cd workspaces/shared-lib && npx ncu", "outdated-deps:shared-lib": "cd workspaces/shared-lib && npx ncu",
"update-deps": "npm run update-deps:electron-app && npm run update-deps:angular-app && npm run update-deps:shared-lib", "update-deps": "npm run update-deps:electron-app && npm run update-deps:angular-app && npm run update-deps:shared-lib",
"update-deps:electron-app": "npx ncu -u", "update-deps:electron-app": "npx ncu -u",
"update-deps:angular-app": "cd workspaces/angular-app && ng update @angular/cli @angular/core --force && npx ncu -u", "update-deps:angular-app": "cd workspaces/angular-app && ng update @angular/cli @angular/core --force && npx ncu -u && cd ../.. && npm run clean",
"update-deps:shared-lib": "cd workspaces/shared-lib && npx ncu -u", "update-deps:shared-lib": "cd workspaces/shared-lib && npx ncu -u",
"release:minor": "standard-version --release-as minor", "release:minor": "standard-version --release-as minor",
"release:patch": "standard-version --release-as patch", "release:patch": "standard-version --release-as patch",
@@ -102,60 +102,63 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.2.3", "@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^16.2.1", "@commitlint/config-conventional": "^17.1.0",
"@electron-forge/cli": "^6.0.0-beta.63", "@electron-forge/cli": "^6.0.0-beta.66",
"@electron-forge/maker-deb": "^6.0.0-beta.63", "@electron-forge/maker-deb": "^6.0.0-beta.66",
"@electron-forge/maker-dmg": "^6.0.0-beta.63", "@electron-forge/maker-dmg": "^6.0.0-beta.66",
"@electron-forge/maker-rpm": "^6.0.0-beta.63", "@electron-forge/maker-rpm": "^6.0.0-beta.66",
"@electron-forge/maker-squirrel": "^6.0.0-beta.63", "@electron-forge/maker-squirrel": "^6.0.0-beta.66",
"@electron-forge/maker-zip": "^6.0.0-beta.63", "@electron-forge/maker-zip": "^6.0.0-beta.66",
"@electron-forge/plugin-webpack": "6.0.0-beta.63", "@electron-forge/plugin-webpack": "6.0.0-beta.66",
"@types/lodash": "^4.14.181", "@types/lodash": "^4.14.185",
"@types/node": "^17.0.23", "@types/node": "^18.7.18",
"@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.18.0", "@typescript-eslint/parser": "^5.38.0",
"@vercel/webpack-asset-relocator-loader": "^1.7.2", "@vercel/webpack-asset-relocator-loader": "^1.7.3",
"@wdio/allure-reporter": "^7.19.1", "@wdio/allure-reporter": "^7.24.1",
"@wdio/cli": "^7.19.3", "@wdio/cli": "^7.24.1",
"@wdio/jasmine-framework": "^7.19.3", "@wdio/jasmine-framework": "^7.24.1",
"@wdio/local-runner": "^7.19.3", "@wdio/local-runner": "^7.24.1",
"@wdio/spec-reporter": "^7.19.1", "@wdio/spec-reporter": "^7.24.1",
"allure-commandline": "^2.17.2", "allure-commandline": "^2.18.1",
"chokidar-cli": "^3.0.0", "chokidar-cli": "^3.0.0",
"chromedriver": "^100.0.0", "chromedriver": "^104.0.0",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^11.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"electron": "^18.0.3", "electron": "^20.2.0",
"eslint": "^8.12.0", "eslint": "^8.23.1",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-typescript": "^3.5.1",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.2.1",
"fork-ts-checker-webpack-plugin": "^7.2.3", "eslint-plugin-sonarjs": "^0.15.0",
"husky": "^7.0.4", "eslint-plugin-unicorn": "^43.0.2",
"lint-staged": "^12.3.7", "fork-ts-checker-webpack-plugin": "^7.2.13",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"node-loader": "^2.0.0", "node-loader": "^2.0.0",
"npm-check-updates": "^12.5.8", "npm-check-updates": "^16.2.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^2.6.2", "prettier": "^2.7.1",
"shx": "^0.3.4", "shx": "^0.3.4",
"standard-version": "^9.3.2", "standard-version": "^9.5.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"tree-kill": "^1.2.2", "tree-kill": "^1.2.2",
"ts-loader": "^9.2.8", "ts-loader": "^9.4.1",
"ts-node": "^10.7.0", "ts-node": "^10.9.1",
"typescript": "^4.6.3", "typescript": "^4.8.3",
"wait-on": "^6.0.1", "wait-on": "^6.0.1",
"wdio-chromedriver-service": "^7.3.2", "wdio-chromedriver-service": "^7.3.2",
"wdio-electron-service": "^2.1.0", "wdio-electron-service": "^3.5.0",
"wdio-wait-for": "^2.2.5" "wdio-wait-for": "^2.2.6"
}, },
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.8", "@electron/remote": "^2.0.8",
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"fs-extra": "^10.0.1", "fs-extra": "^10.1.0",
"winston": "^3.7.2" "winston": "^3.8.2"
}, },
"lint-staged": { "lint-staged": {
"*.ts": "npm run lint" "*.ts": "npm run lint"

View File

@@ -47,7 +47,7 @@
"fileReplacements": [ "fileReplacements": [
{ {
"replace": "src/environments/environment.ts", "replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts" "with": "src/environments/environment.production.ts"
} }
], ],
"outputHashing": "all" "outputHashing": "all"
@@ -128,5 +128,7 @@
} }
} }
}, },
"defaultProject": "angular-app" "cli": {
"analytics": false
}
} }

View File

@@ -0,0 +1,18 @@
import { defineConfig } from 'cypress';
import configCypress from './cypress/plugins/index';
export default defineConfig({
videosFolder: 'cypress/videos',
screenshotsFolder: 'cypress/screenshots',
fixturesFolder: 'cypress/fixtures',
screenshotOnRunFailure: false,
video: false,
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return configCypress(on, config);
},
baseUrl: 'http://localhost:4200',
},
});

View File

@@ -1,11 +0,0 @@
{
"integrationFolder": "cypress/integration",
"supportFile": "cypress/support/index.ts",
"videosFolder": "cypress/videos",
"screenshotsFolder": "cypress/screenshots",
"pluginsFile": "cypress/plugins/index.ts",
"fixturesFolder": "cypress/fixtures",
"baseUrl": "http://localhost:4200",
"screenshotOnRunFailure": false,
"video": false
}

View File

@@ -1,5 +1,8 @@
// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress // Plugins enable you to tap into, modify, or extend the internal behavior of Cypress
// For more info, visit https://on.cypress.io/plugins-api // For more info, visit https://on.cypress.io/plugins-api
module.exports = (_on, _config) => { export default (
_on: Cypress.PluginEvents,
_config: Cypress.PluginConfigOptions
): void => {
// configure plugins here // configure plugins here
}; };

View File

@@ -1,23 +1,6 @@
/// <reference types="cypress" />
// *********************************************** // ***********************************************
// This example namespace declaration will help // This example commands.ts shows you how to
// with Intellisense and code completion in your
// IDE or Text Editor.
// ***********************************************
// declare namespace Cypress {
// interface Chainable<Subject = any> {
// customCommand(param: any): typeof customCommand;
// }
// }
//
// function customCommand(param: any): void {
// console.warn(param);
// }
//
// NOTE: You can use it like so:
// Cypress.Commands.add('customCommand', customCommand);
//
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite // create various custom commands and overwrite
// existing commands. // existing commands.
// //
@@ -28,16 +11,27 @@
// //
// //
// -- This is a parent command -- // -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... }) // Cypress.Commands.add('login', (email, password) => { ... })
// //
// //
// -- This is a child command -- // -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
// //
// //
// -- This is a dual command -- // -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
// //
// //
// -- This will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }

View File

@@ -1,5 +1,7 @@
/* eslint-disable unicorn/prevent-abbreviations */
// *********************************************************** // ***********************************************************
// This example support/index.js is processed and // This example support/e2e.ts is processed and
// loaded automatically before your test files. // loaded automatically before your test files.
// //
// This is a great place to put global configuration and // This is a great place to put global configuration and
@@ -13,5 +15,8 @@
// https://on.cypress.io/configuration // https://on.cypress.io/configuration
// *********************************************************** // ***********************************************************
// When a command from ./commands is ready to use, import with `import './commands'` syntax // Import commands.js using ES2015 syntax:
// import './commands'; import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -10,33 +10,33 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~13.3.2", "@angular/animations": "^14.2.3",
"@angular/common": "~13.3.2", "@angular/common": "^14.2.3",
"@angular/compiler": "~13.3.2", "@angular/compiler": "^14.2.3",
"@angular/core": "~13.3.2", "@angular/core": "^14.2.3",
"@angular/forms": "~13.3.2", "@angular/forms": "^14.2.3",
"@angular/platform-browser": "~13.3.2", "@angular/platform-browser": "^14.2.3",
"@angular/platform-browser-dynamic": "~13.3.2", "@angular/platform-browser-dynamic": "^14.2.3",
"@angular/router": "~13.3.2", "@angular/router": "^14.2.3",
"@ngx-translate/core": "^14.0.0", "@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0", "@ngx-translate/http-loader": "^7.0.0",
"electron": "^18.0.3", "electron": "^20.2.0",
"rxjs": "~7.5.5", "rxjs": "~7.5.6",
"tslib": "^2.3.1", "tslib": "^2.4.0",
"zone.js": "~0.11.5" "zone.js": "~0.11.8"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~13.3.2", "@angular-devkit/build-angular": "^14.2.3",
"@angular/cli": "~13.3.2", "@angular/cli": "^14.2.3",
"@angular/compiler-cli": "~13.3.2", "@angular/compiler-cli": "^14.2.3",
"@cypress/schematic": "^1.6.0", "@cypress/schematic": "^2.1.1",
"@types/node": "^17.0.23", "@types/node": "^18.7.18",
"cypress": "9.5.3", "cypress": "10.8.0",
"karma": "~6.3.17", "karma": "~6.4.1",
"karma-chrome-launcher": "~3.1.1", "karma-chrome-launcher": "~3.1.1",
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~4.0.2", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~1.7.0", "karma-jasmine-html-reporter": "~2.0.0",
"typescript": "^4.6.3" "typescript": "^4.8.3"
} }
} }

View File

@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { MutiplesComponent } from './components/mutiples/mutiples.component'; import { MultiplesComponent } from './components/multiples/multiples.component';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
component: MutiplesComponent, component: MultiplesComponent,
}, },
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes)], imports: [RouterModule.forRoot(routes)],
exports: [RouterModule], exports: [RouterModule],
}) })
export class AppRoutingModule {} export class AppRoutingModule {}

View File

@@ -6,7 +6,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { MutiplesComponent } from './components/mutiples/mutiples.component'; import { MultiplesComponent } from './components/multiples/multiples.component';
// AoT requires an exported function for factories // AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
@@ -14,7 +14,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
} }
@NgModule({ @NgModule({
declarations: [AppComponent, MutiplesComponent], declarations: [AppComponent, MultiplesComponent],
imports: [ imports: [
BrowserModule, BrowserModule,
AppRoutingModule, AppRoutingModule,

View File

@@ -0,0 +1,23 @@
#multiples {
position: absolute;
left: 0;
top: 210px;
width: 100%;
display: flex;
justify-content: center;
color: #ffffff;
font-weight: 900;
.container {
background-color: #d47800e0;
border-radius: 4px;
padding: 20px;
form {
display: flex;
> * {
margin: 5px;
}
}
}
}

View File

@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MultiplesComponent } from './multiples.component';
describe('MultiplesComponent', () => {
let component: MultiplesComponent;
let fixture: ComponentFixture<MultiplesComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [MultiplesComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MultiplesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,17 +1,19 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, UntypedFormGroup } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { WindowApiConst } from 'shared-lib'; import { WindowApiConst } from 'shared-lib';
import { ElectronIpcService } from '../../services/electron-ipc.service'; import { ElectronIpcService } from '../../services/electron-ipc.service';
@Component({ @Component({
selector: 'app-mutiples', selector: 'app-multiples',
templateUrl: './mutiples.component.html', templateUrl: './multiples.component.html',
styleUrls: ['./mutiples.component.scss'], styleUrls: ['./multiples.component.scss'],
}) })
export class MutiplesComponent implements OnInit { export class MultiplesComponent implements OnInit {
timesTableForm = new FormGroup({ timesTableForm = new UntypedFormGroup({
input: new FormControl(Math.round(Math.random() * 100) % 10), input: new FormControl<number>(Math.round(Math.random() * 100) % 10, {
nonNullable: true,
}),
}); });
multiples: number[] = []; multiples: number[] = [];

View File

@@ -1,23 +0,0 @@
#multiples {
position: absolute;
left: 0;
top: 210px;
width: 100%;
display: flex;
justify-content: center;
color: #ffffff;
font-weight: 900;
.container {
background-color: #d47800e0;
border-radius: 4px;
padding: 20px;
form {
display: flex;
> * {
margin: 5px;
}
}
}
}

View File

@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MutiplesComponent } from './mutiples.component';
describe('MutiplesComponent', () => {
let component: MutiplesComponent;
let fixture: ComponentFixture<MutiplesComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ MutiplesComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MutiplesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -16,7 +16,7 @@ export class ElectronIpcService {
} }
} }
public receive<Out>(channel: string, func: (output: Out) => void): void { public receive<Out>(channel: string, callback: (output: Out) => void): void {
if (this._api) { if (this._api) {
this._api.receive<Out>(channel, (output) => { this._api.receive<Out>(channel, (output) => {
console.log(`Received from main process channel [${channel}]`, output); console.log(`Received from main process channel [${channel}]`, output);
@@ -25,7 +25,7 @@ export class ElectronIpcService {
// doesn't recognize it needs to run change detection // doesn't recognize it needs to run change detection
// Further details on SO : https://stackoverflow.com/a/49136353/11480016 // Further details on SO : https://stackoverflow.com/a/49136353/11480016
this.zone.run(() => { this.zone.run(() => {
func(output); callback(output);
}); });
}); });
} }

View File

@@ -1,3 +1,3 @@
export const environment = { export const environment = {
production: true production: true,
}; };

View File

@@ -1,9 +1,9 @@
// This file can be replaced during build by using the `fileReplacements` array. // This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`. // `ng build` replaces `environment.ts` with `environment.production.ts`.
// The list of file replacements can be found in `angular.json`. // The list of file replacements can be found in `angular.json`.
export const environment = { export const environment = {
production: false production: false,
}; };
/* /*

View File

@@ -5,8 +5,10 @@ import { AppModule } from './app/app.module';
import { environment } from './environments/environment'; import { environment } from './environments/environment';
if (environment.production) { if (environment.production) {
enableProdMode(); enableProdMode();
} }
platformBrowserDynamic().bootstrapModule(AppModule) platformBrowserDynamic()
.catch(err => console.error(err)); .bootstrapModule(AppModule)
// eslint-disable-next-line unicorn/prefer-top-level-await
.catch((error) => console.error(error));

View File

@@ -1,11 +1,11 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files // This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting, platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
import 'zone.js/testing';
declare const require: { declare const require: {
context( context(
@@ -29,4 +29,6 @@ getTestBed().initTestEnvironment(
// Then we find all the tests. // Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/); const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules. // And load the modules.
context.keys().map(context); for (const key of context.keys()) {
context(key);
}

View File

@@ -14,7 +14,7 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"target": "es2017", "target": "es2020",
"module": "es2020", "module": "es2020",
"lib": ["es2018", "dom"] "lib": ["es2018", "dom"]
}, },

View File

@@ -1,9 +1,9 @@
import * as remoteMain from '@electron/remote/main';
import { app, BrowserWindow, ipcMain, nativeImage } from 'electron'; import { app, BrowserWindow, ipcMain, nativeImage } from 'electron';
import * as path from 'path'; import * as path from 'node:path';
import { AbstractService } from '../services/abstract-service'; import { AbstractService } from '../services/abstract-service';
import { MultiplesService } from '../services/multiples-service'; import { MultiplesService } from '../services/multiples-service';
import { Logger } from '../utils/logger'; import { Logger } from '../utils/logger';
import * as remoteMain from '@electron/remote/main';
declare const global: Global; declare const global: Global;
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string; declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
@@ -31,6 +31,11 @@ export class Window {
// Isolate window context to protect against prototype pollution // Isolate window context to protect against prototype pollution
// except in e2e test when that access is required // except in e2e test when that access is required
contextIsolation: global.appConfig.isContextIsolation, contextIsolation: global.appConfig.isContextIsolation,
// Introduced in Electron 20 and enabled by default
// Among others security constraints, it prevents from required
// CommonJS modules imports into preload script
// which is not bundled yet in dev mode
sandbox: global.appConfig.isSandbox,
// Use a preload script to enhance security // Use a preload script to enhance security
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
}, },
@@ -44,17 +49,17 @@ export class Window {
} }
private loadIcon(): Electron.NativeImage | undefined { private loadIcon(): Electron.NativeImage | undefined {
let iconObj = undefined; let iconObject;
if (global.appConfig.isIconAvailable) { if (global.appConfig.isIconAvailable) {
const iconPath = path.join(__dirname, 'icons/icon.png'); const iconPath = path.join(__dirname, 'icons/icon.png');
Logger.debug('Icon Path', iconPath); Logger.debug('Icon Path', iconPath);
iconObj = nativeImage.createFromPath(iconPath); iconObject = nativeImage.createFromPath(iconPath);
// Change dock icon on MacOS // Change dock icon on MacOS
if (iconObj && process.platform === 'darwin') { if (iconObject && process.platform === 'darwin') {
app.dock.setIcon(iconObj); app.dock.setIcon(iconObject);
} }
} }
return iconObj; return iconObject;
} }
private loadRenderer(): void { private loadRenderer(): void {
@@ -96,11 +101,11 @@ export class Window {
private registerService<In, Out>(service: AbstractService<In, Out>) { private registerService<In, Out>(service: AbstractService<In, Out>) {
ipcMain.on( ipcMain.on(
service.receptionChannel(), service.receptionChannel(),
async (event: Electron.IpcMainEvent, ...args: any[]) => { async (event: Electron.IpcMainEvent, ...parameters: any[]) => {
// Handling input // Handling input
const input = args[0]; const input = parameters[0];
Logger.debug(`[${service.receptionChannel()}] =====> `, input); Logger.debug(`[${service.receptionChannel()}] =====> `, input);
const output: Out = await service.process(input); const output: Out = service.process(input);
// Handling output // Handling output
if (service.sendingChannel()) { if (service.sendingChannel()) {

View File

@@ -1,6 +1,6 @@
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import * as _ from 'lodash'; import _ from 'lodash';
import * as path from 'path'; import * as path from 'node:path';
import { AppConfig } from 'shared-lib'; import { AppConfig } from 'shared-lib';
import { App } from './components/app'; import { App } from './components/app';
@@ -14,14 +14,14 @@ declare global {
} }
// Load config // Load config
const currentEnv = process.env.X_NODE_ENV || process.env.NODE_ENV; const currentEnvironment = process.env.X_NODE_ENV || process.env.NODE_ENV;
const appConfigs = fs.readJsonSync(path.join(__dirname, 'config.json')); const appConfigs = fs.readJsonSync(path.join(__dirname, 'config.json'));
const defaultConf = appConfigs.development; const defaultConfig = appConfigs.development;
const currentConf = appConfigs[currentEnv]; const currentConfig = appConfigs[currentEnvironment];
global.appConfig = global.appConfig =
currentEnv === 'development' currentEnvironment === 'development'
? defaultConf ? defaultConfig
: _.merge(defaultConf, currentConf); : _.merge(defaultConfig, currentConfig);
// Launch app // Launch app
App.launch(); App.launch();

View File

@@ -1,13 +1,14 @@
const NOT_IMPEMENTED_YET = 'Method not implemented yet.';
export class AbstractService<In, Out> { export class AbstractService<In, Out> {
receptionChannel(): string { receptionChannel(): string {
throw new Error('Method not implemented yet.'); throw new Error(NOT_IMPEMENTED_YET);
} }
sendingChannel(): string { sendingChannel(): string {
throw new Error('Method not implemented yet.'); throw new Error(NOT_IMPEMENTED_YET);
} }
process(_input: In): Out { process(_input: In): Out {
throw new Error('Method not implemented yet.'); throw new Error(NOT_IMPEMENTED_YET);
} }
} }

View File

@@ -1,6 +1,6 @@
import { app } from 'electron'; import { app } from 'electron';
import * as os from 'os'; import * as os from 'node:os';
import * as path from 'path'; import * as path from 'node:path';
import * as winston from 'winston'; import * as winston from 'winston';
declare const global: Global; declare const global: Global;

View File

@@ -1,4 +1,4 @@
// To secure user platform when running renderer process stuff, /*/ To secure user platform when running renderer process stuff,
// Node.JS and Electron APIs are only available in this script // Node.JS and Electron APIs are only available in this script
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
import { WindowApi, WindowApiConst } from 'shared-lib'; import { WindowApi, WindowApiConst } from 'shared-lib';
@@ -11,11 +11,13 @@ const windowApi: WindowApi = {
ipcRenderer.send(channel, input); ipcRenderer.send(channel, input);
} }
}, },
receive: <Out>(channel: string, func: (output: Out) => void) => { receive: <Out>(channel: string, callback: (output: Out) => void) => {
if (WindowApiConst.RECEIVING_SAFE_CHANNELS.includes(channel)) { if (WindowApiConst.RECEIVING_SAFE_CHANNELS.includes(channel)) {
// Deliberately strip event as it includes `sender` // Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event: IpcRendererEvent, ...args: any[]) => ipcRenderer.on(
func(args[0]) channel,
(_event: IpcRendererEvent, ...parameters: any[]) =>
callback(parameters[0])
); );
} }
}, },
@@ -29,6 +31,27 @@ if (process.env.X_NODE_ENV === 'e2e-test') {
// ContextBridge API can only be used when contextIsolation is enabled // ContextBridge API can only be used when contextIsolation is enabled
// which is normally the case except in e2e test mode // which is normally the case except in e2e test mode
contextBridge.exposeInMainWorld('api', windowApi); contextBridge.exposeInMainWorld('api', windowApi);
} }*/
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
import { WindowApiConst } from 'shared-lib';
contextBridge.exposeInMainWorld('api', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
send: <In>(channel: string, input: In) => {
if (WindowApiConst.SENDING_SAFE_CHANNELS.includes(channel)) {
ipcRenderer.send(channel, input);
}
},
receive: <Out>(channel: string, callback: (output: Out) => void) => {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (_event: IpcRendererEvent, ...parameters: any[]) =>
callback(parameters[0])
);
},
// we can also expose variables, not just functions
});
console.log('The preload script has been injected successfully.'); console.log('The preload script has been injected successfully.');

View File

@@ -35,7 +35,8 @@ describe('A simple test to check if app window is opened, visible and with expec
// Checking there is one visible window // Checking there is one visible window
// expect(await browser.).toEqual(true); // expect(await browser.).toEqual(true);
// Please note that getWindowHandles() will return 2 if `dev tools` is opened. // Please note that getWindowHandles() will return 2 if `dev tools` is opened.
expect((await browser.getWindowHandles()).length).toEqual(1); const { length } = await browser.getWindowHandles();
expect(length).toEqual(1);
}); });
it('have expected title', async () => { it('have expected title', async () => {

View File

@@ -10,10 +10,10 @@ describe('A simple test to check if a given input matches with computed multiple
it(`display expected results on input (${number})`, async () => { it(`display expected results on input (${number})`, async () => {
await MultiplesPage.enterInput(number); await MultiplesPage.enterInput(number);
const results = await MultiplesPage.results; const results = await MultiplesPage.results;
for (let i = 0; i < results.length; i++) { for (const index of results.keys()) {
const ntimes = 1 + i; const ntimes = 1 + index;
const expected = `${number} * ${ntimes} = ${number * ntimes}`; const expected = `${number} * ${ntimes} = ${number * ntimes}`;
expect(await results[i].getText()).toEqual(expected); expect(await results[index].getText()).toEqual(expected);
} }
}); });
}); });

View File

@@ -1,5 +1,5 @@
import type { Options } from '@wdio/types'; import type { Options } from '@wdio/types';
import path from 'path'; import path from 'node:path';
// Path to local electron binary // Path to local electron binary
let electronPath = path.join(__dirname, '../../node_modules/.bin/electron'); let electronPath = path.join(__dirname, '../../node_modules/.bin/electron');
@@ -9,7 +9,7 @@ if (process.platform === 'win32') {
// Starting hook // Starting hook
const waitUntilWindowLoaded = async () => { const waitUntilWindowLoaded = async () => {
const timeout = 10000; const timeout = 10_000;
await browser.waitUntil(async () => (await browser.isLoading()) === false, { await browser.waitUntil(async () => (await browser.isLoading()) === false, {
timeout: timeout, timeout: timeout,
timeoutMsg: `Expected app to be loaded in less than ${timeout}ms`, timeoutMsg: `Expected app to be loaded in less than ${timeout}ms`,
@@ -74,7 +74,7 @@ export const config: Options.Testrunner = {
// then the current working directory is where your `package.json` resides, so `wdio` // then the current working directory is where your `package.json` resides, so `wdio`
// will be called from there. // will be called from there.
// //
specs: ['./workspaces/electron-e2e/**/*.e2e-spec.ts'], specs: ['./workspaces/electron-e2e/**/*.spec.ts'],
// Patterns to exclude. // Patterns to exclude.
exclude: [ exclude: [
// 'path/to/excluded/files' // 'path/to/excluded/files'
@@ -154,11 +154,11 @@ export const config: Options.Testrunner = {
baseUrl: 'http://localhost', baseUrl: 'http://localhost',
// //
// Default timeout for all waitFor* commands. // Default timeout for all waitFor* commands.
waitforTimeout: 10000, waitforTimeout: 10_000,
// //
// Default timeout in milliseconds for request // Default timeout in milliseconds for request
// if browser driver or grid doesn't send response // if browser driver or grid doesn't send response
connectionRetryTimeout: 120000, connectionRetryTimeout: 120_000,
// //
// Default request retries count // Default request retries count
connectionRetryCount: 3, connectionRetryCount: 3,
@@ -195,7 +195,7 @@ export const config: Options.Testrunner = {
// Options to be passed to Jasmine. // Options to be passed to Jasmine.
jasmineOpts: { jasmineOpts: {
// Jasmine default timeout // Jasmine default timeout
defaultTimeoutInterval: 60000, defaultTimeoutInterval: 60_000,
// //
// The Jasmine framework allows interception of each assertion in order to log the state of the application // The Jasmine framework allows interception of each assertion in order to log the state of the application
// or website depending on the result. For example, it is pretty handy to take a screenshot every time // or website depending on the result. For example, it is pretty handy to take a screenshot every time

View File

@@ -1,17 +1,17 @@
export class WindowApiConst { export class WindowApiConst {
/** Channel used by the renderer process to send data to the main process */ /** Channel used by the renderer process to send data to the main process */
public static readonly MULTIPLES_INPUT = "getMultiplesInput"; public static readonly MULTIPLES_INPUT = 'getMultiplesInput';
/** Channel used by the renderer process to receive data from the main process */ /** Channel used by the renderer process to receive data from the main process */
public static readonly MULTIPLES_OUTPUT = "getMultiplesOutput"; public static readonly MULTIPLES_OUTPUT = 'getMultiplesOutput';
/** Whitelist of the safe channels to use when sending data to the main process */ /** Whitelist of the safe channels to use when sending data to the main process */
public static readonly SENDING_SAFE_CHANNELS = [ public static readonly SENDING_SAFE_CHANNELS = [
WindowApiConst.MULTIPLES_INPUT, WindowApiConst.MULTIPLES_INPUT,
]; ];
/** Whitelist of the safe channels to use when receiving data from the main process */ /** Whitelist of the safe channels to use when receiving data from the main process */
public static readonly RECEIVING_SAFE_CHANNELS = [ public static readonly RECEIVING_SAFE_CHANNELS = [
WindowApiConst.MULTIPLES_OUTPUT, WindowApiConst.MULTIPLES_OUTPUT,
]; ];
} }

View File

@@ -4,7 +4,7 @@ export interface WindowApi {
* @param channel used by the renderer to receive data and by the main to send them * @param channel used by the renderer to receive data and by the main to send them
* @param func the callback function to execute when data are available * @param func the callback function to execute when data are available
*/ */
receive<Out>(channel: string, func: (output: Out) => void): void; receive<Out>(channel: string, callback: (output: Out) => void): void;
/** /**
* This method is used by the renderer process to send data to the main process * This method is used by the renderer process to send data to the main process

View File

@@ -17,6 +17,9 @@ export interface AppConfig {
/** Tells if `contextIsolation` and `worldSafeExecuteJavaScript` webPreferences are enabled */ /** Tells if `contextIsolation` and `worldSafeExecuteJavaScript` webPreferences are enabled */
isContextIsolation: boolean; isContextIsolation: boolean;
/** Tells if `isSandbox` webPreference is enabled */
isSandbox: boolean;
/** Tells if `isEnableRemoteModule` webPreference is enabled */ /** Tells if `isEnableRemoteModule` webPreference is enabled */
isEnableRemoteModule: boolean; isEnableRemoteModule: boolean;