1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge remote-tracking branch 'origin/master' into issue-3498

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-02-28 15:15:09 -05:00
commit 9f4f4ec108
70 changed files with 1713 additions and 639 deletions

View File

@ -1,7 +1,7 @@
# Lens Desktop Core ("OpenLens")
[![Build Status](https://github.com/lensapp/lens/actions/workflows/test.yml/badge.svg)](https://github.com/lensapp/lens/actions/workflows/test.yml)
[![Chat on Slack](https://img.shields.io/badge/chat-on%20slack-blue.svg?logo=slack&longCache=true&style=flat)](https://k8slens.dev/slack.html)
<img src="https://upload.wikimedia.org/wikipedia/commons/1/17/Discourse_icon.svg" width=25>[Explore our Forums](https://forums.k8slens.dev)
## The Repository

View File

@ -7,15 +7,15 @@ To install your first extension you should goto the [extension page](lens://app/
This documentation describes:
* How to build, run, test, and publish an extension.
* How to take full advantage of the Lens Extension API.
* Where to find [guides](extensions/guides/README.md) and [code samples](https://github.com/lensapp/lens-extension-samples) to help get you started.
- How to build, run, test, and publish an extension.
- How to take full advantage of the Lens Extension API.
- Where to find [guides](extensions/guides/README.md) and [code samples](https://github.com/lensapp/lens-extension-samples) to help get you started.
## What Extensions Can Do
Here are some examples of what you can achieve with the Extension API:
* Add custom components & views in the UI - Extending the Lens Workbench
- Add custom components & views in the UI - Extending the Lens Workbench
For an overview of the Lens Extension API, refer to the [Common Capabilities](extensions/capabilities/common-capabilities.md) page. [Extension Guides Overview](extensions/guides/README.md) also includes a list of code samples and guides that illustrate various ways of using the Lens Extension API.
@ -23,11 +23,11 @@ For an overview of the Lens Extension API, refer to the [Common Capabilities](ex
Here is what each section of the Lens Extension API docs can help you with:
* **Getting Started** teaches fundamental concepts for building extensions with the Hello World sample.
* **Extension Capabilities** dissects Lens's Extension API into smaller categories and points you to more detailed topics.
* **Extension Guides** includes guides and code samples that explain specific usages of Lens Extension API.
* **Testing and Publishing** includes in-depth guides on various extension development topics, such as testing and publishing extensions.
* **API Reference** contains exhaustive references for the Lens Extension API, Contribution Points, and many other topics.
- **Getting Started** teaches fundamental concepts for building extensions with the Hello World sample.
- **Extension Capabilities** dissects Lens's Extension API into smaller categories and points you to more detailed topics.
- **Extension Guides** includes guides and code samples that explain specific usages of Lens Extension API.
- **Testing and Publishing** includes in-depth guides on various extension development topics, such as testing and publishing extensions.
- **API Reference** contains exhaustive references for the Lens Extension API, Contribution Points, and many other topics.
## What's New
@ -45,7 +45,7 @@ See the [Lens v4 to v5 extension migration notes](extensions/extension-migration
## Looking for Help
If you have questions for extension development, try asking on the [Lens Dev Slack](http://k8slens.slack.com/). It's a public chatroom for Lens developers, where Lens team members chime in from time to time.
If you have questions for extension development, try asking on the [Lens Forums](http://forums.k8slens.dev/). It's a public chatroom for Lens developers, where Lens team members chime in from time to time.
To provide feedback on the documentation or issues with the Lens Extension API, create new issues at [lensapp/lens](https://github.com/lensapp/lens/issues). Please use the labels `area/documentation` and/or `area/extension`.

View File

@ -10,33 +10,33 @@ edit_uri: ""
nav:
- Overview: README.md
- Getting Started:
- Overview: extensions/get-started/overview.md
- Your First Extension: extensions/get-started/your-first-extension.md
- Extension Anatomy: extensions/get-started/anatomy.md
- Wrapping Up: extensions/get-started/wrapping-up.md
- Overview: extensions/get-started/overview.md
- Your First Extension: extensions/get-started/your-first-extension.md
- Extension Anatomy: extensions/get-started/anatomy.md
- Wrapping Up: extensions/get-started/wrapping-up.md
- Extension Capabilities:
- Common Capabilities: extensions/capabilities/common-capabilities.md
- Styling: extensions/capabilities/styling.md
- Common Capabilities: extensions/capabilities/common-capabilities.md
- Styling: extensions/capabilities/styling.md
- Extension Guides:
- Overview: extensions/guides/README.md
- Generator: extensions/guides/generator.md
- Main Extension: extensions/guides/main-extension.md
- Renderer Extension: extensions/guides/renderer-extension.md
- Catalog: extensions/guides/catalog.md
- Resource Stack: extensions/guides/resource-stack.md
- Extending KubernetesCluster: extensions/guides/extending-kubernetes-cluster.md
- Stores: extensions/guides/stores.md
- Working with MobX: extensions/guides/working-with-mobx.md
- Protocol Handlers: extensions/guides/protocol-handlers.md
- IPC: extensions/guides/ipc.md
- Overview: extensions/guides/README.md
- Generator: extensions/guides/generator.md
- Main Extension: extensions/guides/main-extension.md
- Renderer Extension: extensions/guides/renderer-extension.md
- Catalog: extensions/guides/catalog.md
- Resource Stack: extensions/guides/resource-stack.md
- Extending KubernetesCluster: extensions/guides/extending-kubernetes-cluster.md
- Stores: extensions/guides/stores.md
- Working with MobX: extensions/guides/working-with-mobx.md
- Protocol Handlers: extensions/guides/protocol-handlers.md
- IPC: extensions/guides/ipc.md
- Testing and Publishing:
- Testing Extensions: extensions/testing-and-publishing/testing.md
- Publishing Extensions: extensions/testing-and-publishing/publishing.md
- Testing Extensions: extensions/testing-and-publishing/testing.md
- Publishing Extensions: extensions/testing-and-publishing/publishing.md
- API Reference: extensions/api/README.md
theme:
name: 'material'
name: "material"
highlightjs: true
language: 'en'
language: "en"
custom_dir: docs/custom_theme
favicon: img/favicon.ico
logo: img/lens-logo-icon.svg
@ -79,9 +79,9 @@ extra:
- icon: fontawesome/brands/twitter
link: https://twitter.com/k8slens
name: Lens on Twitter
- icon: fontawesome/brands/slack
link: http://k8slens.slack.com/
name: Lens on Slack
- icon: fontawesome/brands/discourse
link: https://forums.k8slens.dev/
name: Lens Forums
- icon: fontawesome/solid/link
link: https://k8slens.dev/
name: Lens Website

447
package-lock.json generated
View File

@ -3301,6 +3301,10 @@
"resolved": "packages/extension-api",
"link": true
},
"node_modules/@k8slens/feature-core": {
"resolved": "packages/technical-features/feature-core",
"link": true
},
"node_modules/@k8slens/generate-tray-icons": {
"resolved": "packages/generate-tray-icons",
"link": true
@ -4651,55 +4655,55 @@
}
},
"node_modules/@ogre-tools/fp": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/fp/-/fp-12.0.1.tgz",
"integrity": "sha512-BzMhkI4wPnuI+hXJDbtHUXQn/uBjJLx3W0oDaIFV+lLpkneUU0oW9D5uZFHNOouzCgf67/tnmUC6Ohevbr7/VA==",
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/fp/-/fp-15.1.1.tgz",
"integrity": "sha512-WuLl0lBFjMHcy6o+HZLw2eN9zSUx6210DqLbhjo110PtpMvXqzQOIfmIiKv+awKxs7F2lIj1QUUJ6PpxCXVWSg==",
"peerDependencies": {
"lodash": "^4.17.21"
}
},
"node_modules/@ogre-tools/injectable": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable/-/injectable-12.0.1.tgz",
"integrity": "sha512-uOx8STN2wSc9hknDSTGqViyR89Vwg7rGacwrVNchgyo48/QJsmZZz6cd1Aw3nT4vr7ekjTc2lh0Rz6zGIv47hg==",
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable/-/injectable-15.1.1.tgz",
"integrity": "sha512-koB4z1FkaRbTEW77ULK1viVORlBCDnUtxAhxYiZrUzQcCvd7Fi4izs/YzDWLPc2HHay+EdJw11CuNC1JfzhaaA==",
"peerDependencies": {
"@ogre-tools/fp": "^12.0.0",
"@ogre-tools/fp": "*",
"lodash": "^4.17.21"
}
},
"node_modules/@ogre-tools/injectable-extension-for-auto-registration": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable-extension-for-auto-registration/-/injectable-extension-for-auto-registration-12.0.1.tgz",
"integrity": "sha512-itKcxEJ/J8SKGD/Wwj0UYOA+/nqwnrwanhikY6qhlibj8guujX77Iip7vMBzJFc2nIrRaQRcpNV2eXe+tjQUdg==",
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable-extension-for-auto-registration/-/injectable-extension-for-auto-registration-15.1.1.tgz",
"integrity": "sha512-kByRoG1FTWnB412nkF4GnKzim1ldLbSd9H2PUR6UF0EmjPg3QstyZXSE341bWlWZMAi/1HPiagfZ9E1wOP609w==",
"peerDependencies": {
"@ogre-tools/fp": "^12.0.0",
"@ogre-tools/injectable": "^12.0.0",
"@ogre-tools/fp": "*",
"@ogre-tools/injectable": "*",
"lodash": "^4.17.21"
}
},
"node_modules/@ogre-tools/injectable-extension-for-mobx": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable-extension-for-mobx/-/injectable-extension-for-mobx-12.0.1.tgz",
"integrity": "sha512-M1penOpZfO3/rJMb6WN4IL86p9Lx9tOMCipiNkAyitNLGWfeDPG279JlCs9E3Uw8R9nkFPiw8Je2SLnwnM9o+A==",
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable-extension-for-mobx/-/injectable-extension-for-mobx-15.1.1.tgz",
"integrity": "sha512-ZdIZGG9Zr/okGktICQFY5PzENerjdNAlwvuP1Na8bmIHJAs7yEwi6KlSuoOkZ1oNvQcHAsi9V2WVBh8jGyHN8g==",
"peerDependencies": {
"@ogre-tools/fp": "^12.0.0",
"@ogre-tools/injectable": "^12.0.0",
"@ogre-tools/fp": "*",
"@ogre-tools/injectable": "*",
"lodash": "^4.17.21",
"mobx": "^6.3.0"
}
},
"node_modules/@ogre-tools/injectable-react": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable-react/-/injectable-react-12.0.1.tgz",
"integrity": "sha512-LAOh/EHKqk/pQcBRZUAz0VcJwgBeIPxHwlV/Apw0aEBBoMuYLsLZh47rES8sMYMV6N5x7oVfkjMscujY0DCgaQ==",
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/@ogre-tools/injectable-react/-/injectable-react-15.1.1.tgz",
"integrity": "sha512-nJ1mH3FsL+9WbiWoIbs955rKONf/0jkw4UmEM2dBdi5dQ7G6MCZu/lh4sTcPm5u3g6ZoV7o6rUZjkwkM1qUcZw==",
"peerDependencies": {
"@ogre-tools/fp": "^12.0.0",
"@ogre-tools/injectable": "^12.0.0",
"@ogre-tools/fp": "*",
"@ogre-tools/injectable": "*",
"lodash": "^4.17.21",
"mobx": "^6.3.0",
"mobx-react": "^7.2.0",
"react": "^17.0.0",
"react-dom": "^17.0.0"
"react": "^17 || ^18",
"react-dom": "^17 || ^18"
}
},
"node_modules/@parcel/watcher": {
@ -5851,6 +5855,16 @@
"@types/node": "*"
}
},
"node_modules/@types/inquirer": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.3.tgz",
"integrity": "sha512-CzNkWqQftcmk2jaCWdBTf9Sm7xSw4rkI1zpU/Udw3HX5//adEZUIm9STtoRP1qgWj0CWQtJ9UTvqmO2NNjhMJw==",
"dev": true,
"dependencies": {
"@types/through": "*",
"rxjs": "^7.2.0"
}
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
@ -5952,15 +5966,6 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
"node_modules/@types/jsonfile": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz",
"integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/keyv": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
@ -6377,6 +6382,15 @@
"@types/jest": "*"
}
},
"node_modules/@types/through": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz",
"integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/tough-cookie": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
@ -8834,8 +8848,7 @@
"node_modules/chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"node_modules/chart.js": {
"version": "2.9.4",
@ -9092,7 +9105,6 @@
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz",
"integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==",
"dev": true,
"engines": {
"node": ">=6"
},
@ -9141,7 +9153,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
"dev": true,
"engines": {
"node": ">=0.8"
}
@ -10515,7 +10526,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"dev": true,
"dependencies": {
"clone": "^1.0.2"
},
@ -11074,6 +11084,11 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@ -13146,7 +13161,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"dependencies": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
@ -13160,7 +13174,6 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
@ -21648,8 +21661,7 @@
"node_modules/mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"node_modules/nan": {
"version": "2.17.0",
@ -25392,7 +25404,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -28090,6 +28101,7 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz",
"integrity": "sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==",
"dev": true,
"bin": {
"rimraf": "dist/cjs/src/bin.js"
},
@ -28122,7 +28134,6 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
"integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
@ -28163,7 +28174,6 @@
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
"integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
"dev": true,
"dependencies": {
"tslib": "^2.1.0"
}
@ -30263,7 +30273,6 @@
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"dependencies": {
"os-tmpdir": "~1.0.2"
},
@ -31301,7 +31310,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
"dev": true,
"dependencies": {
"defaults": "^1.0.3"
}
@ -32259,11 +32267,11 @@
"@k8slens/node-fetch": "^6.4.0-beta.13",
"@kubernetes/client-node": "^0.18.1",
"@material-ui/styles": "^4.11.5",
"@ogre-tools/fp": "^12.0.1",
"@ogre-tools/injectable": "^12.0.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^12.0.1",
"@ogre-tools/injectable-extension-for-mobx": "^12.0.1",
"@ogre-tools/injectable-react": "^12.0.1",
"@ogre-tools/fp": "^15.1.1",
"@ogre-tools/injectable": "^15.1.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.1",
"@ogre-tools/injectable-extension-for-mobx": "^15.1.1",
"@ogre-tools/injectable-react": "^15.1.1",
"@sentry/electron": "^3.0.8",
"@sentry/integrations": "^6.19.3",
"@side/jest-runtime": "^1.1.0",
@ -34290,11 +34298,11 @@
"@k8slens/core": "^6.4.0-beta.13",
"@k8slens/ensure-binaries": "^6.4.0-beta.13",
"@k8slens/generate-tray-icons": "^6.4.0-beta.13",
"@ogre-tools/fp": "^12.0.1",
"@ogre-tools/injectable": "^12.0.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^12.0.1",
"@ogre-tools/injectable-extension-for-mobx": "^12.0.1",
"@ogre-tools/injectable-react": "^12.0.1",
"@ogre-tools/fp": "^15.1.1",
"@ogre-tools/injectable": "^15.1.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.1",
"@ogre-tools/injectable-extension-for-mobx": "^15.1.1",
"@ogre-tools/injectable-react": "^15.1.1",
"mobx": "^6.8.0",
"rimraf": "^4.1.2"
},
@ -34461,7 +34469,9 @@
"version": "6.4.0-beta.13",
"license": "MIT",
"dependencies": {
"rimraf": "^4.1.2"
"chalk": "^5.2.0",
"inquirer": "^9.1.4",
"semver": "^7.3.8"
},
"bin": {
"create-release-pr": "dist/index.js"
@ -34469,23 +34479,10 @@
"devDependencies": {
"@swc/cli": "^0.1.61",
"@swc/core": "^1.3.35",
"@types/command-line-args": "^5.2.0",
"@types/fs-extra": "^11.0.1",
"@types/inquirer": "^9.0.3",
"@types/node": "^16.18.11",
"@types/semver": "^7.3.13",
"command-line-args": "^5.2.1",
"fs-extra": "^11.1.0",
"semver": "^7.3.8"
}
},
"packages/release-tool/node_modules/@types/fs-extra": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz",
"integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==",
"dev": true,
"dependencies": {
"@types/jsonfile": "*",
"@types/node": "*"
"rimraf": "^4.1.2"
}
},
"packages/release-tool/node_modules/@types/node": {
@ -34494,18 +34491,293 @@
"integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==",
"dev": true
},
"packages/release-tool/node_modules/fs-extra": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
"integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
"dev": true,
"packages/release-tool/node_modules/ansi-escapes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.0.0.tgz",
"integrity": "sha512-IG23inYII3dWlU2EyiAiGj6Bwal5GzsgPMwjYGvc1HPE2dgbj4ZB5ToWBKSquKw74nB3TIuOwaI6/jSULzfgrw==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
"type-fest": "^3.0.0"
},
"engines": {
"node": ">=14.14"
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"packages/release-tool/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"packages/release-tool/node_modules/bl": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
"integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
"dependencies": {
"buffer": "^6.0.3",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"packages/release-tool/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"packages/release-tool/node_modules/chalk": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
"integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"packages/release-tool/node_modules/cli-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
"dependencies": {
"restore-cursor": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/cli-width": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.0.0.tgz",
"integrity": "sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==",
"engines": {
"node": ">= 12"
}
},
"packages/release-tool/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"packages/release-tool/node_modules/escape-string-regexp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/figures": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz",
"integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==",
"dependencies": {
"escape-string-regexp": "^5.0.0",
"is-unicode-supported": "^1.2.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/inquirer": {
"version": "9.1.4",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.1.4.tgz",
"integrity": "sha512-9hiJxE5gkK/cM2d1mTEnuurGTAoHebbkX0BYl3h7iEg7FYfuNIom+nDfBCSWtvSnoSrWCeBxqqBZu26xdlJlXA==",
"dependencies": {
"ansi-escapes": "^6.0.0",
"chalk": "^5.1.2",
"cli-cursor": "^4.0.0",
"cli-width": "^4.0.0",
"external-editor": "^3.0.3",
"figures": "^5.0.0",
"lodash": "^4.17.21",
"mute-stream": "0.0.8",
"ora": "^6.1.2",
"run-async": "^2.4.0",
"rxjs": "^7.5.7",
"string-width": "^5.1.2",
"strip-ansi": "^7.0.1",
"through": "^2.3.6",
"wrap-ansi": "^8.0.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"packages/release-tool/node_modules/is-interactive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
"integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/is-unicode-supported": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/log-symbols": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz",
"integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==",
"dependencies": {
"chalk": "^5.0.0",
"is-unicode-supported": "^1.1.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/ora": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/ora/-/ora-6.1.2.tgz",
"integrity": "sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==",
"dependencies": {
"bl": "^5.0.0",
"chalk": "^5.0.0",
"cli-cursor": "^4.0.0",
"cli-spinners": "^2.6.1",
"is-interactive": "^2.0.0",
"is-unicode-supported": "^1.1.0",
"log-symbols": "^5.1.0",
"strip-ansi": "^7.0.1",
"wcwidth": "^1.0.1"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/restore-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
"dependencies": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/strip-ansi": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
"integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"packages/release-tool/node_modules/type-fest": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.6.0.tgz",
"integrity": "sha512-RqTRtKTzvPpNdDUp1dVkKQRunlPITk4mXeqFlAZoJsS+fLRn8AdPK0TcQDumGayhU7fjlBfiBjsq3pe3rIfXZQ==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/release-tool/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"packages/semver": {
@ -34536,10 +34808,17 @@
"version": "6.4.0-beta.13",
"license": "MIT",
"peerDependencies": {
"@ogre-tools/fp": "^12.0.1",
"@ogre-tools/injectable": "^12.0.1",
"@ogre-tools/fp": "^15.1.1",
"@ogre-tools/injectable": "^15.1.1",
"lodash": "^4.17.15"
}
},
"packages/technical-features/feature-core": {
"version": "0.0.1",
"license": "MIT",
"peerDependencies": {
"@ogre-tools/injectable": "^15.1.1"
}
}
}
}

View File

@ -57,7 +57,7 @@
"test:unit": "jest --testPathIgnorePatterns integration",
"test:watch": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func",
"lint": "PROD=true eslint --ext js,ts,tsx --max-warnings=0 .",
"lint:fix": "npm run lint --fix"
"lint:fix": "npm run lint -- --fix"
},
"config": {
"k8sProxyVersion": "0.3.0",
@ -130,11 +130,11 @@
"@k8slens/node-fetch": "^6.4.0-beta.13",
"@kubernetes/client-node": "^0.18.1",
"@material-ui/styles": "^4.11.5",
"@ogre-tools/fp": "^12.0.1",
"@ogre-tools/injectable": "^12.0.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^12.0.1",
"@ogre-tools/injectable-extension-for-mobx": "^12.0.1",
"@ogre-tools/injectable-react": "^12.0.1",
"@ogre-tools/fp": "^15.1.1",
"@ogre-tools/injectable": "^15.1.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.1",
"@ogre-tools/injectable-extension-for-mobx": "^15.1.1",
"@ogre-tools/injectable-react": "^15.1.1",
"@sentry/electron": "^3.0.8",
"@sentry/integrations": "^6.19.3",
"@side/jest-runtime": "^1.1.0",

View File

@ -74,8 +74,7 @@ describe("cluster-store", () => {
di.override(kubectlBinaryNameInjectable, () => "kubectl");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin");
createCluster = di.inject(createClusterInjectionToken);
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
writeJsonSync = di.inject(writeJsonSyncInjectable);
writeFileSync = di.inject(writeFileSyncInjectable);
writeBufferSync = di.inject(writeBufferSyncInjectable);
@ -85,6 +84,9 @@ describe("cluster-store", () => {
describe("empty config", () => {
beforeEach(async () => {
createCluster = di.inject(createClusterInjectionToken);
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {});
clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load();
@ -198,6 +200,10 @@ describe("cluster-store", () => {
},
],
});
createCluster = di.inject(createClusterInjectionToken);
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load();
});
@ -249,6 +255,10 @@ describe("cluster-store", () => {
},
],
});
createCluster = di.inject(createClusterInjectionToken);
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load();
});
@ -262,6 +272,11 @@ describe("cluster-store", () => {
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
beforeEach(() => {
di.override(clusterStoreMigrationVersionInjectable, () => "3.6.0");
createCluster = di.inject(createClusterInjectionToken);
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {
__internal__: {
migrations: {
@ -281,8 +296,6 @@ describe("cluster-store", () => {
});
writeBufferSync("/some-directory-for-user-data/icon_path", testDataIcon);
di.override(clusterStoreMigrationVersionInjectable, () => "3.6.0");
clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load();
});

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainer } from "@ogre-tools/injectable";
import kubectlApplyAllInjectable from "../../main/kubectl/kubectl-apply-all.injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import type { KubernetesCluster } from "../catalog-entities";
import readDirectoryInjectable from "../fs/read-directory.injectable";
import readFileInjectable from "../fs/read-file.injectable";
import createResourceStackInjectable from "../k8s/create-resource-stack.injectable";
import appPathsStateInjectable from "../app-paths/app-paths-state.injectable";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
describe("create resource stack tests", () => {
let di: DiContainer;
let cluster: KubernetesCluster;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
cluster = {
getId: () => "test-cluster",
} as any;
di.override(readDirectoryInjectable, () => () => Promise.resolve(["file1"]) as any);
di.override(readFileInjectable, () => () => Promise.resolve("filecontents"));
di.override(appPathsStateInjectable, () => ({
get: () => ({}),
}));
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
});
describe("kubectlApplyFolder", () => {
it("returns response", async () => {
di.override(kubectlApplyAllInjectable, () => () => Promise.resolve({
callWasSuccessful: true as const,
response: "success",
}));
const createResourceStack = di.inject(createResourceStackInjectable);
const resourceStack = createResourceStack(cluster, "test");
const response = await resourceStack.kubectlApplyFolder("/foo/bar");
expect(response).toEqual("success");
});
it("throws on error", async () => {
di.override(kubectlApplyAllInjectable, () => () => Promise.resolve({
callWasSuccessful: false as const,
error: "No permissions",
}));
const createResourceStack = di.inject(createResourceStackInjectable);
const resourceStack = createResourceStack(cluster, "test");
await expect(() => resourceStack.kubectlApplyFolder("/foo/bar")).rejects.toThrow("No permissions");
});
});
});

View File

@ -30,9 +30,9 @@ describe("user store tests", () => {
get: () => "latest" as const,
init: async () => {},
}));
await di.inject(defaultUpdateChannelInjectable).init();
userStore = di.inject(userStoreInjectable);
});
describe("for an empty config", () => {
@ -42,6 +42,8 @@ describe("user store tests", () => {
writeJsonSync("/some-directory-for-user-data/lens-user-store.json", {});
writeJsonSync("/some-directory-for-user-data/kube_config", {});
userStore = di.inject(userStoreInjectable);
userStore.load();
});
@ -90,6 +92,8 @@ describe("user store tests", () => {
di.override(userStoreMigrationVersionInjectable, () => "10.0.0");
userStore = di.inject(userStoreInjectable);
userStore.load();
});

View File

@ -4,7 +4,6 @@
*/
import type { DiContainer } from "@ogre-tools/injectable";
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
@ -18,6 +17,7 @@ import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import { KubeObjectStore } from "../kube-object.store";
import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
import { createClusterInjectionToken } from "../../cluster/create-cluster-injection-token";
// eslint-disable-next-line no-restricted-imports
import { KubeApi as ExternalKubeApi } from "../../../extensions/common-api/k8s-api";
@ -43,7 +43,7 @@ describe("ApiManager", () => {
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
const createCluster = di.inject(createClusterInjectionToken);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",

View File

@ -15,7 +15,6 @@ import setupAutoRegistrationInjectable from "../../../renderer/before-frame-star
import { createMockResponseFromString } from "../../../test-utils/mock-responses";
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import apiManagerInjectable from "../api-manager/manager.injectable";
@ -23,6 +22,7 @@ import type { DiContainer } from "@ogre-tools/injectable";
import ingressApiInjectable from "../endpoints/ingress.api.injectable";
import loggerInjectable from "../../logger.injectable";
import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
import { createClusterInjectionToken } from "../../cluster/create-cluster-injection-token";
describe("KubeApi", () => {
let fetchMock: AsyncFnMock<Fetch>;
@ -39,7 +39,7 @@ describe("KubeApi", () => {
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
const createCluster = di.inject(createClusterInjectionToken);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",

View File

@ -24,7 +24,6 @@ import setupAutoRegistrationInjectable from "../../../renderer/before-frame-star
import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses";
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable";
@ -36,6 +35,7 @@ import namespaceApiInjectable from "../endpoints/namespace.api.injectable";
// NOTE: this is fine because we are testing something that only exported
// eslint-disable-next-line no-restricted-imports
import { PodsApi } from "../../../extensions/common-api/k8s-api";
import { createClusterInjectionToken } from "../../cluster/create-cluster-injection-token";
describe("createKubeApiForRemoteCluster", () => {
let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster;
@ -48,7 +48,7 @@ describe("createKubeApiForRemoteCluster", () => {
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
const createCluster = di.inject(createClusterInjectionToken);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
@ -154,7 +154,7 @@ describe("KubeApi", () => {
fetchMock = asyncFn();
di.override(fetchInjectable, () => fetchMock);
const createCluster = di.inject(createClusterInjectable);
const createCluster = di.inject(createClusterInjectionToken);
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
di.override(hostedClusterInjectable, () => createCluster({

View File

@ -51,7 +51,7 @@ export class ResourceStack {
this.dependencies.logger.warn(`[RESOURCE-STACK]: failed to apply resources: ${result.error}`);
return "";
throw new Error(result.error);
}
/**

View File

@ -3,13 +3,20 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { createLogger, format } from "winston";
import type { Logger } from "./logger";
import winstonLoggerInjectable from "./winston-logger.injectable";
import { loggerTransportInjectionToken } from "./logger/transports";
const loggerInjectable = getInjectable({
id: "logger",
instantiate: (di): Logger => {
const baseLogger = di.inject(winstonLoggerInjectable);
const baseLogger = createLogger({
format: format.combine(
format.splat(),
format.simple(),
),
transports: di.injectMany(loggerTransportInjectionToken),
});
return {
debug: (message, ...data) => baseLogger.debug(message, ...data),

View File

@ -19,7 +19,10 @@ import asyncFn from "@async-fn/jest";
import { getPromiseStatus } from "../../test-utils/get-promise-status";
import { runInAction } from "mobx";
import type { RequestChannelHandler } from "../../../main/utils/channel/channel-listeners/listener-tokens";
import { getRequestChannelListenerInjectable } from "../../../main/utils/channel/channel-listeners/listener-tokens";
import {
getRequestChannelListenerInjectable,
requestChannelListenerInjectionToken,
} from "../../../main/utils/channel/channel-listeners/listener-tokens";
type TestMessageChannel = MessageChannel<string>;
type TestRequestChannel = RequestChannel<string, string>;
@ -199,21 +202,32 @@ describe("channel", () => {
it("when registering multiple handlers for the same channel, throws", async () => {
const applicationBuilder = getApplicationBuilder();
const testChannelListenerInMainInjectable = getRequestChannelListenerInjectable({
channel: testRequestChannel,
handler: () => () => "some-value",
});
const testChannelListenerInMain2Injectable = getRequestChannelListenerInjectable({
channel: testRequestChannel,
handler: () => () => "some-other-value",
const someChannelListenerInjectable = getInjectable({
id: "some-channel-listener",
instantiate: () => ({
channel: testRequestChannel,
handler: () => () => "irrelevant",
}),
injectionToken: requestChannelListenerInjectionToken,
});
testChannelListenerInMain2Injectable.id += "2";
const someOtherChannelListenerInjectable = getInjectable({
id: "some-other-channel-listener",
instantiate: () => ({
channel: testRequestChannel,
handler: () => () => "irrelevant",
}),
injectionToken: requestChannelListenerInjectionToken,
});
applicationBuilder.beforeApplicationStart((mainDi) => {
runInAction(() => {
mainDi.register(testChannelListenerInMainInjectable);
mainDi.register(testChannelListenerInMain2Injectable);
mainDi.register(someChannelListenerInjectable);
mainDi.register(someOtherChannelListenerInjectable);
});
});

View File

@ -19,7 +19,7 @@ const withOrphanPromiseInjectable = getInjectable({
toBeDecorated,
withErrorLoggingFor(() => "Orphan promise rejection encountered"),
withErrorSuppression,
);
) as ((...args: any[]) => any);
decorated(...args);
};

View File

@ -18,6 +18,6 @@ export const apiKubePrefix = "/api-kube"; // k8s cluster apis
// Links
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues" as string;
export const slackUrl = "https://k8slens.dev/slack.html" as string;
export const supportUrl = "https://docs.k8slens.dev/support/" as string;
export const docsUrl = "https://docs.k8slens.dev" as string;
export const forumsUrl = "https://forums.k8slens.dev" as string;

View File

@ -1,20 +0,0 @@
/**
* 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 { createLogger, format } from "winston";
import { loggerTransportInjectionToken } from "./logger/transports";
const winstonLoggerInjectable = getInjectable({
id: "winston-logger",
instantiate: (di) => createLogger({
format: format.combine(
format.splat(),
format.simple(),
),
transports: di.injectMany(loggerTransportInjectionToken),
}),
});
export default winstonLoggerInjectable;

View File

@ -11,7 +11,7 @@ import isWindowsInjectable from "../../common/vars/is-windows.injectable";
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import getEnabledExtensionsInjectable from "./get-enabled-extensions/get-enabled-extensions.injectable";
import { slackUrl, issuesTrackerUrl } from "../../common/vars";
import { issuesTrackerUrl } from "../../common/vars";
import { buildVersionInjectionToken } from "../../common/vars/build-semantic-version.injectable";
import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
@ -53,6 +53,9 @@ export const App = {
return di.inject(isLinuxInjectable);
},
slackUrl,
/**
* @deprecated This value is now `""` and is left here for backwards compatability.
*/
slackUrl: "",
issuesTrackerUrl,
} as const;

View File

@ -107,11 +107,11 @@ exports[`extension special characters in page registrations renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -107,11 +107,11 @@ exports[`navigate to extension page renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -107,11 +107,11 @@ exports[`add-cluster - navigation using application menu renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`installing update when started renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -413,11 +413,11 @@ exports[`installing update when started when user checks for updates renders 1`]
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -718,11 +718,11 @@ exports[`installing update when started when user checks for updates when new up
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1048,11 +1048,11 @@ exports[`installing update when started when user checks for updates when new up
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1378,11 +1378,11 @@ exports[`installing update when started when user checks for updates when new up
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1683,11 +1683,11 @@ exports[`installing update when started when user checks for updates when no new
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -133,11 +133,11 @@ exports[`encourage user to update when sufficient time passed since update was d
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -438,11 +438,11 @@ exports[`encourage user to update when sufficient time passed since update was d
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`installing update using tray when started renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -413,11 +413,11 @@ exports[`installing update using tray when started when user checks for updates
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -718,11 +718,11 @@ exports[`installing update using tray when started when user checks for updates
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1048,11 +1048,11 @@ exports[`installing update using tray when started when user checks for updates
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1378,11 +1378,11 @@ exports[`installing update using tray when started when user checks for updates
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1683,11 +1683,11 @@ exports[`installing update using tray when started when user checks for updates
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -133,11 +133,11 @@ exports[`force user to update when too long since update was downloaded when app
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -463,11 +463,11 @@ exports[`force user to update when too long since update was downloaded when app
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -840,11 +840,11 @@ exports[`force user to update when too long since update was downloaded when app
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`periodical checking of updates given updater is enabled and configurati
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`selection of update stability when started renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`opening catalog entity details panel renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -199,11 +199,11 @@ exports[`Command Pallet: keyboard shortcut tests when on linux renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -595,11 +595,11 @@ exports[`Command Pallet: keyboard shortcut tests when on linux when pressing ESC
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -991,11 +991,11 @@ exports[`Command Pallet: keyboard shortcut tests when on linux when pressing SHI
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1531,11 +1531,11 @@ exports[`Command Pallet: keyboard shortcut tests when on linux when pressing SHI
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1836,11 +1836,11 @@ exports[`Command Pallet: keyboard shortcut tests when on macOS renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -2141,11 +2141,11 @@ exports[`Command Pallet: keyboard shortcut tests when on macOS when pressing ESC
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -2446,11 +2446,11 @@ exports[`Command Pallet: keyboard shortcut tests when on macOS when pressing SHI
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -2847,11 +2847,11 @@ exports[`Command Pallet: keyboard shortcut tests when on macOS when pressing SHI
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`Showing correct entity settings renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -894,7 +894,40 @@ exports[`Showing correct entity settings when navigating to non-local cluster en
>
Proxy
</h2>
<section />
<section>
<section>
<div
class="SubTitle"
id="http-proxy"
>
HTTP Proxy
</div>
<div
class="Input theme round black"
>
<label
class="input-area flex gaps align-center"
id=""
>
<input
class="input box grow"
placeholder="http://<address>:<port>"
spellcheck="false"
value=""
/>
</label>
<div
class="input-info flex gaps"
/>
</div>
<small
class="hint"
>
HTTP Proxy server. Used for communicating with Kubernetes API.
</small>
</section>
</section>
</section>
</div>
<div

View File

@ -107,11 +107,11 @@ exports[`extensions - navigation using application menu renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -107,11 +107,11 @@ exports[`preferences - navigation using application menu renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`show-about-using-tray renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`status-bar-items-originating-from-extensions when application starts wh
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -108,11 +108,11 @@ exports[`extendability-using-extension-api given an extension with a weakly type
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -422,11 +422,11 @@ exports[`extendability-using-extension-api given an extension with top-bar items
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -727,11 +727,11 @@ exports[`extendability-using-extension-api given an extension with top-bar items
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1032,11 +1032,11 @@ exports[`extendability-using-extension-api renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -107,11 +107,11 @@ exports[`welcome - navigation using application menu renders 1`] = `
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>
@ -1182,11 +1182,11 @@ exports[`welcome - navigation using application menu when navigated somewhere el
If you have any questions or feedback, please join our
<a
class="link"
href="https://k8slens.dev/slack.html"
href="https://forums.k8slens.dev"
rel="noreferrer"
target="_blank"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -16,7 +16,8 @@ export function registerInjectables(di: DiContainer) {
autoRegister({
di,
requireContexts: [
targetModule: module,
getRequireContexts: () => [
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),

View File

@ -10,8 +10,8 @@ export const lensWebsiteLinkName = "Lens Website";
export const lensDocumentationWeblinkId = "lens-documentation-link";
export const lensDocumentationWeblinkName = "Lens Documentation";
export const lensSlackWeblinkId = "lens-slack-link";
export const lensSlackWeblinkName = "Lens Community Slack";
export const lensForumsWeblinkId = "lens-forums-link";
export const lensForumsWeblinkName = "Lens Forums";
export const lensTwitterWeblinkId = "lens-twitter-link";
export const lensTwitterWeblinkName = "Lens on Twitter";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { docsUrl, slackUrl } from "../../../common/vars";
import { docsUrl, forumsUrl } from "../../../common/vars";
import type { WeblinkData } from "../../../common/weblinks-store/weblink-store";
import { getInjectable } from "@ogre-tools/injectable";
import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token";
@ -20,7 +20,7 @@ const v514WeblinkStoreMigrationInjectable = getInjectable({
weblinks.push(
{ id: "https://k8slens.dev", name: links.lensWebsiteLinkName, url: "https://k8slens.dev" },
{ id: docsUrl, name: links.lensDocumentationWeblinkName, url: docsUrl },
{ id: slackUrl, name: links.lensSlackWeblinkName, url: slackUrl },
{ id: forumsUrl, name: links.lensForumsWeblinkName, url: forumsUrl },
{ id: "https://twitter.com/k8slens", name: links.lensTwitterWeblinkName, url: "https://twitter.com/k8slens" },
{ id: "https://medium.com/k8slens", name: links.lensBlogWeblinkName, url: "https://medium.com/k8slens" },
{ id: "https://kubernetes.io/docs/home/", name: links.kubernetesDocumentationWeblinkName, url: "https://kubernetes.io/docs/home/" },

View File

@ -16,40 +16,40 @@ const v545Beta1WeblinkStoreMigrationInjectable = getInjectable({
const weblinksRaw = store.get("weblinks");
const weblinks = (Array.isArray(weblinksRaw) ? weblinksRaw : []) as WeblinkData[];
const lensWebsiteLink = weblinks.find(weblink => weblink.name === links.lensWebsiteLinkName);
const lensWebsite = weblinks.find(weblink => weblink.name === links.lensWebsiteLinkName);
if (lensWebsiteLink) {
lensWebsiteLink.id = links.lensWebsiteWeblinkId;
if (lensWebsite) {
lensWebsite.id = links.lensWebsiteWeblinkId;
}
const lensDocumentationWeblinkLink = weblinks.find(weblink => weblink.name === links.lensDocumentationWeblinkName);
const lensDocumentationWeblink = weblinks.find(weblink => weblink.name === links.lensDocumentationWeblinkName);
if (lensDocumentationWeblinkLink) {
lensDocumentationWeblinkLink.id = links.lensDocumentationWeblinkId;
if (lensDocumentationWeblink) {
lensDocumentationWeblink.id = links.lensDocumentationWeblinkId;
}
const lensSlackWeblinkLink = weblinks.find(weblink => weblink.name === links.lensSlackWeblinkName);
const lensForumsWeblink = weblinks.find(weblink => weblink.name === links.lensForumsWeblinkName);
if (lensSlackWeblinkLink) {
lensSlackWeblinkLink.id = links.lensSlackWeblinkId;
if (lensForumsWeblink) {
lensForumsWeblink.id = links.lensForumsWeblinkId;
}
const lensTwitterWeblinkLink = weblinks.find(weblink => weblink.name === links.lensTwitterWeblinkName);
const lensTwitterWeblink = weblinks.find(weblink => weblink.name === links.lensTwitterWeblinkName);
if (lensTwitterWeblinkLink) {
lensTwitterWeblinkLink.id = links.lensTwitterWeblinkId;
if (lensTwitterWeblink) {
lensTwitterWeblink.id = links.lensTwitterWeblinkId;
}
const lensBlogWeblinkLink = weblinks.find(weblink => weblink.name === links.lensBlogWeblinkName);
const lensBlogWeblink = weblinks.find(weblink => weblink.name === links.lensBlogWeblinkName);
if (lensBlogWeblinkLink) {
lensBlogWeblinkLink.id = links.lensBlogWeblinkId;
if (lensBlogWeblink) {
lensBlogWeblink.id = links.lensBlogWeblinkId;
}
const kubernetesDocumentationWeblinkLink = weblinks.find(weblink => weblink.name === links.kubernetesDocumentationWeblinkName);
const kubernetesDocumentationWeblink = weblinks.find(weblink => weblink.name === links.kubernetesDocumentationWeblinkName);
if (kubernetesDocumentationWeblinkLink) {
kubernetesDocumentationWeblinkLink.id = links.kubernetesDocumentationWeblinkId;
if (kubernetesDocumentationWeblink) {
kubernetesDocumentationWeblink.id = links.kubernetesDocumentationWeblinkId;
}
store.set("weblinks", weblinks);

View File

@ -3,11 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { docsUrl, slackUrl } from "../../../common/vars";
import { docsUrl, forumsUrl } from "../../../common/vars";
import type { WeblinkData } from "../../../common/weblinks-store/weblink-store";
import { getInjectable } from "@ogre-tools/injectable";
import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token";
import { lensDocumentationWeblinkId, lensSlackWeblinkId } from "../links";
import { lensDocumentationWeblinkId, lensForumsWeblinkId } from "../links";
import { applicationInformationToken } from "@k8slens/application";
const currentVersionWeblinkStoreMigrationInjectable = getInjectable({
@ -20,10 +20,10 @@ const currentVersionWeblinkStoreMigrationInjectable = getInjectable({
run(store) {
const weblinksRaw = store.get("weblinks");
const weblinks = (Array.isArray(weblinksRaw) ? weblinksRaw : []) as WeblinkData[];
const slackWeblink = weblinks.find(weblink => weblink.id === lensSlackWeblinkId);
const forumsWeblink = weblinks.find(weblink => weblink.id === lensForumsWeblinkId);
if (slackWeblink) {
slackWeblink.url = slackUrl;
if (forumsWeblink) {
forumsWeblink.url = forumsUrl;
}
const docsWeblink = weblinks.find(weblink => weblink.id === lensDocumentationWeblinkId);
@ -32,7 +32,9 @@ const currentVersionWeblinkStoreMigrationInjectable = getInjectable({
docsWeblink.url = docsUrl;
}
store.set("weblinks", weblinks);
const removedSlackLink = weblinks.filter(weblink => weblink.id !== "lens-slack-link");
store.set("weblinks", removedSlackLink);
},
};
},

View File

@ -46,9 +46,7 @@ export async function bootstrap(di: DiContainer) {
}
try {
await initializeApp(() => {
unmountComponentAtNode(rootElem);
});
await initializeApp(() => unmountComponentAtNode(rootElem));
} catch (error) {
console.error(`[BOOTSTRAP]: view initialization error: ${error}`, {
origin: location.href,

View File

@ -10,7 +10,7 @@ import type { IComputedValue } from "mobx";
import type { CarouselProps } from "react-material-ui-carousel";
import LegacyCarousel from "react-material-ui-carousel";
import { Icon } from "../icon";
import { slackUrl } from "../../../common/vars";
import { forumsUrl } from "../../../common/vars";
import { withInjectables } from "@ogre-tools/injectable-react";
import welcomeMenuItemsInjectable from "./welcome-menu-items/welcome-menu-items.injectable";
import type { WelcomeMenuRegistration } from "./welcome-menu-items/welcome-menu-registration";
@ -93,12 +93,12 @@ const NonInjectedWelcome = observer(({
<br />
{"If you have any questions or feedback, please join our "}
<a
href={slackUrl}
href={forumsUrl}
target="_blank"
rel="noreferrer"
className="link"
>
Lens Community slack channel
Lens Forums
</a>
.
</p>

View File

@ -9,7 +9,7 @@ import type { ErrorInfo } from "react";
import React from "react";
import { observer } from "mobx-react";
import { Button } from "../button";
import { issuesTrackerUrl, slackUrl } from "../../../common/vars";
import { issuesTrackerUrl, forumsUrl } from "../../../common/vars";
import type { SingleOrMany } from "../../utils";
import type { ObservableHistory } from "mobx-observable-history";
import { withInjectables } from "@ogre-tools/injectable-react";
@ -53,15 +53,15 @@ class NonInjectedErrorBoundary extends React.Component<ErrorBoundaryProps & Depe
</h5>
<p>
{"To help us improve the product please report bugs to "}
{"To help us improve the product please report bugs on"}
<a
href={slackUrl}
href={forumsUrl}
rel="noreferrer"
target="_blank"
>
Slack
Lens Forums
</a>
{" community or "}
{" or on our"}
<a
href={issuesTrackerUrl}
rel="noreferrer"

View File

@ -19,8 +19,8 @@ import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-create
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
import type { PodStore } from "../+workloads-pods/store";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
describe("kube-object-list-layout", () => {
let di: DiContainer;
@ -34,7 +34,7 @@ describe("kube-object-list-layout", () => {
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
const createCluster = di.inject(createClusterInjectionToken);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",

View File

@ -12,7 +12,6 @@ import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.
import loadExtensionsInjectable from "../../load-extensions.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import showErrorNotificationInjectable from "../../../components/notifications/show-error-notification.injectable";
import closeRendererLogFileInjectable from "../../../logger/close-renderer-log-file.injectable";
const initClusterFrameInjectable = getInjectable({
id: "init-cluster-frame",
@ -30,7 +29,6 @@ const initClusterFrameInjectable = getInjectable({
emitAppEvent: di.inject(emitAppEventInjectable),
logger: di.inject(loggerInjectable),
showErrorNotification: di.inject(showErrorNotificationInjectable),
closeFileLogging: di.inject(closeRendererLogFileInjectable),
});
},
});

View File

@ -2,7 +2,6 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { once } from "lodash";
import type { Cluster } from "../../../../common/cluster/cluster";
import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
import type { ShowNotification } from "../../../components/notifications";
@ -19,7 +18,6 @@ interface Dependencies {
emitAppEvent: EmitAppEvent;
logger: Logger;
showErrorNotification: ShowNotification;
closeFileLogging: () => void;
}
const logPrefix = "[CLUSTER-FRAME]:";
@ -32,7 +30,6 @@ export const initClusterFrame = ({
emitAppEvent,
logger,
showErrorNotification,
closeFileLogging,
}: Dependencies) =>
async (unmountRoot: () => void) => {
await requestSetClusterFrameId(hostedCluster.id);
@ -72,14 +69,11 @@ export const initClusterFrame = ({
});
});
const onCloseFrame = once(() => {
window.onbeforeunload = () => {
logger.info(
`${logPrefix} Unload dashboard, clusterId=${(hostedCluster.id)}, frameId=${frameRoutingId}`,
);
closeFileLogging();
unmountRoot();
});
window.addEventListener("beforeunload", onCloseFrame);
window.addEventListener("pagehide", onCloseFrame);
unmountRoot();
};
};

View File

@ -12,7 +12,6 @@ import loggerInjectable from "../../../common/logger.injectable";
import { delay } from "../../../common/utils";
import { broadcastMessage } from "../../../common/ipc";
import { bundledExtensionsLoaded } from "../../../common/ipc/extension-handling";
import closeRendererLogFileInjectable from "../../logger/close-renderer-log-file.injectable";
const initRootFrameInjectable = getInjectable({
id: "init-root-frame",
@ -23,7 +22,6 @@ const initRootFrameInjectable = getInjectable({
const bindProtocolAddRouteHandlers = di.inject(bindProtocolAddRouteHandlersInjectable);
const lensProtocolRouterRenderer = di.inject(lensProtocolRouterRendererInjectable);
const logger = di.inject(loggerInjectable);
const closeRendererLogFile = di.inject(closeRendererLogFileInjectable);
return async (unmountRoot: () => void) => {
try {
@ -57,7 +55,7 @@ const initRootFrameInjectable = getInjectable({
window.addEventListener("beforeunload", () => {
logger.info("[ROOT-FRAME]: Unload app");
closeRendererLogFile();
unmountRoot();
});
};

View File

@ -20,6 +20,7 @@ import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.injecta
import type { GlobalOverride } from "../common/test-utils/get-global-override";
import nodeEnvInjectionToken from "../common/vars/node-env-injection-token";
import { applicationInformationFakeInjectable } from "../common/vars/application-information-fake-injectable";
import { registerInjectableReact } from "@ogre-tools/injectable-react";
export const getDiForUnitTesting = (
opts: { doGeneralOverrides?: boolean } = {},
@ -46,6 +47,7 @@ export const getDiForUnitTesting = (
) as Injectable<any, any, any>[];
registerMobX(di);
registerInjectableReact(di);
runInAction(() => {
di.register(applicationInformationFakeInjectable);

View File

@ -1,22 +0,0 @@
/**
* 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 winstonLoggerInjectable from "../../common/winston-logger.injectable";
import rendererFileLoggerTransportInjectable from "./file-transport.injectable";
const closeRendererLogFileInjectable = getInjectable({
id: "close-renderer-log-file",
instantiate: (di) => {
const winstonLogger = di.inject(winstonLoggerInjectable);
const fileLoggingTransport = di.inject(rendererFileLoggerTransportInjectable);
return () => {
fileLoggingTransport.close?.();
winstonLogger.remove(fileLoggingTransport);
};
},
});
export default closeRendererLogFileInjectable;

View File

@ -1,44 +0,0 @@
/**
* 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 { transports } from "winston";
import directoryForLogsInjectable from "../../common/app-paths/directory-for-logs.injectable";
import { loggerTransportInjectionToken } from "../../common/logger/transports";
import windowLocationInjectable from "../../common/k8s-api/window-location.injectable";
import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable";
import { getClusterIdFromHost } from "../utils";
const rendererFileLoggerTransportInjectable = getInjectable({
id: "renderer-file-logger-transport",
instantiate: (di) => {
let frameId: string;
const currentlyInClusterFrame = di.inject(
currentlyInClusterFrameInjectable,
);
if (currentlyInClusterFrame) {
const { host } = di.inject(windowLocationInjectable);
const clusterId = getClusterIdFromHost(host);
frameId = clusterId ? `cluster-${clusterId}` : "cluster";
} else {
frameId = "main";
}
return new transports.File({
handleExceptions: false,
level: "info",
filename: `lens-renderer-${frameId}.log`,
dirname: di.inject(directoryForLogsInjectable),
maxsize: 1024 * 1024,
maxFiles: 2,
tailable: true,
});
},
injectionToken: loggerTransportInjectionToken,
});
export default rendererFileLoggerTransportInjectable;

View File

@ -6,18 +6,22 @@
import type { DiContainer } from "@ogre-tools/injectable";
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
import { registerInjectableReact } from "@ogre-tools/injectable-react";
import { runInAction } from "mobx";
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
export function registerInjectables(di: DiContainer) {
setLegacyGlobalDiForExtensionApi(di, Environments.renderer);
registerMobX(di);
registerInjectableReact(di);
runInAction(() => {
registerMobX(di);
autoRegister({
di,
requireContexts: [
targetModule: module,
getRequireContexts: () => [
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES),

View File

@ -199,11 +199,11 @@
"@k8slens/core": "^6.4.0-beta.13",
"@k8slens/ensure-binaries": "^6.4.0-beta.13",
"@k8slens/generate-tray-icons": "^6.4.0-beta.13",
"@ogre-tools/fp": "^12.0.1",
"@ogre-tools/injectable": "^12.0.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^12.0.1",
"@ogre-tools/injectable-extension-for-mobx": "^12.0.1",
"@ogre-tools/injectable-react": "^12.0.1",
"@ogre-tools/fp": "^15.1.1",
"@ogre-tools/injectable": "^15.1.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.1",
"@ogre-tools/injectable-extension-for-mobx": "^15.1.1",
"@ogre-tools/injectable-react": "^15.1.1",
"mobx": "^6.8.0",
"rimraf": "^4.1.2"
},

View File

@ -13,7 +13,8 @@ runInAction(() => {
try {
autoRegister({
di,
requireContexts: [
targetModule: module,
getRequireContexts: () => [
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
],

View File

@ -13,7 +13,8 @@ const app = createApp({
runInAction(() => {
autoRegister({
di,
requireContexts: [
targetModule: module,
getRequireContexts: () => [
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
],

View File

@ -16,15 +16,14 @@
"devDependencies": {
"@swc/cli": "^0.1.61",
"@swc/core": "^1.3.35",
"@types/command-line-args": "^5.2.0",
"@types/fs-extra": "^11.0.1",
"@types/inquirer": "^9.0.3",
"@types/node": "^16.18.11",
"@types/semver": "^7.3.13",
"command-line-args": "^5.2.1",
"fs-extra": "^11.1.0",
"semver": "^7.3.8"
"rimraf": "^4.1.2"
},
"dependencies": {
"rimraf": "^4.1.2"
"chalk": "^5.2.0",
"inquirer": "^9.1.4",
"semver": "^7.3.8"
}
}

View File

@ -3,122 +3,34 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import child_process from "child_process";
import commandLineArgs from "command-line-args";
import fse from "fs-extra";
import { basename } from "path";
import { createInterface } from "readline";
import assert from "assert";
import chalk from "chalk";
import child_process, { spawn } from "child_process";
import { readFile } from "fs/promises";
import inquirer from "inquirer";
import { createInterface, ReadLine } from "readline";
import semver from "semver";
import { promisify } from "util";
const {
SemVer,
valid: semverValid,
rcompare: semverRcompare,
lte: semverLte,
} = semver;
type SemVer = semver.SemVer;
const { SemVer } = semver;
const exec = promisify(child_process.exec);
const execFile = promisify(child_process.execFile);
const options = commandLineArgs([
{
name: "type",
defaultOption: true,
},
{
name: "preid",
},
{
name: "check-commits",
type: Boolean,
},
]);
async function pipeExecFile(file: string, args: string[], opts?: { stdin: string }) {
const p = execFile(file, args);
const validReleaseValues = [
"major",
"minor",
"patch",
];
const validPrereleaseValues = [
"premajor",
"preminor",
"prepatch",
"prerelease",
];
const validPreidValues = [
"alpha",
"beta",
];
p.child.stdout?.pipe(process.stdout);
p.child.stderr?.pipe(process.stderr);
const errorMessages = {
noReleaseType: `No release type provided. Valid options are: ${[...validReleaseValues, ...validPrereleaseValues].join(", ")}`,
invalidRelease: (invalid: string) => `Invalid release type was provided (value was "${invalid}"). Valid options are: ${[...validReleaseValues, ...validPrereleaseValues].join(", ")}`,
noPreid: `No preid was provided. Use '--preid' to specify. Valid options are: ${validPreidValues.join(", ")}`,
invalidPreid: (invalid: string) => `Invalid preid was provided (value was "${invalid}"). Valid options are: ${validPreidValues.join(", ")}`,
wrongCwd: "It looks like you are running this script from the 'scripts' directory. This script assumes it is run from the root of the git repo",
};
if (!options.type) {
console.error(errorMessages.noReleaseType);
process.exit(1);
}
if (validReleaseValues.includes(options.type)) {
// do nothing, is valid
} else if (validPrereleaseValues.includes(options.type)) {
if (!options.preid) {
console.error(errorMessages.noPreid);
process.exit(1);
if (opts) {
p.child.stdin?.end(opts.stdin);
}
if (!validPreidValues.includes(options.preid)) {
console.error(errorMessages.invalidPreid(options.preid));
process.exit(1);
}
} else {
console.error(errorMessages.invalidRelease(options.type));
process.exit(1);
await p;
}
if (basename(process.cwd()) === "scripts") {
console.error(errorMessages.wrongCwd);
}
const currentVersion = new SemVer((await fse.readJson("./lerna.json")).version);
console.log(`current version: ${currentVersion.format()}`);
const newVersion = currentVersion.inc(options.type, options.preid);
const newVersionMilestone = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
const prBranch = `release/v${newVersion.format()}`;
await exec(`npm run bump-version --yes ${newVersion.format()}`);
await exec(`git checkout -b ${prBranch}`);
await exec("git add lerna.json packages/*/package.json");
await exec(`git commit -sm "Release ${newVersion.format()}"`);
console.log(`new version: ${newVersion.format()}`);
console.log("fetching tags...");
await exec("git fetch --tags --force");
const actualTags = (await exec("git tag --list", { encoding: "utf-8" })).stdout.split(/\r?\n/).map(line => line.trim());
const [previousReleasedVersion] = actualTags
.map((value) => semverValid(value))
.filter((v): v is string => typeof v === "string")
.sort((l, r) => semverRcompare(l, r))
.filter(version => semverLte(version, currentVersion));
const getMergedPrsArgs = [
"gh",
"pr",
"list",
"--limit=500", // Should be big enough, if not we need to release more often ;)
"--state=merged",
"--base=master",
"--json mergeCommit,title,author,labels,number,milestone,mergedAt",
];
interface GithubPrData {
author: {
login: string;
@ -147,168 +59,285 @@ interface ExtendedGithubPrData extends Omit<GithubPrData, "mergedAt"> {
mergedAt: Date;
}
console.log("retreiving last 500 PRs to create release PR body...");
const mergedPrs = JSON.parse((await exec(getMergedPrsArgs.join(" "), { encoding: "utf-8" })).stdout) as GithubPrData[];
const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone?.title === newVersionMilestone);
const relaventPrsQuery = await Promise.all(
milestoneRelevantPrs.map(async pr => ({
pr,
stdout: (await exec(`git tag v${previousReleasedVersion} --no-contains ${pr.mergeCommit.oid}`)).stdout,
})),
);
const relaventPrs = relaventPrsQuery
.filter(query => query.stdout)
.map(query => query.pr)
.filter(pr => pr.labels.every(label => label.name !== "skip-changelog"))
.map(pr => ({ ...pr, mergedAt: new Date(pr.mergedAt) } as ExtendedGithubPrData))
.sort((left, right) => {
const leftAge = left.mergedAt.valueOf();
const rightAge = right.mergedAt.valueOf();
async function getCurrentBranch(): Promise<string> {
return (await exec("git branch --show-current")).stdout.trim();
}
if (leftAge === rightAge) {
return 0;
}
async function getAbsolutePathToRepoRoot(): Promise<string> {
return (await exec("git rev-parse --show-toplevel")).stdout.trim();
}
if (leftAge > rightAge) {
return 1;
}
async function fetchAllGitTags(): Promise<string[]> {
await execFile("git", ["fetch", "--tags", "--force"]);
return -1;
const { stdout } = await exec("git tag --list", { encoding: "utf-8" });
return stdout
.split(/\r?\n/)
.map(line => line.trim());
}
function bumpPackageVersions() {
const bumpPackages = spawn("npm", ["run", "bump-version"], {
stdio: "inherit"
});
const cleaners: (() => void)[] = [
() => bumpPackages.stdout?.unpipe(),
() => bumpPackages.stderr?.unpipe(),
];
const cleanup = () => cleaners.forEach(clean => clean());
const enhancementPrLabelName = "enhancement";
const bugfixPrLabelName = "bug";
return new Promise<void>((resolve, reject) => {
const onExit = (code: number | null) => {
cleanup();
if (code) {
reject(new Error(`"npm run bump-version" failed with code ${code}`));
} else {
resolve();
}
};
const onError = (error: Error) => {
cleanup();
reject(error);
};
const isEnhancementPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === enhancementPrLabelName);
const isBugfixPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === bugfixPrLabelName);
bumpPackages.once("error", onError);
cleaners.push(() => bumpPackages.off("error", onError));
const prLines = {
enhancement: [] as string[],
bugfix: [] as string[],
maintenence: [] as string[],
};
bumpPackages.once("exit", onExit);
cleaners.push(() => bumpPackages.off("exit", onExit));
});
}
function getPrEntry(pr: ExtendedGithubPrData) {
function isDefined<T>(value: T | null | undefined): value is T {
return value != null;
}
function findClosestVersionTagLessThanVersion(tags: string[], version: SemVer): string {
const lessThanTags = tags
.map((value) => semver.parse(value))
.filter(isDefined)
.filter(version => !version.prerelease.includes("cron"))
.sort(semver.rcompare)
.filter(version => semver.lte(version, version));
assert(lessThanTags.length > 0, `Cannot find version tag less than ${version.format()}`);
return lessThanTags[0].format();
}
async function getCurrentVersionOfSubPackage(packageName: string): Promise<SemVer> {
const packageJson = JSON.parse(await readFile(`./packages/${packageName}/package.json`, "utf-8"));
return new SemVer(packageJson.version);
}
async function checkCurrentWorkingDirectory(): Promise<void> {
const repoRoot = await getAbsolutePathToRepoRoot();
if (process.cwd() !== repoRoot) {
console.error("It looks like you are running this script from the 'scripts' directory. This script assumes it is run from the root of the git repo");
process.exit(1);
}
}
function formatSemverForMilestone(version: SemVer): string {
return `${version.major}.${version.minor}.${version.patch}`;
}
async function createReleaseBranchAndCommit(prBase: string, version: SemVer, prBody: string): Promise<void> {
const prBranch = `release/v${version.format()}`;
await pipeExecFile("git", ["checkout", "-b", prBranch]);
await pipeExecFile("git", ["add", "lerna.json", "packages/*/package.json"]);
await pipeExecFile("git", ["commit", "-sm", `Release ${version.format()}`]);
await pipeExecFile("git", ["push", "--set-upstream", "origin", prBranch]);
await pipeExecFile("gh", [
"pr",
"create",
"--base", prBase,
"--title", `Release ${version.format()}`,
"--label", "skip-changelog",
"--label", "release",
"--milestone", formatSemverForMilestone(version),
"--body-file", "-",
], {
stdin: prBody,
});
}
function sortExtendedGithubPrData(left: ExtendedGithubPrData, right: ExtendedGithubPrData): number {
const leftAge = left.mergedAt.valueOf();
const rightAge = right.mergedAt.valueOf();
if (leftAge === rightAge) {
return 0;
}
if (leftAge > rightAge) {
return 1;
}
return -1;
}
async function getRelevantPRs(milestone: string, previousReleasedVersion: string): Promise<ExtendedGithubPrData[]> {
console.log("retrieving previous 200 PRs...");
const getMergedPrsArgs = [
"gh",
"pr",
"list",
"--limit=500", // Should be big enough, if not we need to release more often ;)
"--state=merged",
"--base=master",
"--json mergeCommit,title,author,labels,number,milestone,mergedAt",
];
const mergedPrs = JSON.parse((await exec(getMergedPrsArgs.join(" "), { encoding: "utf-8" })).stdout) as GithubPrData[];
const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone?.title === milestone);
const relevantPrsQuery = await Promise.all(
milestoneRelevantPrs.map(async pr => ({
pr,
stdout: (await exec(`git tag v${previousReleasedVersion} --no-contains ${pr.mergeCommit.oid}`)).stdout,
})),
);
return relevantPrsQuery
.filter(query => query.stdout)
.map(query => query.pr)
.filter(pr => pr.labels.every(label => label.name !== "skip-changelog"))
.map(pr => ({ ...pr, mergedAt: new Date(pr.mergedAt) } as ExtendedGithubPrData))
.sort(sortExtendedGithubPrData);
}
function formatPrEntry(pr: ExtendedGithubPrData) {
return `- ${pr.title} (**[#${pr.number}](https://github.com/lensapp/lens/pull/${pr.number})**) https://github.com/${pr.author.login}`;
}
const rl = createInterface(process.stdin);
const prBase = newVersion.patch === 0
? "master"
: `release/v${newVersion.major}.${newVersion.minor}`;
const isEnhancementPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === "enhancement");
const isBugfixPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === "bug");
function askQuestion(question: string): Promise<boolean> {
return new Promise<boolean>(resolve => {
function _askQuestion() {
console.log(question);
const cherryPickCommitWith = (rl: ReadLine) => async (commit: string) => {
try {
await pipeExecFile("git", ["cherry-pick", commit]);
} catch {
console.error(chalk.bold("Please resolve conflicts in a separate terminal and then press enter here..."));
await new Promise<void>(resolve => rl.once("line", () => resolve()));
}
};
rl.once("line", (answer) => {
const cleaned = answer.trim().toLowerCase();
if (cleaned === "y") {
resolve(true);
} else if (cleaned === "n") {
resolve(false);
} else {
_askQuestion();
}
});
}
_askQuestion();
async function pickWhichPRsToUse(prs: ExtendedGithubPrData[]): Promise<ExtendedGithubPrData[]> {
const answers = await inquirer.prompt<{ commits: number[] }>({
type: "checkbox",
name: `commits`,
message: "Pick which commits to use...",
default: [],
choices: prs.map(pr => ({
checked: true,
key: pr.number,
name: `#${pr.number}: ${pr.title} (https://github.com/lensapp/lens/pull/${pr.number})`,
value: pr.number,
short: `#${pr.number}`,
})),
loop: false,
});
return prs.filter(pr => answers.commits.includes(pr.number));
}
async function handleRelaventPr(pr: ExtendedGithubPrData) {
if (options["check-commits"] && !(await askQuestion(`Would you like to use #${pr.number}: ${pr.title}? - Y/N`))) {
return;
}
function formatChangelog(previousReleasedVersion: string, prs: ExtendedGithubPrData[]): string {
const enhancementPrLines: string[] = [];
const bugPrLines: string[] = [];
const maintenancePrLines: string[] = [];
if (prBase !== "master") {
try {
const promise = exec(`git cherry-pick ${pr.mergeCommit.oid}`);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
promise.child.stdout!.pipe(process.stdout);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
promise.child.stderr!.pipe(process.stderr);
await promise;
} catch {
console.error(`Failed to cherry-pick ${pr.mergeCommit.oid}, please resolve conflicts and then press enter here:`);
await new Promise<void>(resolve => rl.once("line", () => resolve()));
for (const pr of prs) {
if (isEnhancementPr(pr)) {
enhancementPrLines.push(formatPrEntry(pr));
} else if (isBugfixPr(pr)) {
bugPrLines.push(formatPrEntry(pr));
} else {
maintenancePrLines.push(formatPrEntry(pr));
}
}
if (isEnhancementPr(pr)) {
prLines.enhancement.push(getPrEntry(pr));
} else if (isBugfixPr(pr)) {
prLines.bugfix.push(getPrEntry(pr));
} else {
prLines.maintenence.push(getPrEntry(pr));
if (enhancementPrLines.length > 0) {
enhancementPrLines.unshift("## 🚀 Features", "");
enhancementPrLines.push("");
}
if (bugPrLines.length > 0) {
bugPrLines.unshift("## 🐛 Bug Fixes", "");
bugPrLines.push("");
}
if (maintenancePrLines.length > 0) {
maintenancePrLines.unshift("## 🧰 Maintenance", "");
maintenancePrLines.push("");
}
return [
`## Changes since ${previousReleasedVersion}`,
"",
...enhancementPrLines,
...bugPrLines,
...maintenancePrLines,
].join("\n");
}
for (const pr of relaventPrs) {
await handleRelaventPr(pr);
async function cherryPickCommits(prs: ExtendedGithubPrData[]): Promise<void> {
const rl = createInterface(process.stdin);
const cherryPickCommit = cherryPickCommitWith(rl);
for (const pr of prs) {
await cherryPickCommit(pr.mergeCommit.oid);
}
rl.close();
}
rl.close();
async function pickRelevantPrs(prs: ExtendedGithubPrData[], isMasterBranch: boolean): Promise<ExtendedGithubPrData[]> {
if (isMasterBranch) {
return prs;
}
const prBodyLines = [
`## Changes since ${previousReleasedVersion}`,
"",
...(
prLines.enhancement.length > 0
? [
"## 🚀 Features",
"",
...prLines.enhancement,
"",
]
: []
),
...(
prLines.bugfix.length > 0
? [
"## 🐛 Bug Fixes",
"",
...prLines.bugfix,
"",
]
: []
),
...(
prLines.maintenence.length > 0
? [
"## 🧰 Maintenance",
"",
...prLines.maintenence,
"",
]
: []
),
];
const prBody = prBodyLines.join("\n");
const createPrArgs = [
"pr",
"create",
"--base", prBase,
"--title", `Release ${newVersion.format()}`,
"--label", "skip-changelog",
"--label", "release",
"--milestone", `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`,
"--body-file", "-",
];
let selectedPrs: ExtendedGithubPrData[];
await exec(`git push --set-upstream origin ${prBranch}`);
do {
selectedPrs = await pickWhichPRsToUse(prs);
} while (selectedPrs.length === 0 && (console.warn("[WARNING]: must pick at least once commit"), true));
const createPrProcess = execFile("gh", createPrArgs);
await cherryPickCommits(selectedPrs);
createPrProcess.child.stdout?.pipe(process.stdout);
createPrProcess.child.stderr?.pipe(process.stderr);
return selectedPrs;
}
createPrProcess.child.stdin?.write(prBody);
createPrProcess.child.stdin?.end();
async function createRelease(): Promise<void> {
await checkCurrentWorkingDirectory();
await createPrProcess;
const currentK8slensCoreVersion = await getCurrentVersionOfSubPackage("core");
const prBase = await getCurrentBranch();
const isMasterBranch = prBase === "master";
const tags = await fetchAllGitTags();
const previousReleasedVersion = findClosestVersionTagLessThanVersion(tags, currentK8slensCoreVersion);
if (isMasterBranch) {
await bumpPackageVersions();
}
const prMilestone = formatSemverForMilestone(await getCurrentVersionOfSubPackage("core"));
const relevantPrs = await getRelevantPRs(prMilestone, previousReleasedVersion);
const selectedPrs = await pickRelevantPrs(relevantPrs, isMasterBranch);
const prBody = formatChangelog(previousReleasedVersion, selectedPrs);
if (!isMasterBranch) {
await bumpPackageVersions();
}
const newK8slensCoreVersion = await getCurrentVersionOfSubPackage("core");
await createReleaseBranchAndCommit(prBase, newK8slensCoreVersion, prBody);
}
await createRelease();

View File

@ -29,8 +29,8 @@
"test": "jest --coverage --runInBand"
},
"peerDependencies": {
"@ogre-tools/fp": "^12.0.1",
"@ogre-tools/injectable": "^12.0.1",
"@ogre-tools/fp": "^15.1.1",
"@ogre-tools/injectable": "^15.1.1",
"lodash": "^4.17.15"
}
}

View File

@ -0,0 +1,41 @@
# @k8slens/feature-core
Feature is set of injectables that are registered and deregistered simultaneously.
## Install
```bash
$ npm install @k8slens/feature-core
```
## Usage
```typescript
import { createContainer } from "@ogre-tools/injectable"
import { getFeature, registerFeature, deregisterFeature } from "@k8slens/feature-core"
// Notice that this Feature is usually exported from another NPM package.
const someFeature = getFeature({
id: "some-feature",
register: (di) => {
di.register(someInjectable, someOtherInjectable);
},
// Feature dependencies are automatically registered and
// deregistered when necessary.
dependencies: [someOtherFeature]
});
const di = createContainer("some-container");
registerFeature(di, someFeature);
// Or perhaps you want to deregister?
deregisterFeature(di, someFeature);
```
## Need to know
#### NPM packages exporting a Feature
- Prefer `peerDependencies` since they are installed from the application and are not allowed to be in the built bundle.
- Prefer exporting `injectionToken` instead of `injectable` for not allowing other features to access technical details like the `injectable`

View File

@ -0,0 +1,3 @@
export { getFeature } from "./src/feature";
export { registerFeature } from "./src/register-feature";
export type { Feature, GetFeatureArgs } from "./src/feature";

View File

@ -0,0 +1,2 @@
module.exports =
require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;

View File

@ -0,0 +1,30 @@
{
"name": "@k8slens/feature-core",
"private": false,
"version": "0.0.1",
"description": "Code that is common to all Features and those registering them.",
"type": "commonjs",
"files": [
"dist"
],
"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": {
"@ogre-tools/injectable": "^15.1.1"
}
}

View File

@ -0,0 +1,82 @@
import type { DiContainer } from "@ogre-tools/injectable";
import type { Feature } from "./feature";
import { featureContextMapInjectable } from "./feature-context-map-injectable";
export const deregisterFeature = (di: DiContainer, ...features: Feature[]) => {
features.forEach((feature) => {
deregisterFeatureRecursed(di, feature);
});
};
const deregisterFeatureRecursed = (
di: DiContainer,
feature: Feature,
dependedBy?: Feature
) => {
const featureContextMap = di.inject(featureContextMapInjectable);
const featureContext = featureContextMap.get(feature);
if (!featureContext) {
throw new Error(
`Tried to deregister feature "${feature.id}", but it was not registered.`
);
}
featureContext.numberOfRegistrations--;
const getDependingFeatures = getDependingFeaturesFor(featureContextMap);
const dependingFeatures = getDependingFeatures(feature);
if (!dependedBy && dependingFeatures.length) {
throw new Error(
`Tried to deregister Feature "${
feature.id
}", but it is the dependency of Features "${dependingFeatures.join(
", "
)}"`
);
}
if (dependedBy) {
const oldNumberOfDependents = featureContext.dependedBy.get(dependedBy)!;
const newNumberOfDependants = oldNumberOfDependents - 1;
featureContext.dependedBy.set(dependedBy, newNumberOfDependants);
if (newNumberOfDependants === 0) {
featureContext.dependedBy.delete(dependedBy);
}
}
if (featureContext.numberOfRegistrations === 0) {
featureContextMap.delete(feature);
featureContext.deregister();
}
feature.dependencies?.forEach((dependency) => {
deregisterFeatureRecursed(di, dependency, feature);
});
};
const getDependingFeaturesFor = (
featureContextMap: Map<Feature, { dependedBy: Map<Feature, number> }>
) => {
const getDependingFeaturesForRecursion = (
feature: Feature,
atRoot = true
): string[] => {
const context = featureContextMap.get(feature);
if (context?.dependedBy.size) {
return [...context!.dependedBy.entries()].flatMap(([dependant]) =>
getDependingFeaturesForRecursion(dependant, false)
);
}
return atRoot ? [] : [feature.id];
};
return getDependingFeaturesForRecursion;
};

View File

@ -0,0 +1,27 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { Feature } from "./feature";
export type FeatureContextMap = Map<
Feature,
{
register: () => void;
deregister: () => void;
dependedBy: Map<Feature, number>;
numberOfRegistrations: number;
}
>;
export const featureContextMapInjectionToken =
getInjectionToken<FeatureContextMap>({
id: "feature-context-map-injection-token",
});
const featureContextMapInjectable = getInjectable({
id: "feature-store",
instantiate: (): FeatureContextMap => new Map(),
injectionToken: featureContextMapInjectionToken,
});
export { featureContextMapInjectable };

View File

@ -0,0 +1,281 @@
import {
createContainer,
DiContainer,
getInjectable,
Injectable,
} from "@ogre-tools/injectable";
import type { Feature } from "./feature";
import { registerFeature } from "./register-feature";
import { deregisterFeature } from "./deregister-feature";
import { getFeature } from "./feature" ;
describe("feature-dependencies", () => {
describe("given a parent Feature with another Features as dependency", () => {
let di: DiContainer;
let someInjectable: Injectable<string>;
let someInjectableInDependencyFeature: Injectable<string>;
let someParentFeature: Feature;
let someDependencyFeature: Feature;
beforeEach(() => {
di = createContainer("irrelevant");
someInjectable = getInjectable({
id: "some-injectable-2",
instantiate: () => "some-instance",
});
someInjectableInDependencyFeature = getInjectable({
id: "some-injectable",
instantiate: () => "some-instance-2",
});
someDependencyFeature = getFeature({
id: "some-dependency-feature",
register: (di) => di.register(someInjectableInDependencyFeature),
});
someParentFeature = getFeature({
id: "some-feature",
register: (di) => di.register(someInjectable),
dependencies: [someDependencyFeature],
});
registerFeature(di, someParentFeature);
});
it("when an injectable from the dependency Feature is injected, does so", () => {
const actual = di.inject(someInjectableInDependencyFeature);
expect(actual).toBe("some-instance-2");
});
it("when the dependency Feature is deregistered, throws", () => {
expect(() => {
deregisterFeature(di, someDependencyFeature);
}).toThrow(
'Tried to deregister Feature "some-dependency-feature", but it is the dependency of Features "some-feature"'
);
});
it("given the parent Feature is already deregistered, when also the dependency Feature is deregistered, throws", () => {
deregisterFeature(di, someParentFeature);
expect(() => {
deregisterFeature(di, someDependencyFeature);
}).toThrow(
'Tried to deregister feature "some-dependency-feature", but it was not registered.'
);
});
it("given the parent Feature is deregistered, when injecting an injectable from the dependency Feature, throws", () => {
deregisterFeature(di, someParentFeature);
expect(() => {
di.inject(someInjectableInDependencyFeature);
}).toThrow(
'Tried to inject non-registered injectable "irrelevant" -> "some-injectable".'
);
});
});
describe("given a first Feature is registered, when second Feature using the first Feature as dependency gets registered", () => {
let di: DiContainer;
let someInjectable: Injectable<string>;
let someFeature2: Feature;
let someFeature1: Feature;
beforeEach(() => {
di = createContainer("irrelevant");
someInjectable = getInjectable({
id: "some-injectable",
instantiate: () => "some-instance",
});
someFeature1 = getFeature({
id: "some-feature-1",
register: (di) => di.register(someInjectable),
});
someFeature2 = getFeature({
id: "some-feature-2",
register: () => {},
dependencies: [someFeature1],
});
registerFeature(di, someFeature1, someFeature2);
});
it("when the first Feature is deregistered, throws", () => {
expect(() => {
deregisterFeature(di, someFeature1);
}).toThrow(
'Tried to deregister Feature "some-feature-1", but it is the dependency of Features "some-feature-2"'
);
});
it("given the second Feature is deregistered, when injecting an injectable from the first Feature, still does so", () => {
deregisterFeature(di, someFeature2);
const actual = di.inject(someInjectable);
expect(actual).toBe("some-instance");
});
});
describe("given parent Features with a shared Feature as dependency", () => {
let di: DiContainer;
let someInjectableInDependencyFeature: Injectable<string>;
let someFeature1: Feature;
let someFeature2: Feature;
let someSharedDependencyFeature: Feature;
beforeEach(() => {
di = createContainer("irrelevant");
someInjectableInDependencyFeature = getInjectable({
id: "some-injectable-in-dependency-feature",
instantiate: () => "some-instance",
});
someSharedDependencyFeature = getFeature({
id: "some-dependency-feature",
register: (di) => di.register(someInjectableInDependencyFeature),
});
const someFeatureForAdditionalHierarchy = getFeature({
id: "some-feature-for-additional-hierarchy",
register: () => {},
dependencies: [someSharedDependencyFeature],
});
someFeature1 = getFeature({
id: "some-feature-1",
register: () => {},
dependencies: [someFeatureForAdditionalHierarchy],
});
someFeature2 = getFeature({
id: "some-feature-2",
register: () => {},
dependencies: [someFeatureForAdditionalHierarchy],
});
registerFeature(di, someFeature1, someFeature2);
});
it("when the shared Feature is deregistered, throws", () => {
expect(() => {
deregisterFeature(di, someSharedDependencyFeature);
}).toThrow(
'Tried to deregister Feature "some-dependency-feature", but it is the dependency of Features "some-feature-1, some-feature-2"'
);
});
it("given only part of the parent Features get deregistered, when injecting an injectable from the shared Feature, does so", () => {
deregisterFeature(di, someFeature1);
const actual = di.inject(someInjectableInDependencyFeature);
expect(actual).toBe("some-instance");
});
it("given all of the parent Features get deregistered, when injecting an injectable from the shared Feature, throws", () => {
deregisterFeature(di, someFeature1, someFeature2);
expect(() => {
di.inject(someInjectableInDependencyFeature);
}).toThrow(
'Tried to inject non-registered injectable "irrelevant" -> "some-injectable-in-dependency-feature".'
);
});
});
describe("given parent Features with a shared Feature as dependency and registered, when the shared Feature gets registered again", () => {
let di: DiContainer;
let someInjectableInDependencyFeature: Injectable<string>;
let someFeature1: Feature;
let someFeature2: Feature;
let someSharedDependencyFeature: Feature;
beforeEach(() => {
di = createContainer("irrelevant");
someInjectableInDependencyFeature = getInjectable({
id: "some-injectable-in-dependency-feature",
instantiate: () => "some-instance",
});
someSharedDependencyFeature = getFeature({
id: "some-dependency-feature",
register: (di) => di.register(someInjectableInDependencyFeature),
});
const someFeatureForAdditionalHierarchy = getFeature({
id: "some-feature-for-additional-hierarchy",
register: () => {},
dependencies: [someSharedDependencyFeature],
});
someFeature1 = getFeature({
id: "some-feature-1",
register: () => {},
dependencies: [someFeatureForAdditionalHierarchy],
});
someFeature2 = getFeature({
id: "some-feature-2",
register: () => {},
dependencies: [someFeatureForAdditionalHierarchy],
});
registerFeature(
di,
someFeature1,
someFeature2,
someSharedDependencyFeature
);
});
it("when the shared Feature is deregistered, throws", () => {
expect(() => {
deregisterFeature(di, someSharedDependencyFeature);
}).toThrow(
'Tried to deregister Feature "some-dependency-feature", but it is the dependency of Features "some-feature-1, some-feature-2"'
);
});
it("given only part of the parent Features get deregistered, when injecting an injectable from the shared Feature, does so", () => {
deregisterFeature(di, someFeature1);
const actual = di.inject(someInjectableInDependencyFeature);
expect(actual).toBe("some-instance");
});
it("given all of the parent Features get deregistered, when injecting an injectable from the shared Feature, still does so", () => {
deregisterFeature(di, someFeature1, someFeature2);
const actual = di.inject(someInjectableInDependencyFeature);
expect(actual).toBe("some-instance");
});
it("given all of the Features get deregistered, when injecting an injectable from the shared Feature, throws", () => {
deregisterFeature(
di,
someFeature1,
someFeature2,
someSharedDependencyFeature
);
expect(() => {
di.inject(someInjectableInDependencyFeature);
}).toThrow(
'Tried to inject non-registered injectable "irrelevant" -> "some-injectable-in-dependency-feature".'
);
});
});
});

View File

@ -0,0 +1,12 @@
import type { DiContainerForInjection } from "@ogre-tools/injectable";
export interface Feature {
id: string;
register: (di: DiContainerForInjection) => void;
dependencies?: Feature[];
}
export interface GetFeatureArgs extends Feature {}
export const getFeature = (getFeatureArgs: GetFeatureArgs): Feature =>
getFeatureArgs;

View File

@ -0,0 +1,90 @@
import type { DiContainer } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable";
import type { Feature } from "./feature";
import {
featureContextMapInjectable,
featureContextMapInjectionToken,
} from "./feature-context-map-injectable";
export const registerFeature = (di: DiContainer, ...features: Feature[]) => {
features.forEach((feature) => {
registerFeatureRecursed(di, feature);
});
};
const registerFeatureRecursed = (
di: DiContainer,
feature: Feature,
dependedBy?: Feature
) => {
const featureContextMaps = di.injectMany(featureContextMapInjectionToken);
if (featureContextMaps.length === 0) {
di.register(featureContextMapInjectable);
}
const featureContextMap = di.inject(featureContextMapInjectable);
const existingFeatureContext = featureContextMap.get(feature);
if (
!dependedBy &&
existingFeatureContext &&
existingFeatureContext.dependedBy.size === 0
) {
throw new Error(
`Tried to register feature "${feature.id}", but it was already registered.`
);
}
const featureContext =
existingFeatureContext || createFeatureContext(feature, di);
featureContext.numberOfRegistrations++;
if (dependedBy) {
const oldNumberOfDependents =
featureContext.dependedBy.get(dependedBy) || 0;
const newNumberOfDependants = oldNumberOfDependents + 1;
featureContext.dependedBy.set(dependedBy, newNumberOfDependants);
}
if (!existingFeatureContext) {
featureContext.register();
}
feature.dependencies?.forEach((dependency) => {
registerFeatureRecursed(di, dependency, feature);
});
};
const createFeatureContext = (feature: Feature, di: DiContainer) => {
const featureContextInjectable = getInjectable({
id: feature.id,
instantiate: (diForContextOfFeature) => ({
register: () => {
feature.register(diForContextOfFeature);
},
deregister: () => {
diForContextOfFeature.deregister(featureContextInjectable);
},
dependedBy: new Map<Feature, number>(),
numberOfRegistrations: 0,
}),
scope: true,
});
di.register(featureContextInjectable);
const featureContextMap = di.inject(featureContextMapInjectable);
const featureContext = di.inject(featureContextInjectable);
featureContextMap.set(feature, featureContext);
return featureContext;
};

View File

@ -0,0 +1,147 @@
import { registerFeature } from "./register-feature";
import {
createContainer,
DiContainer,
getInjectable,
Injectable,
} from "@ogre-tools/injectable";
import type { Feature } from "./feature";
import { getFeature } from "./feature";
import { deregisterFeature } from "./deregister-feature";
describe("register-feature", () => {
describe("given di-container and a Features with injectables, and given Features are registered", () => {
let di: DiContainer;
let someInjectable: Injectable<string>;
let someInjectable2: Injectable<string>;
let someFeature: Feature;
let someFeature2: Feature;
let instance: string;
beforeEach(() => {
di = createContainer("irrelevant");
someInjectable = getInjectable({
id: "some-injectable",
instantiate: () => "some-instance",
});
someInjectable2 = getInjectable({
id: "some-injectable-2",
instantiate: () => "some-instance-2",
});
someFeature = getFeature({
id: "some-feature-1",
register: (di) => di.register(someInjectable),
});
someFeature2 = getFeature({
id: "some-feature-2",
register: (di) => di.register(someInjectable2),
});
registerFeature(di, someFeature);
registerFeature(di, someFeature2);
});
it("when an injectable is injected, does so", () => {
instance = di.inject(someInjectable);
expect(instance).toBe("some-instance");
});
describe("given a Feature is deregistered", () => {
beforeEach(() => {
deregisterFeature(di, someFeature);
});
it("when injecting a related injectable, throws", () => {
expect(() => {
di.inject(someInjectable);
}).toThrow();
});
it("when injecting an unrelated injectable, does so", () => {
const instance = di.inject(someInjectable2);
expect(instance).toBe("some-instance-2");
});
describe("given the Feature is registered again", () => {
beforeEach(() => {
registerFeature(di, someFeature);
});
it("when injecting a related injectable, does so", () => {
const instance = di.inject(someInjectable);
expect(instance).toBe("some-instance");
});
it("when injecting an unrelated injectable, does so", () => {
const instance = di.inject(someInjectable2);
expect(instance).toBe("some-instance-2");
});
});
});
it("when a Feature is registered again, throws", () => {
expect(() => {
registerFeature(di, someFeature);
}).toThrow(
'Tried to register feature "some-feature-1", but it was already registered.'
);
});
it("given a Feature deregistered, when deregistered again, throws", () => {
deregisterFeature(di, someFeature);
expect(() => {
deregisterFeature(di, someFeature);
}).toThrow(
'Tried to deregister feature "some-feature-1", but it was not registered.'
);
});
});
it("given di-container and registered Features with injectables forming a cycle, when an injectable is injected, throws with namespaced error about cycle", () => {
const someInjectable: Injectable<any> = getInjectable({
id: "some-injectable-1",
instantiate: (di) => di.inject(someInjectable2),
});
const someInjectable2: Injectable<any> = getInjectable({
id: "some-injectable-2",
instantiate: (di) => di.inject(someInjectable),
});
const di = createContainer("some-container");
const someFeature = getFeature({
id: "some-feature-1",
register: (di) => {
di.register(someInjectable);
},
});
const someFeature2 = getFeature({
id: "some-feature-2",
register: (di) => {
di.register(someInjectable2);
},
});
registerFeature(di, someFeature, someFeature2);
expect(() => {
di.inject(someInjectable);
}).toThrow(
// 'Cycle of injectables encountered: "some-container" -> "some-feature-1:some-injectable-1" -> "some-feature-2:some-injectable-2" -> "some-feature-1:some-injectable-1"'
'Maximum call stack size exceeded'
);
});
});

View File

@ -0,0 +1,3 @@
{
"extends": "@k8slens/typescript/config/base.json"
}

View File

@ -0,0 +1 @@
module.exports = require("@k8slens/webpack").configForNode;