Extract agnostic and electron-main ways of starting application to separate packages (#7259)

* Introduce an agnostic way to start application is the Feature

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce directory for application-packages

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove non agnostic timeslots from agnostic application

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce empty package for electron main specific stuff for Application Feature

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce electron-main specific timeslots

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update injectable version

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update README

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak export names to make them easier to import

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make starting of application synchronous for Electron main

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak versions in package jsons

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate name of feature file

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

---------

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen authored and GitHub committed 2023-03-06 10:17:24 +02:00
1 parent a917494e9c
commit a9bd5ae7e2
35 files changed
+1841 -1036

No files matched your search

@@ -0,0 +1,35 @@
# @k8slens/application-for-electron-main
This Feature extends `@k8slens/application` with Electron specifics.
# Usage
```bash
$ npm install @k8slens/application-for-electron-main
```
```typescript
import { applicationFeature, startApplicationInjectionToken } from "@k8slens/application";
import { applicationFeatureForElectronMain } from "@k8slens/application-for-electron-main";
import { registerFeature } from "@k8slens/feature-core";
import { createContainer } from "@ogre-tools/injectable";
const di = createContainer("some-container");
registerFeature(di, applicationFeature, applicationFeatureForElectronMain);
const startApplication = di.inject(startApplicationInjectionToken);
startApplication();
```
# Extendability
### Timeslots
#### `beforeAnythingInjectionToken`
Runnables registered here will be called before anything else. **Special requirement** here is that everything here needs to be synchronous.
#### `beforeElectronIsReadyInjectionToken`
Runnables registered here will be called right after runnables in `beforeAnythingInjectionToken` but still before we are sure that Electron application is ready (`electron.app.whenReady()`). **Special requirement** here is that everything here needs to be synchronous.
@@ -0,0 +1,8 @@
import { overrideSideEffectsWithFakes } from "./src/override-side-effects-with-fakes";
export { beforeElectronIsReadyInjectionToken } from "./src/start-application/timeslots/before-electron-is-ready-injection-token";
export { beforeAnythingInjectionToken } from "./src/start-application/timeslots/before-anything-injection-token";
export { applicationFeatureForElectronMain } from "./src/feature";
export const testUtils = { overrideSideEffectsWithFakes }
@@ -0,0 +1,2 @@
module.exports =
require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode;
@@ -0,0 +1,44 @@
{
"name": "@k8slens/application-for-electron-main",
"private": false,
"version": "6.5.0-alpha.0",
"description": "Electron's main specifics for creating Lens applications",
"type": "commonjs",
"files": [
"dist"
],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lensapp/lens.git"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": {
"name": "OpenLens Authors",
"email": "info@k8slens.dev"
},
"license": "MIT",
"homepage": "https://github.com/lensapp/lens",
"scripts": {
"build": "webpack",
"dev": "webpack --mode=development --watch",
"test": "jest --coverage --runInBand"
},
"peerDependencies": {
"@k8slens/feature-core": "^6.5.0-alpha.0",
"@k8slens/application": "^6.5.0-alpha.0",
"@ogre-tools/injectable": "^15.1.2",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
"@ogre-tools/injectable-utils": "^15.1.2",
"electron": "^19.1.9"
},
"devDependencies": {
"@async-fn/jest": "^1.6.4",
"@ogre-tools/test-utils": "^15.1.2"
}
}
@@ -0,0 +1,14 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { app } from "electron";
const electronAppInjectable = getInjectable({
id: "electron-app",
instantiate: () => app,
causesSideEffects: true,
});
export default electronAppInjectable;
@@ -0,0 +1,17 @@
import electronAppInjectable from "./electron-app.injectable";
import { app } from 'electron';
import { createContainer } from "@ogre-tools/injectable";
import { applicationFeatureForElectronMain } from "../feature";
import { registerFeature } from "@k8slens/feature-core";
describe('electron-app', () => {
it('is electron app', () => {
const di = createContainer('irrelevant');
registerFeature(di, applicationFeatureForElectronMain)
const actual = di.inject(electronAppInjectable);
expect(actual).toBe(app);
});
});
@@ -0,0 +1,22 @@
import { getFeature } from "@k8slens/feature-core";
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
import { applicationFeature } from '@k8slens/application';
export const applicationFeatureForElectronMain = getFeature({
id: "application-for-electron-main",
register: (di) => {
autoRegister({
di,
targetModule: module,
getRequireContexts: () => [
require.context("./", true, /\.injectable\.(ts|tsx)$/),
],
});
},
dependencies: [
applicationFeature
]
});
@@ -0,0 +1,6 @@
import type { DiContainer } from "@ogre-tools/injectable";
import whenAppIsReadyInjectable from "./start-application/when-app-is-ready.injectable";
export const overrideSideEffectsWithFakes = (di: DiContainer) => {
di.override(whenAppIsReadyInjectable, () => () => Promise.resolve())
};
@@ -0,0 +1,58 @@
import {
DiContainer,
getInjectable,
instantiationDecoratorToken,
lifecycleEnum,
} from "@ogre-tools/injectable";
import { startApplicationInjectionToken } from "@k8slens/application";
import whenAppIsReadyInjectable from "./when-app-is-ready.injectable";
import { beforeAnythingInjectionToken } from "./timeslots/before-anything-injection-token";
import { beforeElectronIsReadyInjectionToken } from "./timeslots/before-electron-is-ready-injection-token";
import { runManySyncFor } from "@ogre-tools/injectable-utils";
const startElectronApplicationInjectable = getInjectable({
id: "start-electron-application",
instantiate: () => ({
decorate:
(toBeDecorated: unknown) =>
(di: DiContainer, ...args: unknown[]) => {
const whenAppIsReady = di.inject(whenAppIsReadyInjectable);
const runManySync = runManySyncFor(di);
const beforeAnything = runManySync(beforeAnythingInjectionToken);
const beforeElectronIsReady = runManySync(
beforeElectronIsReadyInjectionToken
);
const typedToBeDecorated = toBeDecorated as (
di: DiContainer,
...args: unknown[]
) => unknown;
const startApplication = typedToBeDecorated(di, ...args) as (
...args: unknown[]
) => unknown;
return (...startApplicationArgs: unknown[]) => {
beforeAnything();
beforeElectronIsReady();
return (async () => {
await whenAppIsReady();
return startApplication(...startApplicationArgs);
})()
};
},
target: startApplicationInjectionToken,
}),
decorable: false,
injectionToken: instantiationDecoratorToken,
lifecycle: lifecycleEnum.singleton,
});
export default startElectronApplicationInjectable;
@@ -0,0 +1,6 @@
import { getInjectionToken } from "@ogre-tools/injectable";
import type { Runnable } from "@ogre-tools/injectable-utils";
export const beforeAnythingInjectionToken = getInjectionToken<Runnable>({
id: "before-anything",
});
@@ -0,0 +1,6 @@
import { getInjectionToken } from "@ogre-tools/injectable";
import type { Runnable } from "@ogre-tools/injectable-utils";
export const beforeElectronIsReadyInjectionToken = getInjectionToken<Runnable>({
id: "before-electron-is-ready-injection-token",
});
@@ -0,0 +1,16 @@
import { getInjectable } from "@ogre-tools/injectable";
import electronAppInjectable from "../electron/electron-app.injectable";
const whenAppIsReadyInjectable = getInjectable({
id: "when-app-is-ready",
instantiate: (di) => {
const electronApp = di.inject(electronAppInjectable);
return () => electronApp.whenReady();
},
decorable: false,
});
export default whenAppIsReadyInjectable;
@@ -0,0 +1,50 @@
import { createContainer, DiContainer } from "@ogre-tools/injectable";
import { applicationFeatureForElectronMain } from "../../feature";
import { registerFeature } from "@k8slens/feature-core";
import whenAppIsReadyInjectable from "../when-app-is-ready.injectable";
import { getPromiseStatus } from "@ogre-tools/test-utils";
import electronAppInjectable from "../../electron/electron-app.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
describe("when-app-is-ready", () => {
let di: DiContainer;
let whenReadyMock: AsyncFnMock<() => Promise<void>>;
beforeEach(() => {
di = createContainer("irrelevant");
registerFeature(di, applicationFeatureForElectronMain);
whenReadyMock = asyncFn();
di.override(
electronAppInjectable,
() => ({ whenReady: whenReadyMock } as unknown)
);
});
describe("when called", () => {
let actualPromise: Promise<void>;
beforeEach(() => {
const whenAppIsReady = di.inject(whenAppIsReadyInjectable);
actualPromise = whenAppIsReady();
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
it("when app is ready, resolves", async () => {
await whenReadyMock.resolve();
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(true);
});
});
});
@@ -0,0 +1,94 @@
import {
createContainer,
DiContainer,
getInjectable,
} from "@ogre-tools/injectable";
import { registerFeature } from "@k8slens/feature-core";
import { applicationFeatureForElectronMain } from "./feature";
import {
beforeApplicationIsLoadingInjectionToken,
startApplicationInjectionToken,
} from "@k8slens/application";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import whenAppIsReadyInjectable from "./start-application/when-app-is-ready.injectable";
import { beforeAnythingInjectionToken } from "./start-application/timeslots/before-anything-injection-token";
import { beforeElectronIsReadyInjectionToken } from "./start-application/timeslots/before-electron-is-ready-injection-token";
describe("starting-of-electron-main-application", () => {
let di: DiContainer;
let beforeAnythingMock: jest.Mock;
let beforeElectronIsReadyMock: jest.Mock;
let beforeApplicationIsLoadingMock: AsyncFnMock<() => Promise<void>>;
let whenAppIsReadyMock: AsyncFnMock<() => Promise<void>>;
beforeEach(() => {
di = createContainer("irrelevant");
beforeAnythingMock = jest.fn();
beforeElectronIsReadyMock = jest.fn();
beforeApplicationIsLoadingMock = asyncFn();
whenAppIsReadyMock = asyncFn();
registerFeature(di, applicationFeatureForElectronMain);
const beforeAnythingIsLoadingInjectable = getInjectable({
id: "before-anything",
instantiate: () => ({ run: beforeAnythingMock }),
injectionToken: beforeAnythingInjectionToken,
});
const beforeElectronIsReadyIsLoadingInjectable = getInjectable({
id: "before-electron-is-ready",
instantiate: () => ({ run: beforeElectronIsReadyMock }),
injectionToken: beforeElectronIsReadyInjectionToken,
});
const beforeApplicationIsLoadingInjectable = getInjectable({
id: "before-application-is-loading",
instantiate: () => ({ run: beforeApplicationIsLoadingMock }),
injectionToken: beforeApplicationIsLoadingInjectionToken,
});
di.register(
beforeAnythingIsLoadingInjectable,
beforeElectronIsReadyIsLoadingInjectable,
beforeApplicationIsLoadingInjectable
);
di.override(whenAppIsReadyInjectable, () => whenAppIsReadyMock);
});
describe("when application is started", () => {
beforeEach(() => {
const startApplication = di.inject(startApplicationInjectionToken);
startApplication();
});
it("calls for synchronous runnables for before anything", () => {
expect(beforeAnythingMock).toHaveBeenCalled();
});
it("calls for synchronous runnables for before electron is ready", () => {
expect(beforeElectronIsReadyMock).toHaveBeenCalled();
});
it("calls to wait when electron is ready", () => {
expect(whenAppIsReadyMock).toHaveBeenCalled();
});
it("does not call runnables for before application is loading yet", () => {
expect(beforeApplicationIsLoadingMock).not.toHaveBeenCalled();
});
describe("when electron is ready", () => {
beforeEach(async () => {
await whenAppIsReadyMock.resolve();
});
it("calls runnables for before application is loading", () => {
expect(beforeApplicationIsLoadingMock).toHaveBeenCalled();
});
});
});
});
@@ -0,0 +1,3 @@
{
"extends": "@k8slens/typescript/config/base.json"
}
@@ -0,0 +1 @@
module.exports = require("@k8slens/webpack").configForNode;