├── .github ├── config │ ├── configuration.json │ └── labels.yml ├── dependabot.yml ├── release.yml └── workflows │ ├── build.yml │ ├── dependabot-prs.yml │ ├── jextract.yml │ └── sync-labels.yml ├── .gitignore ├── .sdkmanrc ├── README.md ├── backend ├── README.md ├── agent │ ├── jfr │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── suresh │ │ │ ├── AgentType.kt │ │ │ ├── App.kt │ │ │ └── system │ │ │ ├── BlockSystemExitAgent.kt │ │ │ └── SystemExitInvoker.kt │ └── otel │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── opentelemetry │ │ └── extensions │ │ └── HttpResponseCustomizer.java ├── boot │ ├── build.gradle.kts │ ├── compose.yaml │ └── src │ │ ├── main │ │ ├── kotlin │ │ │ └── dev │ │ │ │ └── suresh │ │ │ │ ├── App.kt │ │ │ │ ├── aot │ │ │ │ └── AotConfig.kt │ │ │ │ └── books │ │ │ │ └── Books.kt │ │ └── resources │ │ │ ├── application.yml │ │ │ └── schema.sql │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── AppTest.kt ├── data │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── suresh │ │ ├── DataApp.kt │ │ └── gen │ │ └── GenArt.kt ├── jvm │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ ├── java │ │ │ └── module-info.java.txt │ │ ├── kotlin │ │ │ └── dev │ │ │ │ └── suresh │ │ │ │ ├── App.kt │ │ │ │ ├── config │ │ │ │ └── AppConfig.kt │ │ │ │ ├── lang │ │ │ │ ├── FFM.kt │ │ │ │ ├── TM.kt │ │ │ │ └── VThread.kt │ │ │ │ ├── log │ │ │ │ └── RespLogger.kt │ │ │ │ ├── plugins │ │ │ │ ├── Error.kt │ │ │ │ ├── Http.kt │ │ │ │ ├── OTel.kt │ │ │ │ ├── Security.kt │ │ │ │ └── custom │ │ │ │ │ ├── CookieSession.kt │ │ │ │ │ └── Plugins.kt │ │ │ │ ├── routes │ │ │ │ ├── Admin.kt │ │ │ │ ├── Mgmt.kt │ │ │ │ ├── Scheduler.kt │ │ │ │ ├── Service.kt │ │ │ │ └── Webapp.kt │ │ │ │ └── wasm │ │ │ │ └── Wasm.kt │ │ └── resources │ │ │ ├── META-INF │ │ │ └── native-image │ │ │ │ ├── native-image.properties │ │ │ │ └── reachability-metadata.json │ │ │ ├── app │ │ │ └── index.html │ │ │ ├── application.conf │ │ │ ├── backend-jvm-res.txt │ │ │ ├── logback.xml │ │ │ ├── openapi │ │ │ └── documentation.yaml │ │ │ ├── otel │ │ │ └── sdk-config.yaml │ │ │ ├── templates │ │ │ └── FileBrowser.kte │ │ │ └── wasm │ │ │ ├── factorial.wasm │ │ │ └── hello-wasi.wasm │ │ └── test │ │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── AppTests.kt │ │ │ ├── DnsTest.kt │ │ │ ├── K8STests.kt │ │ │ ├── TestLogger.kt │ │ │ └── http │ │ │ └── TestHttpClient.kt │ │ └── resources │ │ └── hosts_disabled ├── native │ ├── build.gradle.kts │ └── src │ │ ├── appleMain │ │ └── kotlin │ │ │ └── Env.kt │ │ ├── linuxMain │ │ └── kotlin │ │ │ └── Env.kt │ │ ├── mingwMain │ │ └── kotlin │ │ │ └── Env.kt │ │ ├── nativeMain │ │ └── kotlin │ │ │ ├── Main.kt │ │ │ ├── io │ │ │ └── IO.kt │ │ │ └── wasm │ │ │ └── Wasm.kt │ │ └── nativeTest │ │ └── kotlin │ │ └── NativeTest.kt ├── profiling │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── suresh │ │ ├── JFR.kt │ │ └── Profiling.kt └── security │ ├── api │ ├── security.api │ └── security.klib.api │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── dev │ └── suresh │ ├── SecApp.kt │ ├── TrustStore.kt │ ├── cert │ ├── CertScan.kt │ └── PemExtns.kt │ ├── security │ └── PasswordGen.kt │ └── tls │ ├── AliasKeyManager.kt │ ├── CustomSSLSocketFactory.kt │ ├── SavingTrustManager.kt │ └── TLSProp.kt ├── benchmark ├── README.md ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── CommonBenchmark.kt │ └── jvmMain │ └── kotlin │ └── JvmBenchmark.kt ├── build.gradle.kts ├── compose ├── README.md ├── cli │ ├── build.gradle.kts │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── suresh │ │ │ ├── Counter.kt │ │ │ └── Gradient.kt │ │ └── jvmMain │ │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── App.kt ├── cmp │ ├── build.gradle.kts │ └── src │ │ ├── assets │ │ ├── common │ │ │ └── splash.jpg │ │ ├── icons │ │ │ ├── linux.png │ │ │ ├── mac.icns │ │ │ └── win.ico │ │ └── macos-arm64 │ │ │ └── resource.txt │ │ ├── commonMain │ │ ├── composeResources │ │ │ ├── drawable │ │ │ │ ├── animated_svg │ │ │ │ │ ├── audio.svg │ │ │ │ │ ├── ball-triangle.svg │ │ │ │ │ ├── bars.svg │ │ │ │ │ ├── circles.svg │ │ │ │ │ ├── grid.svg │ │ │ │ │ ├── hearts.svg │ │ │ │ │ ├── oval.svg │ │ │ │ │ ├── puff.svg │ │ │ │ │ ├── rings.svg │ │ │ │ │ ├── spinning-circles.svg │ │ │ │ │ ├── tail-spin.svg │ │ │ │ │ └── three-dots.svg │ │ │ │ ├── compose-logo.svg │ │ │ │ ├── compose-multiplatform.xml │ │ │ │ ├── ic-fluent-rocket.svg │ │ │ │ ├── idea-logo.svg │ │ │ │ └── particles.gif │ │ │ └── files │ │ │ │ ├── .keep │ │ │ │ └── lottie │ │ │ │ └── anim.json │ │ └── kotlin │ │ │ ├── App.kt │ │ │ ├── nav │ │ │ ├── NavRoot.kt │ │ │ └── Screen.kt │ │ │ └── ui │ │ │ ├── Image.kt │ │ │ ├── Modifier.kt │ │ │ ├── Theme.kt │ │ │ ├── birds │ │ │ ├── BirdsScreen.kt │ │ │ └── BirdsViewModel.kt │ │ │ ├── file │ │ │ └── FileBrowser.kt │ │ │ ├── home │ │ │ └── HomeScreen.kt │ │ │ ├── lottie │ │ │ └── Animation.kt │ │ │ └── misc │ │ │ └── Window.kt │ │ ├── commonTest │ │ └── kotlin │ │ │ └── ui │ │ │ └── ComposeTest.kt │ │ ├── jvmMain │ │ └── kotlin │ │ │ ├── Main.kt │ │ │ └── ui │ │ │ ├── Image.jvm.kt │ │ │ ├── crash │ │ │ └── CrashDialog.kt │ │ │ └── file │ │ │ ├── FileBrowser.jvm.kt │ │ │ └── FileChooser.jvm.kt │ │ └── wasmJsMain │ │ ├── kotlin │ │ ├── Main.kt │ │ └── ui │ │ │ └── file │ │ │ └── FileBrowser.wasmJs.kt │ │ └── resources │ │ ├── images │ │ └── compose.svg │ │ ├── index.html │ │ └── manifest.json └── html │ ├── .kobweb │ └── conf.yaml │ ├── build.gradle.kts │ └── src │ └── jsMain │ ├── kotlin │ └── dev │ │ └── suresh │ │ ├── MyApp.kt │ │ ├── components │ │ ├── layouts │ │ │ └── PageLayout.kt │ │ └── sections │ │ │ ├── Footer.kt │ │ │ └── NavHeader.kt │ │ └── pages │ │ └── Index.kt │ └── resources │ ├── markdown │ └── About.md │ ├── public │ ├── favicon.ico │ └── kobweb-logo.png │ └── test.html ├── dep-mgmt ├── bom │ └── build.gradle.kts └── catalog │ └── build.gradle.kts ├── gradle.properties ├── gradle ├── .githooks │ └── pre-commit ├── build-logic │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ ├── jte │ │ ├── BuildConfig.kte │ │ └── TableConfig.kte │ │ ├── kotlin │ │ ├── common │ │ │ ├── CommonExtns.kt │ │ │ ├── GithubAction.kt │ │ │ ├── Multiplatform.kt │ │ │ ├── PatchModuleArgProvider.kt │ │ │ ├── Platform.kt │ │ │ ├── ProjectExtns.kt │ │ │ └── SettingsExtns.kt │ │ ├── dev.suresh.plugin.catalog.gradle.kts │ │ ├── dev.suresh.plugin.common.gradle.kts │ │ ├── dev.suresh.plugin.graalvm.gradle.kts │ │ ├── dev.suresh.plugin.kotlin.benchmark.gradle.kts │ │ ├── dev.suresh.plugin.kotlin.docs.gradle.kts │ │ ├── dev.suresh.plugin.kotlin.jvm.gradle.kts │ │ ├── dev.suresh.plugin.kotlin.mpp.gradle.kts │ │ ├── dev.suresh.plugin.publishing.gradle.kts │ │ ├── dev.suresh.plugin.repos.settings.gradle.kts │ │ ├── dev.suresh.plugin.root.gradle.kts │ │ ├── plugins │ │ │ ├── DepReportsPlugin.kt │ │ │ ├── GenericPlugin.kt │ │ │ └── XvfbServer.kt │ │ └── tasks │ │ │ ├── BuildConfig.kt │ │ │ ├── ExecTask.kt │ │ │ ├── Jdeprscan.kt │ │ │ ├── MultiReleaseJar.kt │ │ │ ├── ReallyExecJar.kt │ │ │ └── SampleTask.kt │ │ └── resources │ │ └── exec-jar-stub.sh ├── gradle-daemon-jvm.properties ├── kotlin-js-store │ ├── package-lock.json │ └── wasm │ │ └── package-lock.json ├── libs.versions.toml ├── verification-keyring.keys └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── meta ├── README.md ├── compiler │ └── plugin │ │ ├── build.gradle.kts │ │ └── src │ │ └── test │ │ └── kotlin │ │ └── PluginTest.kt ├── ksp │ └── processor │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── kotlin │ │ └── TestProcessor.kt └── scripts │ ├── JavaSingleFile │ ├── Mach-O-Bin.sh │ ├── cpu-mem-viz.sh │ ├── jextract │ ├── c │ │ ├── ioctl.h │ │ └── windows.h │ ├── gen │ │ ├── linux.sh │ │ ├── macos.sh │ │ ├── set-package.sh │ │ └── windows.sh │ └── jextract.sh │ ├── jrtserver │ ├── mkdocs │ ├── mkdocs.sh │ └── mkdocs.yml │ ├── openjdk-ea.sh │ ├── otel │ ├── OTel-Config.md │ ├── grafana │ │ ├── datasource.yaml │ │ └── grafana.ini │ ├── otel-collector-config.yml │ ├── otel-compose.yml │ └── systemd │ │ ├── README.md │ │ ├── docker-cleanup.service │ │ ├── docker-cleanup.timer │ │ └── docker-compose@.service │ ├── quine.jshell │ ├── rsocket.http │ └── script.main.kts ├── settings.gradle.kts ├── shared ├── README.md ├── api │ ├── shared.api │ └── shared.klib.api ├── build.gradle.kts ├── data.ipynb └── src │ ├── commonMain │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Greeting.kt │ │ │ ├── Platform.kt │ │ │ ├── flow │ │ │ └── Timer.kt │ │ │ ├── http │ │ │ ├── Config.kt │ │ │ ├── CurlLogging.kt │ │ │ ├── HttpClient.kt │ │ │ └── MediaApiClient.kt │ │ │ ├── lang │ │ │ ├── Features.kt │ │ │ ├── HackersDelight.kt │ │ │ └── Person.kt │ │ │ ├── lc │ │ │ └── Tree.kt │ │ │ └── serde │ │ │ ├── AnySerializer.kt │ │ │ └── ByteArrayAsBase64Serializer.kt │ └── resources │ │ └── common-main-res.txt │ ├── commonTest │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── CommonTest.kt │ ├── jsMain │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Extensions.kt │ │ │ ├── Platform.kt │ │ │ ├── http │ │ │ └── HttpClient.js.kt │ │ │ └── tz │ │ │ └── JsJodaTZ.kt │ └── resources │ │ └── common-js-res.txt │ ├── jsTest │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── PlatformTest.kt │ ├── jvmMain │ ├── java │ │ └── dev │ │ │ └── suresh │ │ │ ├── DOP.java │ │ │ ├── Expr.java │ │ │ ├── Gatherers.java │ │ │ ├── Lang.java │ │ │ └── Result.java │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Extns.kt │ │ │ ├── FFM.kt │ │ │ ├── Platform.kt │ │ │ ├── ReentrantLazy.kt │ │ │ ├── cert │ │ │ ├── CertExtn.kt │ │ │ ├── PemFormat.kt │ │ │ └── RootCA.kt │ │ │ ├── http │ │ │ └── HttpClient.jvm.kt │ │ │ └── qos │ │ │ └── FFMQosSetter.kt │ └── resources │ │ ├── ca │ │ └── cacert.pem │ │ └── common-jvm-res.txt │ ├── jvmTest │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── PlatformTest.kt │ ├── nativeMain │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Platform.kt │ │ │ └── http │ │ │ └── HttpClient.native.kt │ └── resources │ │ └── common-native-res.txt │ ├── nativeTest │ └── kotlin │ │ └── dev.suresh │ │ └── PlatformTest.kt │ ├── wasmJsMain │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Extensions.kt │ │ │ ├── FilePicker.kt │ │ │ ├── Platform.kt │ │ │ └── http │ │ │ └── HttpClient.wasmJs.kt │ └── resources │ │ └── common-wasm-res.txt │ └── wasmJsTest │ └── kotlin │ └── dev │ └── suresh │ └── PlatformTest.kt └── web ├── build.gradle.kts └── src ├── jsMain ├── kotlin │ ├── App.kt │ ├── hljs │ │ └── Highlight.kt │ ├── interop │ │ └── JsInterop.kt │ ├── play │ │ └── KotlinPlayground.kt │ └── xterm │ │ ├── Xterm.kt │ │ └── lib.es5.kt └── resources │ ├── css │ ├── app.css │ ├── bulma.min.css │ ├── hljs │ │ ├── github-dark.min.css │ │ └── github.min.css │ └── xterm.css │ ├── favicon.ico │ ├── index.html │ └── logo │ └── kotlin-logo.svg ├── jsTest └── kotlin │ └── AppTest.kt └── wasmJsMain ├── kotlin └── App.kt └── resources ├── css └── app.css ├── favicon.ico ├── index.html └── logo ├── kodee-excited.png ├── kodee-loving.png ├── kodee-regular.svg ├── kodee-sharing.svg ├── kodee-walking.gif ├── kotlin-full-logo.svg └── kotlin-logo.svg /.github/config/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## 🚀 Features", 5 | "labels": [ 6 | "feat" 7 | ] 8 | }, 9 | { 10 | "title": "## 🐛 Fixes", 11 | "labels": [ 12 | "fix" 13 | ] 14 | }, 15 | { 16 | "title": "## 🧰 Maintenance", 17 | "labels": [ 18 | "chore" 19 | ] 20 | }, 21 | { 22 | "title": "## 🧪 Tests", 23 | "labels": [ 24 | "test" 25 | ] 26 | }, 27 | { 28 | "title": "## 🖍️ Documentation", 29 | "labels": [ 30 | "doc", 31 | "docs" 32 | ] 33 | }, 34 | { 35 | "title": "## 📦 Dependencies", 36 | "labels": [ 37 | "deps", 38 | "dependencies", 39 | "chore(deps)", 40 | "build(deps)" 41 | ] 42 | }, 43 | { 44 | "title": "## 💬 Other", 45 | "labels": [ 46 | "other", 47 | "misc" 48 | ] 49 | } 50 | ], 51 | "sort": "ASC", 52 | "template": "${{CHANGELOG}}\n\n\nUncategorized\n\n${{UNCATEGORIZED}}\n", 53 | "pr_template": "${{TITLE}}", 54 | "empty_template": "- no changes", 55 | "label_extractor": [ 56 | { 57 | "pattern": "(.+): (.+)", 58 | "target": "$1" 59 | } 60 | ], 61 | "exclude_merge_branches": [ 62 | "merge pull request", 63 | "Merge pull request" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /.github/config/labels.yml: -------------------------------------------------------------------------------- 1 | - name: bug 2 | description: Something isn't working 3 | color: 'd73a4a' 4 | - name: doc 5 | description: Improvements to documentation 6 | color: 'd4c5f9' 7 | - name: duplicate 8 | description: This issue or pull request already exists 9 | color: 'cfd3d7' 10 | - name: feature 11 | color: '1d76db' 12 | description: New features 13 | - name: enhancement 14 | description: Enhancement of existing functionality 15 | color: '84b6eb' 16 | - name: deprecated 17 | description: Deprecating API 18 | color: 'f4c21d' 19 | - name: removed 20 | description: Removing API 21 | color: 'e4b21d' 22 | - name: tests 23 | description: Enhancement of tests 24 | color: '0e8a16' 25 | - name: java 26 | description: Java/JDK changes 27 | color: '03d0d6' 28 | - name: gradle 29 | description: Gradle changes 30 | color: 'd0d603' 31 | - name: maven 32 | description: Maven changes 33 | color: 'd60366' 34 | - name: compose 35 | description: Jetbrains Compose issues 36 | color: '3cdc84' 37 | - name: help 38 | description: Help Wanted 39 | color: '0e8a16' 40 | - name: question 41 | description: Questions and discussions 42 | color: 'cc317c' 43 | - name: dependencies 44 | description: Changes that affect dependencies 45 | color: '5319e7' 46 | - name: docker 47 | description: Container changes 48 | color: 'e99695' 49 | - name: github-actions 50 | description: Github action workflow changes 51 | color: 'ff7619' 52 | - name: graalvm 53 | description: GraalVM changes 54 | color: '387E65' 55 | - name: native-image 56 | description: GraalVM native-image changes 57 | color: 'A720DE' 58 | - name: kotlin 59 | description: Kotlin changes 60 | color: '5319E7' 61 | - name: kotlin-kmp 62 | description: Kotlin Multiplatform changes 63 | color: '7f52ff' 64 | - name: kotlin-multiplatform 65 | description: Kotlin Multiplatform changes 66 | color: '7f52ff' 67 | - name: build-logic 68 | description: Build Logic changes 69 | color: '4CAF50' 70 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 5 8 | labels: 9 | - "gradle" 10 | - "dependencies" 11 | reviewers: 12 | - sureshg 13 | 14 | - package-ecosystem: "gradle" 15 | directory: "/gradle/build-logic" 16 | schedule: 17 | interval: "daily" 18 | open-pull-requests-limit: 5 19 | labels: 20 | - "gradle" 21 | - "dependencies" 22 | reviewers: 23 | - sureshg 24 | 25 | - package-ecosystem: "github-actions" 26 | directory: "/" 27 | schedule: 28 | interval: "daily" 29 | open-pull-requests-limit: 5 30 | labels: 31 | - "github-actions" 32 | - "dependencies" 33 | reviewers: 34 | - sureshg 35 | 36 | - package-ecosystem: "docker" 37 | directory: "/" 38 | schedule: 39 | interval: "daily" 40 | labels: 41 | - "docker" 42 | - "dependencies" 43 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | - ci 6 | - skip-ci 7 | authors: 8 | - octocat 9 | categories: 10 | - title: New Features 🎉 11 | labels: 12 | - feat 13 | - enhancement 14 | - title: Breaking Changes 🛠 15 | labels: 16 | - Semver-Major 17 | - breaking-change 18 | - title: Fixes 🐛 19 | labels: 20 | - fix 21 | - bug 22 | - title: Maintenance 🧰 23 | labels: 24 | - chore 25 | - maintenance 26 | - title: Tests 🧪 27 | labels: 28 | - test 29 | - title: Documentation 🖍️ 30 | labels: 31 | - doc 32 | - title: Dependencies 📦️ 33 | labels: 34 | - deps 35 | - dependencies 36 | - chore(deps) 37 | - build(deps) 38 | - title: Other Changes 🖇️ 39 | labels: 40 | - "*" 41 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-prs.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot Auto Approve 2 | 3 | on: pull_request_target 4 | 5 | permissions: write-all 6 | 7 | jobs: 8 | dependabot: 9 | runs-on: ubuntu-latest 10 | if: github.actor == 'dependabot[bot]' 11 | steps: 12 | - name: 🔧Dependabot metadata 13 | id: dependabot-metadata 14 | uses: dependabot/fetch-metadata@v2.4.0 15 | with: 16 | github-token: "${{ secrets.GITHUB_TOKEN }}" 17 | 18 | - name: Approve a PR 19 | run: gh pr review --approve "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | 24 | - name: Enable auto-merge for Dependabot PRs 25 | if: steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' 26 | run: gh pr merge --auto --merge "$PR_URL" 27 | env: 28 | PR_URL: ${{github.event.pull_request.html_url}} 29 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 30 | 31 | - name: Add a label for dependencies 32 | if: steps.dependabot-metadata.outputs.dependency-type == 'direct:production' 33 | run: gh pr edit "$PR_URL" --add-label "dependencies" 34 | env: 35 | PR_URL: ${{github.event.pull_request.html_url}} 36 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | name: Sync Labels 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - .github/config/labels.yml 9 | workflow_dispatch: 10 | 11 | permissions: 12 | issues: write 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | sparse-checkout: .github/config/labels.yml 21 | 22 | - uses: EndBug/label-sync@v2 23 | with: 24 | config-file: | 25 | .github/config/labels.yml 26 | delete-other-labels: false 27 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OSX 2 | .DS_Store 3 | 4 | # IntelliJ IDEA 5 | .idea/ 6 | *.iml 7 | out/ 8 | 9 | # Gradle 10 | .gradle 11 | build 12 | reports 13 | !docs/reports 14 | !gradle-wrapper.jar 15 | 16 | # Java 17 | hs_err_pid* 18 | classes.lst 19 | *.jsa 20 | *.aot* 21 | jre/ 22 | 23 | # Kotlin 24 | **/.kotlin 25 | 26 | # Kobweb ignores 27 | **/.kobweb/* 28 | !**/.kobweb/conf.yaml 29 | 30 | # Gradle release 31 | cx.json 32 | -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | java=openjdk-ea 4 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | ### Run the backend JVM app 2 | 3 | ```bash 4 | # JVM 5 | $ ./gradlew :backend:jvm:run 6 | ``` 7 | 8 | ### OpenTelemetry 9 | 10 | * [Prometheus Java client Sample](https://prometheus.github.io/client_java/otel/otlp/) 11 | 12 | ### Misc 13 | 14 | * [JFR Speedscope](https://github.com/parttimenerd/jfrtofp/blob/main/src/main/kotlin/me/bechberger/jfrtofp/other) 15 | * [JFR Spring Starter](https://github.com/mirkosertic/flight-recorder-starter) 16 | * [Kotlin Native Samples](https://github.com/JetBrains/kotlin/tree/master/kotlin-native/backend.native/tests/samples) 17 | -------------------------------------------------------------------------------- /backend/agent/jfr/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | import kotlin.collections.plus 3 | 4 | plugins { 5 | dev.suresh.plugin.kotlin.jvm 6 | application 7 | com.gradleup.shadow 8 | dev.suresh.plugin.publishing 9 | } 10 | 11 | description = "JVM JFR Agent!" 12 | 13 | application { 14 | mainClass = libs.versions.app.mainclass.get() 15 | applicationDefaultJvmArgs += "" 16 | } 17 | 18 | tasks { 19 | jar { 20 | manifest { 21 | attributes( 22 | "Premain-Class" to application.mainClass, 23 | "Agent-Class" to application.mainClass, 24 | "Launcher-Agent-Class" to application.mainClass, 25 | "Can-Redefine-Classes" to "true", 26 | "Can-Retransform-Classes" to "true", 27 | "Can-Set-Native-Method-Prefix" to "true", 28 | "Implementation-Title" to project.name, 29 | "Implementation-Version" to version, 30 | ) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/agent/jfr/src/main/kotlin/dev/suresh/AgentType.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import jdk.jfr.* 4 | 5 | @Name("dev.suresh.agent.AgentType") 6 | @Label("JVM Agent Type") 7 | @Description("JVM agent attach type") 8 | @Category("JVM Agent", "Agent") 9 | @Period("5 s") 10 | @StackTrace(false) 11 | class AgentType(@Label("Agent Type") val type: String = "static") : Event() 12 | -------------------------------------------------------------------------------- /backend/agent/jfr/src/main/kotlin/dev/suresh/system/SystemExitInvoker.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.system 2 | 3 | object SystemExitInvoker { 4 | operator fun invoke() = System.exit(0) 5 | } 6 | -------------------------------------------------------------------------------- /backend/agent/otel/src/main/java/io/opentelemetry/extensions/HttpResponseCustomizer.java: -------------------------------------------------------------------------------- 1 | package io.opentelemetry.extensions; 2 | 3 | import com.google.auto.service.AutoService; 4 | import io.opentelemetry.api.trace.Span; 5 | import io.opentelemetry.context.Context; 6 | import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizer; 7 | import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator; 8 | 9 | /** 10 | * Customizes the HTTP response by adding the trace ID as a header. 11 | */ 12 | @AutoService(HttpServerResponseCustomizer.class) 13 | public class HttpResponseCustomizer implements HttpServerResponseCustomizer { 14 | 15 | private static final String TRACE_ID_HEADER = "X-Trace-Id"; 16 | 17 | 18 | @Override 19 | public void customize(Context serverContext, T response, HttpServerResponseMutator responseMutator) { 20 | var span = Span.fromContextOrNull(serverContext); 21 | if (span != null) { 22 | var spanContext = span.getSpanContext(); 23 | if (spanContext.isValid() && spanContext.isSampled()) { 24 | responseMutator.appendHeader(response, TRACE_ID_HEADER, spanContext.getTraceId()); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/boot/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.jvm 5 | alias(libs.plugins.spring.boot) 6 | alias(libs.plugins.spring.depmgmt) 7 | `kotlin-spring` 8 | // org.graalvm.buildtools.native 9 | // dev.suresh.plugin.publishing 10 | } 11 | 12 | description = "Kotlin SpringBoot app" 13 | 14 | springBoot { buildInfo {} } 15 | 16 | dependencies { 17 | implementation(projects.shared) 18 | implementation("org.springframework.boot:spring-boot-starter-web") 19 | implementation("org.jetbrains.kotlin:kotlin-reflect") 20 | runtimeOnly("org.postgresql:postgresql") 21 | // implementation("org.springframework.boot:spring-boot-starter-jdbc") 22 | // implementation("org.springframework.boot:spring-boot-starter-security") 23 | // implementation("com.fasterxml.jackson.module:jackson-module-kotlin") 24 | // developmentOnly("org.springframework.boot:spring-boot-docker-compose") 25 | testImplementation("org.springframework.boot:spring-boot-starter-test") 26 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") 27 | testImplementation("org.springframework.security:spring-security-test") 28 | testImplementation("org.springframework.boot:spring-boot-testcontainers") 29 | testImplementation("org.testcontainers:junit-jupiter") 30 | testImplementation("org.testcontainers:postgresql") 31 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 32 | } 33 | 34 | // Fix for https://github.com/Kotlin/dokka/issues/3472 35 | // configurations 36 | // .matching { it.name.startsWith("dokka") } 37 | // .configureEach { 38 | // resolutionStrategy.eachDependency { 39 | // if (requested.group.startsWith("com.fasterxml.jackson")) { 40 | // useVersion("2.15.3") 41 | // } 42 | // } 43 | // } 44 | 45 | tasks { bootRun { jvmArgs = project.runJvmArgs } } 46 | -------------------------------------------------------------------------------- /backend/boot/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: 'postgres:latest' 4 | environment: 5 | - 'POSTGRES_DB=mydb' 6 | - 'POSTGRES_USER=user' 7 | - 'POSTGRES_PASSWORD=secret' 8 | ports: 9 | - '5432:5432' 10 | -------------------------------------------------------------------------------- /backend/boot/src/main/kotlin/dev/suresh/App.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication class App 7 | 8 | fun main(args: Array) { 9 | System.setProperty("spring.classformat.ignore", "true") 10 | runApplication(*args) 11 | } 12 | -------------------------------------------------------------------------------- /backend/boot/src/main/kotlin/dev/suresh/aot/AotConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.aot 2 | 3 | import org.springframework.aot.hint.RuntimeHints 4 | import org.springframework.aot.hint.RuntimeHintsRegistrar 5 | import org.springframework.context.annotation.Configuration 6 | import org.springframework.context.annotation.ImportRuntimeHints 7 | 8 | @Configuration @ImportRuntimeHints(Hints::class) class AotConfig 9 | 10 | class Hints : RuntimeHintsRegistrar { 11 | override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) { 12 | hints.reflection().apply { 13 | // registerType(Klass::class.java, *MemberCategory.entries.toTypedArray()) 14 | // registerType(Klass::class.java, MemberCategory.INVOKE_DECLARED_METHODS) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/boot/src/main/kotlin/dev/suresh/books/Books.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.books 2 | 3 | import kotlinx.datetime.LocalDateTime 4 | import kotlinx.serialization.Serializable 5 | import org.springframework.stereotype.Controller 6 | import org.springframework.web.bind.annotation.GetMapping 7 | import org.springframework.web.bind.annotation.RequestMapping 8 | import org.springframework.web.bind.annotation.ResponseBody 9 | 10 | @Serializable data class Author(val id: Long, val name: String) 11 | 12 | @Serializable 13 | data class Book( 14 | val id: Long, 15 | val title: String, 16 | val description: String, 17 | val created: LocalDateTime, 18 | val author: Author 19 | ) 20 | 21 | @Controller 22 | @ResponseBody 23 | @RequestMapping("/books") 24 | class BooksController { 25 | 26 | @GetMapping fun books() = emptyList() 27 | } 28 | -------------------------------------------------------------------------------- /backend/boot/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | logging: 5 | level: 6 | root: INFO 7 | org.springframework.web: INFO 8 | org.springframework.security: INFO 9 | 10 | spring: 11 | application: 12 | name: boot 13 | 14 | threads: 15 | virtual: 16 | enabled: true 17 | 18 | sql: 19 | init: 20 | mode: always 21 | 22 | datasource: 23 | url: jdbc:postgresql://localhost:5432/mydb 24 | username: user 25 | password: secret 26 | 27 | 28 | -------------------------------------------------------------------------------- /backend/boot/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table if not exists author 2 | ( 3 | id bigserial primary key, 4 | name varchar not null 5 | ); 6 | 7 | create table if not exists book 8 | ( 9 | id serial primary key, 10 | title varchar not null, 11 | description varchar(256) not null default '', 12 | created timestamp with time zone default current_timestamp, 13 | author bigint not null references author (id) 14 | ); -------------------------------------------------------------------------------- /backend/boot/src/test/kotlin/dev/suresh/AppTest.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlin.test.Test 4 | import org.springframework.boot.test.context.SpringBootTest 5 | 6 | @SpringBootTest 7 | class AppTest { 8 | 9 | @Test fun contextLoads() {} 10 | } 11 | -------------------------------------------------------------------------------- /backend/data/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.jvmTarget 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.mpp 5 | dev.suresh.plugin.publishing 6 | } 7 | 8 | description = "Kotlin Data Science!" 9 | 10 | kotlin { 11 | jvmTarget(project) 12 | 13 | sourceSets { 14 | commonMain { dependencies { implementation(projects.shared) } } 15 | 16 | jvmMain { 17 | dependencies { 18 | implementation(libs.pty4j) 19 | // implementation(libs.graal.polyglot) 20 | // implementation(libs.graal.wasm) 21 | // implementation(fileTree("lib") { include("*.jar") }) 22 | } 23 | 24 | kotlin.srcDir("src/main/kotlin") 25 | resources.srcDir("src/main/resources") 26 | } 27 | 28 | jvmTest { 29 | kotlin.srcDir("src/test/kotlin") 30 | resources.srcDir("src/test/resources") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/data/src/main/kotlin/dev/suresh/DataApp.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import com.pty4j.PtyProcessBuilder 4 | import java.util.concurrent.CountDownLatch 5 | 6 | fun main() { 7 | println("Hello Kotlin Data! ${Greeting().greeting()}") 8 | ssh() 9 | } 10 | 11 | fun ssh() { 12 | val ptyProcess = 13 | PtyProcessBuilder().run { 14 | setCommand(arrayOf("/bin/sh", "-l")) 15 | setEnvironment(mapOf("TERM" to "xterm-256color")) 16 | setDirectory(System.getProperty("user.home")) 17 | start() 18 | } 19 | 20 | println("PTY child process started ${ptyProcess.pid()}, size: ${ptyProcess.winSize}") 21 | println(ptyProcess.isConsoleMode) 22 | println(ptyProcess.isAlive) 23 | val tos = ptyProcess.outputStream 24 | val tis = ptyProcess.inputReader() 25 | 26 | val size = 10 27 | val latch = CountDownLatch(size) 28 | 29 | Thread.ofVirtual().start { 30 | runCatching { 31 | for (i in 1..size) { 32 | tos.write("echo $i".encodeToByteArray()) 33 | tos.write(byteArrayOf(ptyProcess.enterKeyCode)) 34 | tos.flush() 35 | Thread.sleep(100) 36 | latch.countDown() 37 | } 38 | } 39 | } 40 | 41 | Thread.ofVirtual().start { runCatching { tis.forEachLine { println(it) } } } 42 | 43 | latch.await() 44 | tos.close() 45 | tis.close() 46 | 47 | // wait until the PTY child process is terminated 48 | val result = ptyProcess.waitFor() 49 | println("PTY child process terminated with exit code $result") 50 | ptyProcess.destroyForcibly() 51 | } 52 | 53 | // fun wasm() { 54 | // val source = 55 | // Source.newBuilder( 56 | // "wasm", 57 | // """ 58 | // (module 59 | // (func (export "add") (param i32 i32) (result i32) 60 | // local.get 0 61 | // local.get 1 62 | // i32.add) 63 | // ) 64 | // """ 65 | // .trimIndent(), 66 | // "test") 67 | // .build() 68 | // Context.newBuilder("wasm").build().use { ctx -> 69 | // val wasm = ctx.eval(source) 70 | // val add = wasm.getMember("add") 71 | // val result = add.execute(10, 20) 72 | // println(result) 73 | // } 74 | // } 75 | -------------------------------------------------------------------------------- /backend/data/src/main/kotlin/dev/suresh/gen/GenArt.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.gen 2 | 3 | import java.awt.Color 4 | import java.awt.geom.Ellipse2D 5 | import java.awt.image.BufferedImage 6 | import java.awt.image.BufferedImage.TYPE_INT_ARGB 7 | import javax.imageio.ImageIO 8 | import kotlin.math.PI 9 | 10 | object GenArt { 11 | 12 | const val S = 1024 13 | 14 | fun flower() { 15 | BufferedImage(S, S, TYPE_INT_ARGB).apply { 16 | createGraphics().apply { 17 | color = Color(0, 0, 0, 25) 18 | for (angle in 0..<360 step 15) { 19 | val old = transform 20 | val centerX = S / 2.0 21 | val centerY = S / 2.0 22 | val eWidth = S / 8.0 23 | val eHeight = S * 7.0 / 16 24 | translate(centerX, centerY) 25 | rotate(angle.toRadians()) 26 | fill(Ellipse2D.Double(eHeight - centerX, eWidth - centerY, eWidth, eHeight)) 27 | transform = old 28 | } 29 | dispose() 30 | } 31 | ImageIO.write(this, "png", java.io.File("flower.png")) 32 | } 33 | } 34 | } 35 | 36 | fun Double.toRadians() = this / 180 * PI 37 | 38 | fun Int.toRadians() = toDouble().toRadians() 39 | 40 | fun Double.toDegrees() = this * 180 / PI 41 | -------------------------------------------------------------------------------- /backend/jvm/src/main/java/module-info.java.txt: -------------------------------------------------------------------------------- 1 | module dev.suresh.kmp { 2 | requires kotlin.stdlib; 3 | requires kotlinx.serialization.core; 4 | } 5 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/App.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import BuildConfig 4 | import dev.suresh.config.AppConfig 5 | import dev.suresh.plugins.* 6 | import dev.suresh.routes.* 7 | import io.ktor.server.application.* 8 | import io.ktor.server.netty.* 9 | import io.ktor.server.routing.* 10 | import io.ktor.util.logging.* 11 | import kotlin.io.path.Path 12 | import kotlin.io.path.exists 13 | 14 | fun main(args: Array) = 15 | try { 16 | initProps() 17 | EngineMain.main(args) 18 | } catch (e: Throwable) { 19 | val log = KtorSimpleLogger("main") 20 | log.error("Failed to start ${BuildConfig.description}: ${e.message}", e) 21 | } 22 | 23 | fun Application.module() { 24 | log.info("Starting ${BuildConfig.description} v${BuildConfig.version}...") 25 | AppConfig.init(environment.config) 26 | configureOTel() 27 | configureInterceptors() 28 | configureHTTP() 29 | configureSecurity() 30 | errorRoutes() 31 | scheduledTasks() 32 | 33 | routing { 34 | adminRoutes() 35 | webApp() 36 | services() 37 | mgmtRoutes() 38 | } 39 | // CoroutineScope(coroutineContext).launch {} 40 | } 41 | 42 | /** 43 | * Initializes the system properties required for the application to run. This should be invoked 44 | * before the Engine main() method is called. 45 | */ 46 | fun initProps() { 47 | val logDir = 48 | System.getProperty("LOG_DIR", System.getenv("LOG_DIR")).orEmpty().ifBlank { 49 | when { 50 | Path("/log").exists() -> "/log" 51 | else -> System.getProperty("user.dir") 52 | } 53 | } 54 | 55 | System.setProperty("jdk.tls.maxCertificateChainLength", "15") 56 | System.setProperty("jdk.includeInExceptions", "hostInfo") 57 | System.setProperty("slf4j.internal.verbosity", "WARN") 58 | System.setProperty("LOG_DIR", logDir) 59 | 60 | println("⚡ ${BuildConfig.description} v${BuildConfig.version} ⚡") 61 | println("Log Dir: $logDir") 62 | 63 | // Redirect JUL to SLF4J 64 | // SLF4JBridgeHandler.removeHandlersForRootLogger() 65 | // SLF4JBridgeHandler.install() 66 | } 67 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/config/AppConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.config 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import io.github.oshai.kotlinlogging.KotlinLogging 5 | import io.ktor.server.config.ApplicationConfig 6 | import kotlin.reflect.full.withNullability 7 | import kotlin.reflect.typeOf 8 | import kotlin.time.Duration 9 | import kotlinx.serialization.hocon.* 10 | 11 | /** 12 | * Initializes the config data classes from the application config. Since HOCON is used for 13 | * application configuration, `kotlinx.serialization.hocon` is used to deserialize the config. The 14 | * [AppConfig.init] method should be called before accessing the config values. 15 | */ 16 | data object AppConfig { 17 | 18 | val log = KotlinLogging.logger {} 19 | 20 | private lateinit var appConfig: ApplicationConfig 21 | 22 | private val hocon = Hocon { 23 | serializersModule = Hocon.serializersModule 24 | encodeDefaults = true 25 | } 26 | 27 | /** Initializes application config */ 28 | fun init(config: ApplicationConfig) { 29 | log.info { "Initializing App configurations..." } 30 | appConfig = config 31 | } 32 | 33 | /** Application authn/authz configuration. */ 34 | val auth by lazy { 35 | val config = ConfigFactory.parseMap(appConfig.config("app.auth").toMap()) 36 | hocon.decodeFromConfig>(config) 37 | } 38 | } 39 | 40 | /** 41 | * Extension function to get and convert config values to their respective type. Nullability is 42 | * disabled to support java types 43 | */ 44 | @Suppress("IMPLICIT_CAST_TO_ANY") 45 | inline fun ApplicationConfig.prop(prop: String) = 46 | when (typeOf().withNullability(false)) { 47 | typeOf() -> property(prop).getString() 48 | typeOf>() -> property(prop).getList() 49 | typeOf() -> property(prop).getString().toBoolean() 50 | typeOf() -> property(prop).getString().toInt() 51 | typeOf() -> property(prop).getString().toLong() 52 | typeOf() -> property(prop).getString().toDouble() 53 | typeOf() -> Duration.parse(property(prop).getString().lowercase()) 54 | else -> throw IllegalArgumentException("Unsupported type: ${typeOf()}") 55 | } 56 | as T 57 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/lang/TM.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.lang 2 | 3 | import java.lang.foreign.MemoryLayout 4 | import java.lang.foreign.MemorySegment 5 | import java.lang.foreign.ValueLayout 6 | 7 | /** 8 | * Time representing the number of seconds since the Unix Epoch (January 1, 1970, 00:00:00 UTC) into 9 | * a broken-down UTC representation 10 | */ 11 | class TM(private val segment: MemorySegment) { 12 | companion object { 13 | val LAYOUT = 14 | MemoryLayout.structLayout( 15 | ValueLayout.JAVA_INT.withName("sec"), 16 | ValueLayout.JAVA_INT.withName("min"), 17 | ValueLayout.JAVA_INT.withName("hour"), 18 | ValueLayout.JAVA_INT.withName("mday"), 19 | ValueLayout.JAVA_INT.withName("mon"), 20 | ValueLayout.JAVA_INT.withName("year"), 21 | ValueLayout.JAVA_INT.withName("wday"), 22 | ValueLayout.JAVA_INT.withName("yday"), 23 | ValueLayout.JAVA_BOOLEAN.withName("isdst"), 24 | MemoryLayout.paddingLayout(24)) 25 | 26 | private val yearVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("year")) 27 | private val monthVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("mon")) 28 | private val dayVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("mday")) 29 | private val hourVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("hour")) 30 | private val minVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("min")) 31 | private val secVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sec")) 32 | } 33 | 34 | val year: Int 35 | get() = yearVH.get(segment, 0L) as Int + 1900 36 | 37 | val month: Int 38 | get() = monthVH.get(segment, 0L) as Int + 1 39 | 40 | val day: Int 41 | get() = dayVH.get(segment, 0L) as Int 42 | 43 | val hour: Int 44 | get() = hourVH.get(segment, 0L) as Int 45 | 46 | val min: Int 47 | get() = minVH.get(segment, 0L) as Int 48 | 49 | val sec: Int 50 | get() = secVH.get(segment, 0L) as Int 51 | 52 | override fun toString(): String { 53 | return "TM(year=$year, month=$month, day=$day, hour=$hour, min=$min, sec=$sec)" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/log/RespLogger.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.log 2 | 3 | import dev.suresh.log 4 | import io.github.oshai.kotlinlogging.KLogger 5 | import java.io.Writer 6 | 7 | class RespLogger(private val out: Writer, private val logger: KLogger = log) : KLogger by logger { 8 | override fun info(message: () -> Any?) { 9 | super.info(message) 10 | out.appendLine(message().toString()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/plugins/Error.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.plugins 2 | 3 | import dev.suresh.http.* 4 | import io.ktor.http.HttpStatusCode 5 | import io.ktor.server.application.* 6 | import io.ktor.server.plugins.* 7 | import io.ktor.server.plugins.statuspages.* 8 | import io.ktor.server.request.* 9 | import io.ktor.server.response.* 10 | 11 | fun Application.errorRoutes() { 12 | 13 | install(StatusPages) { 14 | status(HttpStatusCode.Unauthorized) { call, status -> 15 | when { 16 | call.isApi -> 17 | call.respondError( 18 | HttpStatusCode.Unauthorized, "Authorization is required to access this resource") 19 | } 20 | } 21 | 22 | status(HttpStatusCode.NotFound) { call, _ -> 23 | call.respondRedirect("/app", permanent = true) // 301 24 | } 25 | 26 | exception { call, cause -> 27 | val status = 28 | when (cause) { 29 | is BadRequestException -> HttpStatusCode.BadRequest 30 | else -> HttpStatusCode.InternalServerError 31 | } 32 | 33 | call.application.log.error(status.description, cause) 34 | call.respondError(status, cause.message ?: "Unknown error", cause) 35 | } 36 | 37 | unhandled { 38 | it.respondError( 39 | HttpStatusCode.NotFound, "The requested URL ${it.request.path()} was not found") 40 | } 41 | } 42 | } 43 | 44 | fun userError(message: Any): Nothing = throw BadRequestException(message.toString()) 45 | 46 | suspend fun ApplicationCall.respondError( 47 | status: HttpStatusCode, 48 | message: String, 49 | cause: Throwable? = null 50 | ) = 51 | respond( 52 | status = status, 53 | message = 54 | ErrorStatus( 55 | code = status.value, 56 | message = message, 57 | details = if (debug) cause?.stackTraceToString() else cause?.message)) 58 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/plugins/Security.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.plugins 2 | 3 | import io.ktor.server.application.* 4 | import io.ktor.server.auth.* 5 | 6 | fun Application.configureSecurity() { 7 | authentication { 8 | bearer("auth-bearer") { 9 | realm = "Ktor App" 10 | authenticate { tokenCredential -> 11 | when (tokenCredential.token) { 12 | "token" -> UserIdPrincipal("admin") 13 | else -> null 14 | } 15 | } 16 | } 17 | 18 | basic("admin") { 19 | realm = "App Admin" 20 | validate { credentials -> 21 | when (credentials.name == "admin" && credentials.password == "admin") { 22 | true -> UserIdPrincipal(credentials.name) 23 | else -> null 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/plugins/custom/CookieSession.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.plugins.custom 2 | 3 | import io.ktor.server.sessions.SessionSerializer 4 | 5 | /** Custom Cookie Session serializer sample. */ 6 | data class CookieSession(val text: String) 7 | 8 | object CookieSessionSerializer : SessionSerializer { 9 | override fun deserialize(text: String): CookieSession = CookieSession(text = text) 10 | 11 | override fun serialize(session: CookieSession): String = session.text 12 | } 13 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/plugins/custom/Plugins.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.plugins.custom 2 | 3 | import dev.suresh.plugins.isApi 4 | import io.ktor.server.application.* 5 | import io.ktor.util.* 6 | import io.opentelemetry.api.trace.Span 7 | 8 | /** 9 | * A custom ktor plugin to automatically add OpenTelemetry trace id to response headers for API 10 | * endpoints. 11 | */ 12 | val OTelExtnPlugin = 13 | createApplicationPlugin(name = "OTelExtnPlugin", createConfiguration = ::OTelExtnPluginConfig) { 14 | val onCallTimeKey = AttributeKey("onCallTimeKey") 15 | onCall { call -> 16 | val onCallTime = System.currentTimeMillis() 17 | call.attributes.put(onCallTimeKey, onCallTime) 18 | 19 | if (pluginConfig.enabled && call.isApi) { 20 | Span.current()?.let { span -> 21 | call.response.headers.append(pluginConfig.traceIdHeader, span.spanContext.traceId) 22 | // span.setAttribute("custom-attribute", "TestPlugin") 23 | // span.addEvent("TestPluginEvent") 24 | } 25 | } 26 | } 27 | 28 | // on(MonitoringEvent(ApplicationStarted)) { 29 | // it.log.info("Application started - ${it.isActive}") 30 | // } 31 | // 32 | // on(CallFailed) { call, error -> 33 | // if (pluginConfig.enabled) { 34 | // call.application.log.error("Failed call: $call", error) 35 | // } 36 | // } 37 | // 38 | // onCallReceive { call, body -> 39 | // val onCallTime = call.attributes[onCallTimeKey] 40 | // if (pluginConfig.enabled) { 41 | // call.application.log.info("Received: $body") 42 | // } 43 | // } 44 | // 45 | // onCallRespond { call, res -> 46 | // if (pluginConfig.enabled) { 47 | // call.application.log.info("Responded: $res") 48 | // } 49 | // } 50 | } 51 | 52 | class OTelExtnPluginConfig { 53 | var enabled: Boolean = false 54 | var traceIdHeader: String = "X-TraceId" 55 | } 56 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/routes/Scheduler.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.routes 2 | 3 | import dev.suresh.virtualThreadScope 4 | import io.github.kevincianfarini.cardiologist.* 5 | import io.ktor.server.application.* 6 | import io.ktor.util.logging.* 7 | import io.opentelemetry.instrumentation.annotations.* 8 | import kotlin.time.Duration.Companion.seconds 9 | import kotlinx.coroutines.* 10 | import kotlinx.datetime.* 11 | 12 | fun Application.scheduledTasks() { 13 | log.info("Starting scheduled tasks...") 14 | virtualThreadScope.launch { 15 | Clock.System.fixedPeriodPulse(10.seconds).beat(PulseBackpressureStrategy.SkipNext) { scheduled 16 | -> 17 | context(log) { task("Task at ${scheduled.toLocalDateTime(TimeZone.currentSystemDefault())}") } 18 | } 19 | } 20 | } 21 | 22 | @WithSpan("scheduled-task") 23 | context(log: Logger) 24 | fun task(name: String) { 25 | try { 26 | log.warn("Running $name") 27 | // log.info("Scheduled task: ${MediaApiClient().images().size}") 28 | } catch (e: Exception) { 29 | log.error("Failed to run scheduled task: ${e.message}", e) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/routes/Webapp.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.routes 2 | 3 | import io.ktor.server.http.content.* 4 | import io.ktor.server.request.* 5 | import io.ktor.server.response.* 6 | import io.ktor.server.routing.* 7 | 8 | fun Routing.webApp() { 9 | webApp("/app", "app") 10 | } 11 | 12 | fun Routing.webApp(remotePath: String, basePackage: String) { 13 | staticResources(remotePath, basePackage) { 14 | exclude { it.path.endsWith(".log") } 15 | default("index.html") 16 | modify { url, call -> 17 | when (call.request.path()) { 18 | remotePath -> call.respondRedirect("$remotePath/") 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/wasm/Wasm.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.wasm 2 | 3 | import com.dylibso.chicory.compiler.MachineFactoryCompiler 4 | import com.dylibso.chicory.runtime.HostFunction 5 | import com.dylibso.chicory.runtime.Instance 6 | import com.dylibso.chicory.wasm.Parser 7 | import com.dylibso.chicory.wasm.types.FunctionType 8 | import com.dylibso.chicory.wasm.types.ValType 9 | import io.ktor.server.response.* 10 | import io.ktor.server.routing.* 11 | 12 | /** 13 | * More wasm modules can be found in 14 | * - [wasm-corpus](https://github.com/dylibso/chicory/tree/main/wasm-corpus/src/main/resources/compiled) 15 | * - [extism](https://github.com/extism/plugins/releases) 16 | */ 17 | val factWasmInst: Instance by lazy { 18 | val wasmRes = Thread.currentThread().contextClassLoader.getResourceAsStream("wasm/factorial.wasm") 19 | // val wasmRes = object {}.javaClass.getResourceAsStream("wasm/factorial.wasm") 20 | val wasmMod = Parser.parse(wasmRes) 21 | Instance.builder(wasmMod).withMachineFactory(MachineFactoryCompiler::compile).build() 22 | } 23 | 24 | fun Routing.wasm() { 25 | route("/wasm") { 26 | get("fact") { 27 | val num = call.parameters["num"]?.toLongOrNull() ?: 5 28 | 29 | val iterFact = factWasmInst.export("iterFact") 30 | val fact = iterFact.apply(num)[0] 31 | 32 | call.respondText("WASM: Factorial($num): $fact") 33 | } 34 | } 35 | } 36 | 37 | fun logFunction() = 38 | HostFunction( 39 | "console", "log", FunctionType.of(listOf(ValType.I32, ValType.I32), emptyList())) { 40 | instance, 41 | args -> 42 | val msg = instance.memory().readString(args[0].toInt(), args[1].toInt()) 43 | println("WASM: $msg") 44 | // Value.i32(0) 45 | longArrayOf() 46 | } 47 | -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/META-INF/native-image/native-image.properties: -------------------------------------------------------------------------------- 1 | # Args=--initialize-at-build-time=com.github.ajalt.mordant.internal.nativeimage.NativeImagePosixMppImpls -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Kotlin Multiplatform web application 12 | 13 | 14 | 15 | Kotlin Multiplatform web application 16 | JS App 17 | Wasm App 18 | ComposeWeb App 19 | 20 | 38 | 39 | -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | host = 0.0.0.0 4 | port = 8080 5 | port = ${?PORT} 6 | // The url limit including query parameters 7 | maxInitialLineLength = 2048 8 | maxHeaderSize = 4096 9 | shutdownGracePeriod = 200 10 | shutdownTimeout = 200 11 | shutdown.url = "/shutdown" 12 | // tcpKeepAlive = true 13 | } 14 | 15 | development = false 16 | 17 | application { 18 | modules = [dev.suresh.AppKt.module] 19 | } 20 | } -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/backend-jvm-res.txt: -------------------------------------------------------------------------------- 1 | Resource from "Backend jvmMain" module ${version}! -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/openapi/documentation.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.1.0" 2 | info: 3 | title: "{project.name} api" 4 | description: "{project.name} api" 5 | version: "{project.version}" 6 | servers: 7 | - url: "https://kotlin_mpp_playground" 8 | paths: 9 | /: 10 | get: 11 | description: "" 12 | responses: 13 | "302": 14 | description: "Found Redirect" 15 | content: 16 | text/plain: 17 | schema: 18 | type: "string" 19 | examples: 20 | Example#1: 21 | value: "/swagger" 22 | /browse/{param}: 23 | get: 24 | description: "" 25 | parameters: 26 | - name: "param" 27 | in: "path" 28 | required: true 29 | schema: 30 | type: "array" 31 | items: 32 | type: "string" 33 | responses: 34 | "200": 35 | description: "OK A file response" 36 | content: 37 | application/*: 38 | schema: 39 | type: "object" 40 | format: "binary" 41 | "404": 42 | description: "Not Found" 43 | content: 44 | text/html: 45 | schema: 46 | type: "string" 47 | /ffm: 48 | get: 49 | description: "" 50 | /heapdump: 51 | get: 52 | description: "" 53 | responses: 54 | "200": 55 | description: "OK A file response" 56 | content: 57 | application/*: 58 | schema: 59 | type: "object" 60 | format: "binary" 61 | /info: 62 | get: 63 | description: "" 64 | responses: 65 | "200": 66 | description: "OK" 67 | content: 68 | '*/*': 69 | schema: 70 | type: "string" 71 | /jfr: 72 | get: 73 | description: "" 74 | /profile: 75 | get: 76 | description: "" 77 | responses: 78 | "200": 79 | description: "OK A file response" 80 | content: 81 | application/*: 82 | schema: 83 | type: "object" 84 | format: "binary" 85 | /vthreads: 86 | get: 87 | description: "" -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/wasm/factorial.wasm: -------------------------------------------------------------------------------- 1 | asm ` iterFact 2 | *(A!@ E @ l! Aj" E -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/wasm/hello-wasi.wasm: -------------------------------------------------------------------------------- 1 | asm `` #wasi_snapshot_preview1fd_write memory _start 2 | A A6 AA6 AA AA Ahello world 3 | -------------------------------------------------------------------------------- /backend/jvm/src/test/kotlin/dev/suresh/DnsTest.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig 4 | import com.github.tomakehurst.wiremock.junit5.WireMockExtension 5 | import com.marcinziolo.kotlin.wiremock.get 6 | import com.marcinziolo.kotlin.wiremock.like 7 | import com.marcinziolo.kotlin.wiremock.returns 8 | import dev.suresh.http.testHttpClient 9 | import io.ktor.client.plugins.defaultRequest 10 | import io.ktor.client.request.get 11 | import io.ktor.client.statement.bodyAsText 12 | import java.net.InetAddress 13 | import kotlin.test.Test 14 | import kotlin.test.assertEquals 15 | import kotlinx.coroutines.test.runTest 16 | import org.junit.jupiter.api.condition.EnabledIfSystemProperty 17 | import org.junit.jupiter.api.extension.RegisterExtension 18 | 19 | @EnabledIfSystemProperty(named = "jdk.net.hosts.file", matches = "true") 20 | class DnsTest { 21 | 22 | companion object { 23 | 24 | @JvmField 25 | @RegisterExtension 26 | val wireMock = 27 | WireMockExtension.newInstance() 28 | .options( 29 | wireMockConfig().httpDisabled(true).httpsPort(8888) 30 | // .keystorePath("src/test/resources/keystore.jks") 31 | // .keystorePassword("changeit") 32 | // .keyManagerPassword("changeit") 33 | // .keystoreType("PKCS12") 34 | // .trustStorePath("src/test/resources/truststore.jks") 35 | // .trustStorePassword("changeit") 36 | // .trustStoreType("PKCS12") 37 | // .needClientAuth(true) 38 | ) 39 | .build() 40 | } 41 | 42 | @Test 43 | fun hostFileTest() = runTest { 44 | val addr = InetAddress.getByName("test.dev") 45 | assertEquals("127.0.0.1", addr.hostAddress, "Address should be 127.0.0.1") 46 | } 47 | 48 | @Test 49 | fun wireMockTest() = runTest { 50 | wireMock.get { url like "/users/.*" } returns 51 | { 52 | header = "Content-Type" to "application/json" 53 | statusCode = 200 54 | body = 55 | """ 56 | { 57 | "id": 1, 58 | "name": "Suresh" 59 | } 60 | """ 61 | } 62 | 63 | val client = testHttpClient.config { defaultRequest { url(wireMock.baseUrl()) } } 64 | println(client.get("/users/1").bodyAsText()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /backend/jvm/src/test/kotlin/dev/suresh/K8STests.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import io.kubernetes.client.openapi.apis.CoreV1Api 4 | import io.kubernetes.client.util.Config 5 | import kotlin.test.Test 6 | import kotlin.test.assertTrue 7 | import org.junit.jupiter.api.condition.EnabledIfSystemProperty 8 | import org.slf4j.LoggerFactory 9 | import org.testcontainers.containers.output.Slf4jLogConsumer 10 | import org.testcontainers.junit.jupiter.Container 11 | import org.testcontainers.junit.jupiter.Testcontainers 12 | import org.testcontainers.k3s.K3sContainer 13 | import org.testcontainers.utility.DockerImageName 14 | 15 | @Testcontainers 16 | @EnabledIfSystemProperty(named = "k8sTest", matches = "true") 17 | class K8STests { 18 | 19 | companion object { 20 | 21 | val logger = LoggerFactory.getLogger("k8s") 22 | 23 | @Container 24 | val k3s = 25 | K3sContainer(DockerImageName.parse("rancher/k3s:latest")) 26 | .withLogConsumer(Slf4jLogConsumer(logger, false)) 27 | .withReuse(true) 28 | // .withCommand("server", "--disable=traefik", 29 | // "--tls-san=${DockerClientFactory.instance().dockerHostIpAddress()}") 30 | } 31 | 32 | @Test 33 | fun testK8S() { 34 | assertTrue(k3s.isRunning) 35 | } 36 | 37 | @Test 38 | fun testK8SClient() { 39 | val client = Config.fromConfig(k3s.kubeConfigYaml.reader()) 40 | val api = CoreV1Api(client) 41 | api.listNode().execute().items.forEach { println("K8S NodeName: ${it.metadata?.name}") } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /backend/jvm/src/test/kotlin/dev/suresh/TestLogger.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | import org.slf4j.MDC 6 | 7 | object TestLogger : Logger by LoggerFactory.getLogger("ktor.test") { 8 | 9 | private val _messages = mutableListOf() 10 | 11 | val messages: List 12 | get() = _messages 13 | 14 | override fun trace(message: String?) = add("TRACE: $message") 15 | 16 | override fun debug(message: String?) = add("DEBUG: $message") 17 | 18 | override fun debug(message: String?, cause: Throwable) = add("DEBUG: $message") 19 | 20 | override fun info(message: String?) = add("INFO: $message") 21 | 22 | private fun add(message: String?) { 23 | if (message != null) { 24 | val mdcText = 25 | MDC.getCopyOfContextMap()?.let { mdc -> 26 | if (mdc.isNotEmpty()) { 27 | mdc.entries 28 | .sortedBy { it.key } 29 | .joinToString(prefix = " [", postfix = "]") { "${it.key}=${it.value}" } 30 | } else { 31 | "" 32 | } 33 | } ?: "" 34 | _messages.add("$message$mdcText") 35 | } 36 | } 37 | 38 | fun clear() = _messages.clear() 39 | } 40 | -------------------------------------------------------------------------------- /backend/jvm/src/test/kotlin/dev/suresh/http/TestHttpClient.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.http 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.engine.java.Java 5 | import nl.altindag.ssl.SSLFactory 6 | 7 | val testSSLFactory by lazy { SSLFactory.builder().withUnsafeTrustMaterial().build() } 8 | 9 | val testHttpClient by lazy { 10 | HttpClient(Java) { engine { config { sslContext(testSSLFactory.sslContext) } } } 11 | } 12 | -------------------------------------------------------------------------------- /backend/jvm/src/test/resources/hosts_disabled: -------------------------------------------------------------------------------- 1 | 127.0.0.1 test.dev www.test.dev -------------------------------------------------------------------------------- /backend/native/src/appleMain/kotlin/Env.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.cinterop.toKString 2 | import platform.posix.getpass 3 | 4 | actual fun readPassword(prompt: String) = getpass(prompt)?.toKString() 5 | -------------------------------------------------------------------------------- /backend/native/src/linuxMain/kotlin/Env.kt: -------------------------------------------------------------------------------- 1 | import kotlin.time.Instant 2 | import kotlinx.cinterop.* 3 | import kotlinx.io.files.Path 4 | import platform.posix.* 5 | 6 | actual fun readPassword(prompt: String) = getpass(prompt)?.toKString() 7 | 8 | fun getMTime(path: Path): Instant { 9 | memScoped { 10 | val stat = alloc() 11 | if (lstat(path.toString(), stat.ptr) != 0) { 12 | throw IllegalStateException("Failed to get mtime for $path") 13 | } 14 | return Instant.fromEpochSeconds(stat.st_mtim.tv_sec, stat.st_mtim.tv_nsec) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/native/src/mingwMain/kotlin/Env.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.cinterop.* 2 | import platform.windows.* 3 | 4 | actual fun readPassword(prompt: String): String? = memScoped { 5 | print(prompt) 6 | 7 | val handle = GetStdHandle(STD_INPUT_HANDLE) 8 | if (handle == INVALID_HANDLE_VALUE) { 9 | error("Standard input not available!") 10 | } 11 | 12 | val record = alloc() 13 | val read = alloc() 14 | 15 | buildString { 16 | while (true) { 17 | val numberOfRecordsToRead = 1.convert() 18 | if (ReadConsoleInput?.let { it(handle, record.ptr, numberOfRecordsToRead, read.ptr) } == 0) { 19 | error("Could not read console input") 20 | } 21 | 22 | if (record.EventType == KEY_EVENT.toUShort() && record.Event.KeyEvent.bKeyDown != 0) { 23 | val char = record.Event.KeyEvent.uChar.UnicodeChar 24 | if (char == '\r'.code.toUShort()) { 25 | break 26 | } else if (char != 0.toUShort()) { 27 | append(char) 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/native/src/nativeMain/kotlin/io/IO.kt: -------------------------------------------------------------------------------- 1 | import kotlin.test.* 2 | import kotlinx.io.* 3 | import kotlinx.io.files.* 4 | 5 | fun buffer() { 6 | val buffer = Buffer() 7 | buffer.write(byteArrayOf(70, 64, -26, -74)) 8 | assertEquals(12345.678F.toBits(), buffer.readFloat().toBits()) 9 | } 10 | 11 | fun dir() { 12 | println("SystemPathSeparator = $SystemPathSeparator") 13 | println("SystemTemporaryDirectory: $SystemTemporaryDirectory") 14 | SystemFileSystem.list(Path(".")).forEach { println(it) } 15 | } 16 | 17 | fun Path.append(data: String) { 18 | SystemFileSystem.sink(this, append = true).buffered().use { f -> f.writeString(data) } 19 | } 20 | -------------------------------------------------------------------------------- /backend/native/src/nativeMain/kotlin/wasm/Wasm.kt: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import io.github.charlietap.chasm.embedding.instance 4 | import io.github.charlietap.chasm.embedding.invoke 5 | import io.github.charlietap.chasm.embedding.module 6 | import io.github.charlietap.chasm.embedding.shapes.getOrNull 7 | import io.github.charlietap.chasm.embedding.shapes.map 8 | import io.github.charlietap.chasm.embedding.store 9 | import io.github.charlietap.chasm.runtime.value.NumberValue 10 | import kotlinx.io.buffered 11 | import kotlinx.io.files.Path 12 | import kotlinx.io.files.SystemFileSystem 13 | import kotlinx.io.readByteArray 14 | 15 | fun execWasm(path: Path, arg: Int = 5) { 16 | try { 17 | val exists = SystemFileSystem.metadataOrNull(path)?.isRegularFile == true 18 | if (exists) { 19 | println("Executing wasm: $path") 20 | val module = module(bytes = SystemFileSystem.source(path).buffered().readByteArray()) 21 | val store = store() 22 | val instance = 23 | instance(store = store, module = module.getOrNull()!!, imports = emptyList()) 24 | .getOrNull()!! 25 | 26 | val result = 27 | invoke(store, instance, "iterFact", listOf(NumberValue.I32(arg))).map { 28 | (it.first() as NumberValue.I32).value 29 | } 30 | println("Result: $result") 31 | } else { 32 | println("Wasm file not found: $path!") 33 | } 34 | } catch (e: Exception) { 35 | println("Failed to execute wasm: ${e.message}") 36 | e.printStackTrace() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backend/native/src/nativeTest/kotlin/NativeTest.kt: -------------------------------------------------------------------------------- 1 | import dev.suresh.platform 2 | import kotlin.test.Test 3 | import kotlin.test.assertTrue 4 | 5 | class NativeTest { 6 | @Test 7 | fun test() { 8 | assertTrue(platform.name == "Native") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/profiling/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.jvmTarget 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.mpp 5 | dev.suresh.plugin.publishing 6 | } 7 | 8 | description = "JVM Profiling and Monitoring!" 9 | 10 | kotlin { 11 | jvmTarget(project) 12 | 13 | sourceSets { 14 | commonMain { dependencies { implementation(projects.shared) } } 15 | 16 | jvmMain { 17 | dependencies { 18 | implementation(libs.jmc.common) 19 | implementation(libs.jmc.jfr) 20 | implementation(libs.ap.jfr.converter) 21 | implementation(libs.bytesize) 22 | // implementation(libs.ap.loader.all) 23 | } 24 | kotlin.srcDir("src/main/kotlin") 25 | resources.srcDir("src/main/resources") 26 | } 27 | 28 | jvmTest { 29 | kotlin.srcDir("src/test/kotlin") 30 | resources.srcDir("src/test/resources") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/security/api/security.klib.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/backend/security/api/security.klib.api -------------------------------------------------------------------------------- /backend/security/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.jvmTarget 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.mpp 5 | dev.suresh.plugin.publishing 6 | `binary-compatibility-validator` 7 | } 8 | 9 | description = "Certificate and Security!" 10 | 11 | kotlin { 12 | jvmTarget(project) 13 | 14 | sourceSets { 15 | commonMain { dependencies { implementation(projects.shared) } } 16 | 17 | jvmMain { 18 | kotlin.srcDir("src/main/kotlin") 19 | resources.srcDir("src/main/resources") 20 | } 21 | 22 | jvmTest { 23 | kotlin.srcDir("src/test/kotlin") 24 | resources.srcDir("src/test/resources") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/TrustStore.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import com.github.marschall.directorykeystore.* 4 | import java.nio.file.Path 5 | import java.security.KeyStore 6 | import java.security.Security 7 | import javax.net.ssl.TrustManagerFactory 8 | import javax.net.ssl.X509TrustManager 9 | 10 | /** 11 | * JVM can be switched to use a different truststore using **-Djavax.net.ssl.trustStoreType=xxx** 12 | */ 13 | object TrustStore { 14 | 15 | fun allTrustStores(): List = 16 | Security.getProviders() 17 | .flatMap { it.entries } 18 | .map { it.key.toString() } 19 | .filter { it.startsWith("KeyStore.") && it.endsWith("ImplementedIn").not() } 20 | .map { it.substringAfter("KeyStore.").trim() } 21 | .distinct() 22 | 23 | fun systemTrustStore(type: TrustStoreType): KeyStore = 24 | when (type) { 25 | is TrustStoreType.Directory -> { 26 | if (Security.getProvider(DirectoryKeystoreProvider.NAME) == null) { 27 | Security.addProvider(DirectoryKeystoreProvider()) 28 | } 29 | KeyStore.getInstance(type.name).apply { load(DirectoryLoadStoreParameter(type.path)) } 30 | } 31 | else -> KeyStore.getInstance(type.name).apply { load(null, null) } 32 | } 33 | 34 | val caCerts = 35 | TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).run { 36 | init(null as KeyStore?) 37 | // trustManagers.filterIsInstance().flatMap { it.acceptedIssuers.toList() 38 | // } 39 | } 40 | 41 | /** Returns the default trust managers. This is initialized using JDK's `cacerts` trust store. */ 42 | val cacertsTrustManager by lazy { 43 | TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).run { 44 | // Use the JDK cacerts 45 | init(null as KeyStore?) 46 | trustManagers.filterIsInstance() 47 | } 48 | } 49 | } 50 | 51 | sealed class TrustStoreType(val name: String) { 52 | 53 | data object WIN_USER : TrustStoreType("Windows-MY") 54 | 55 | data object WIN_SYSTEM : TrustStoreType("Windows-ROOT") 56 | 57 | data object MACOS_USER : TrustStoreType("KeychainStore") 58 | 59 | data object MACOS_SYSTEM : TrustStoreType("KeychainStore-ROOT") 60 | 61 | class Directory(val path: Path) : TrustStoreType("Directory") 62 | } 63 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/cert/CertScan.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.cert 2 | 3 | import dev.suresh.tls.SavingTrustManager 4 | import dev.suresh.tls.newTLSSocket 5 | import java.net.InetSocketAddress 6 | import java.security.cert.X509Certificate 7 | import javax.net.ssl.SNIHostName 8 | import kotlin.time.Duration 9 | import kotlin.time.Duration.Companion.seconds 10 | 11 | object CertScan { 12 | 13 | fun scan( 14 | host: String, 15 | port: Int = 443, 16 | sni: String? = null, 17 | timeout: Duration = 2.seconds 18 | ): List { 19 | val trustManager = SavingTrustManager() 20 | val socket = trustManager.newTLSSocket() 21 | return socket.use { sock -> 22 | val handshake = runCatching { 23 | sni?.let { 24 | // sock.sslParameters will create a new copy 25 | val sslParams = sock.sslParameters 26 | sslParams.serverNames = listOf(SNIHostName(sni)) 27 | sock.sslParameters = sslParams 28 | } 29 | sock.soTimeout = timeout.inWholeMilliseconds.toInt() 30 | sock.connect(InetSocketAddress(host, port), timeout.inWholeMilliseconds.toInt()) 31 | sock.startHandshake() 32 | 33 | // Peer has to be authenticated this to work 34 | // sock.session.peerCertificates.filterIsInstance() 35 | } 36 | trustManager.chain 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/security/PasswordGen.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.security 2 | 3 | /** Password generator. This is not thread safe! */ 4 | class PasswordGen( 5 | private val lowercase: Boolean = true, 6 | private val uppercase: Boolean = true, 7 | private val number: Boolean = true, 8 | private val special: Boolean = true, 9 | ) { 10 | 11 | private var allowedChars: List 12 | 13 | init { 14 | check(lowercase || uppercase || number || special) { "At least one char type must be enabled" } 15 | allowedChars = buildList { 16 | if (lowercase) addAll('a'..'z') 17 | if (uppercase) addAll('A'..'Z') 18 | if (number) addAll('0'..'9') 19 | if (special) addAll("~!@#$%^&*+=".toList()) 20 | } 21 | } 22 | 23 | fun generate(len: Int = 15): String { 24 | check(len > 0) { "Password length must be greater than 0" } 25 | allowedChars = allowedChars.shuffled() 26 | return buildString { repeat(len) { append(allowedChars.random()) } } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/tls/AliasKeyManager.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tls 2 | 3 | import java.net.Socket 4 | import java.security.Principal 5 | import javax.net.ssl.X509KeyManager 6 | 7 | /** 8 | * A [X509KeyManager] implementation which selects the client private key for client authentication 9 | * based on given key alias name. 10 | */ 11 | class AliasKeyManager(private val delegate: X509KeyManager, private val aliasName: String) : 12 | X509KeyManager by delegate { 13 | override fun chooseClientAlias( 14 | keyType: Array, 15 | issuers: Array, 16 | socket: Socket 17 | ): String = aliasName 18 | } 19 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/tls/CustomSSLSocketFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tls 2 | 3 | import java.net.InetAddress 4 | import java.net.Socket 5 | import javax.net.ssl.SSLSocket 6 | import javax.net.ssl.SSLSocketFactory 7 | 8 | class CustomSSLSocketFactory(private val delegate: SSLSocketFactory) : SSLSocketFactory() { 9 | override fun getDefaultCipherSuites(): Array = delegate.defaultCipherSuites 10 | 11 | override fun getSupportedCipherSuites(): Array = delegate.supportedCipherSuites 12 | 13 | override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket = 14 | delegate.createSocket(s, host, port, autoClose).apply { reconfigureSSLSocket() } 15 | 16 | override fun createSocket(host: String, port: Int): Socket = 17 | delegate.createSocket(host, port).apply { reconfigureSSLSocket() } 18 | 19 | override fun createSocket( 20 | host: String, 21 | port: Int, 22 | localHost: InetAddress, 23 | localPort: Int 24 | ): Socket = 25 | delegate.createSocket(host, port, localHost, localPort).apply { reconfigureSSLSocket() } 26 | 27 | override fun createSocket(host: InetAddress, port: Int): Socket = 28 | delegate.createSocket(host, port).apply { reconfigureSSLSocket() } 29 | 30 | override fun createSocket( 31 | address: InetAddress, 32 | port: Int, 33 | localAddress: InetAddress, 34 | localPort: Int 35 | ): Socket = 36 | delegate.createSocket(address, port, localAddress, localPort).apply { reconfigureSSLSocket() } 37 | } 38 | 39 | fun Socket.reconfigureSSLSocket() { 40 | val sslSock = this as SSLSocket 41 | val sslParams = sslSock.sslParameters 42 | // Disable SNI 43 | sslParams.serverNames = emptyList() 44 | // Disable Hostname verification (Same as -Djdk.internal.httpclient.disableHostnameVerification) 45 | sslParams.endpointIdentificationAlgorithm = null 46 | sslParameters = sslParams 47 | } 48 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/tls/SavingTrustManager.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tls 2 | 3 | import java.security.cert.X509Certificate 4 | import javax.net.ssl.* 5 | 6 | class SavingTrustManager : X509TrustManager { 7 | 8 | private val _chain = mutableListOf() 9 | 10 | val chain: List 11 | get() = _chain 12 | 13 | override fun checkClientTrusted(chain: Array, authType: String) { 14 | _chain.addAll(chain) 15 | } 16 | 17 | override fun checkServerTrusted(chain: Array, authType: String) { 18 | _chain.addAll(chain) 19 | } 20 | 21 | override fun getAcceptedIssuers(): Array = emptyArray() 22 | } 23 | 24 | fun SavingTrustManager.newTLSSocket(): SSLSocket { 25 | val tm = this 26 | return SSLContext.getInstance("TLS").run { 27 | init(null, arrayOf(tm), null) 28 | socketFactory.createSocket() as SSLSocket 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/tls/TLSProp.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tls 2 | 3 | import java.net.JarURLConnection 4 | import java.security.Security 5 | import java.util.jar.Manifest 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * Enumeration of JSSE (Java Secure Socket Extension) system and security properties used for 10 | * configuring TLS/SSL connections. 11 | */ 12 | enum class TLSProp(val prop: String, val desc: String, val system: Boolean = true) { 13 | Debug("javax.net.debug", "Debugging SSL/TLS Connections."), 14 | KeyStore("javax.net.ssl.keyStore", "Default keystore"), 15 | KeyStoreType("javax.net.ssl.keyStoreType", "Default keystore type"), 16 | KeyStorePassword("javax.net.ssl.keyStorePassword", "Default keystore password"), 17 | KeyStoreProvider("javax.net.ssl.keyStoreProvider", "Default keystore provider"), 18 | TrustStore("javax.net.ssl.trustStore", "Default truststore"), 19 | TrustStoreType("javax.net.ssl.trustStoreType", "Default truststore type"), 20 | TrustStorePassword("javax.net.ssl.trustStorePassword", "Default truststore password"), 21 | TrustStoreProvider("javax.net.ssl.trustStoreProvider", "Default truststore provider"), 22 | ProxyHost("https.proxyHost", "Default HTTPS proxy host"), 23 | ProxyPort("https.proxyPort", "Default HTTPS proxy port"), 24 | HttpsCipherSuites("https.cipherSuites", "Default cipher suites"), 25 | HttpsProtocols("https.protocols", "Default HTTPS handshaking protocols"), 26 | TLSProtocols("jdk.tls.client.protocols", "Default Enabled TLS Protocols"), 27 | CertPathDisabledAlgos( 28 | "jdk.certpath.disabledAlgorithms", 29 | "Disabled certificate verification cryptographic algorithms", 30 | false), 31 | TLSDisabledAlgos("jdk.tls.disabledAlgorithms", "Disabled/Restricted Algorithms", false); 32 | 33 | /** Sets the JSSE system/security property to the given value. */ 34 | fun set(value: String) { 35 | when (system) { 36 | true -> System.setProperty(prop, value) 37 | else -> Security.setProperty(prop, value) 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Returns the jar [Manifest] of the class. Returns `null` if the class is not bundled in a jar 44 | * (Classes in an unpacked class hierarchy). 45 | */ 46 | val KClass.jarManifest: Manifest? 47 | get() { 48 | val res = java.getResource("${java.simpleName}.class") 49 | val conn = res?.openConnection() 50 | return if (conn is JarURLConnection) conn.manifest else null 51 | } 52 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | ### Run Benchmarks 2 | 3 | ```bash 4 | $ ./gradlew :benchmark:benchmark 5 | ``` 6 | 7 | ### Resources 8 | 9 | - [Kotlin Multiplatform Benchmarking](https://github.com/Kotlin/kotlinx-benchmark/tree/master/examples) 10 | -------------------------------------------------------------------------------- /benchmark/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { dev.suresh.plugin.kotlin.benchmark } 2 | 3 | dependencies { commonMainImplementation(projects.shared) } 4 | -------------------------------------------------------------------------------- /benchmark/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import kotlin.math.* 4 | import kotlinx.benchmark.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 9 | @BenchmarkMode(Mode.AverageTime) 10 | class CommonBenchmark { 11 | private var data = 0.0 12 | private lateinit var text: String 13 | 14 | @Setup 15 | fun setUp() { 16 | data = 3.0 17 | text = "Hello!" 18 | } 19 | 20 | @TearDown fun teardown() {} 21 | 22 | @Benchmark 23 | fun exception() { 24 | try { 25 | fail() 26 | } catch (e: Throwable) { 27 | throw Exception("I failed!", e) 28 | } 29 | } 30 | 31 | private fun fail() { 32 | error("Not implemented") 33 | } 34 | 35 | @Benchmark 36 | fun mathBenchmark(): Double { 37 | return log(sqrt(data) * cos(data), 2.0) 38 | } 39 | 40 | @Benchmark 41 | fun longBenchmark(): Double { 42 | var value = 1.0 43 | repeat(1000) { value *= text.length } 44 | return value 45 | } 46 | 47 | @Benchmark 48 | fun longBlackholeBenchmark(bh: Blackhole) { 49 | repeat(1000) { bh.consume(text.length) } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /benchmark/src/jvmMain/kotlin/JvmBenchmark.kt: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import java.util.concurrent.TimeUnit 4 | import jdk.incubator.vector.LongVector 5 | import org.openjdk.jmh.annotations.* 6 | 7 | const val WARMUP_ITERATIONS = 5 8 | 9 | @State(Scope.Benchmark) 10 | @BenchmarkMode(Mode.AverageTime) 11 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 12 | @Warmup(iterations = WARMUP_ITERATIONS, time = 1, timeUnit = TimeUnit.SECONDS) 13 | @Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) 14 | @Fork(value = 1, jvmArgsAppend = ["--add-modules=jdk.incubator.vector"]) 15 | class JvmTestBenchmark { 16 | 17 | private var data = 0.0 18 | 19 | @Setup 20 | fun setUp() { 21 | data = 3.0 22 | } 23 | 24 | @Benchmark 25 | fun sqrtBenchmark(): Double { 26 | return Math.sqrt(data) 27 | } 28 | 29 | @Benchmark 30 | fun cosBenchmark(): Double { 31 | return Math.cos(data) 32 | } 33 | 34 | @Benchmark fun vectorAPI() = LongVector.SPECIES_PREFERRED.length() 35 | } 36 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | 3 | plugins { 4 | dev.suresh.plugin.root 5 | // alias(libs.plugins.kotlin.multiplatform) apply false 6 | // id(libs.plugins.kotlin.multiplatform.get().pluginId) 7 | } 8 | 9 | description = "Kotlin Multiplatform Playground!" 10 | 11 | nmcp { 12 | centralPortal { 13 | username = mavenCentralUsername 14 | password = mavenCentralPassword 15 | publishingType = "AUTOMATIC" 16 | } 17 | } 18 | 19 | dependencies { 20 | dokka(projects.shared) 21 | dokka(projects.meta.ksp.processor) 22 | dokka(projects.meta.compiler.plugin) 23 | dokka(projects.backend.jvm) 24 | dokka(projects.backend.data) 25 | dokka(projects.backend.profiling) 26 | dokka(projects.backend.security) 27 | dokka(projects.web) 28 | 29 | nmcpAggregation(projects.shared) 30 | nmcpAggregation(projects.depMgmt.bom) 31 | nmcpAggregation(projects.depMgmt.catalog) 32 | nmcpAggregation(projects.meta.ksp.processor) 33 | nmcpAggregation(projects.meta.compiler.plugin) 34 | nmcpAggregation(projects.backend.jvm) 35 | nmcpAggregation(projects.backend.data) 36 | nmcpAggregation(projects.backend.profiling) 37 | nmcpAggregation(projects.backend.security) 38 | nmcpAggregation(projects.web) 39 | 40 | // Optional modules 41 | findProject(":backend:native")?.let { nmcpAggregation(it) } 42 | findProject(":compose:cmp")?.let { nmcpAggregation(it) } 43 | findProject(":compose:html")?.let { nmcpAggregation(it) } 44 | } 45 | -------------------------------------------------------------------------------- /compose/README.md: -------------------------------------------------------------------------------- 1 | ## [JetBrains Compose Multiplatform][1] Projects 2 | 3 | ### Resources 4 | 5 | * [CSS2KobWeb](https://opletter.github.io/css2kobweb/) 6 | * [Jetbrains Compose Multiplatform Core Repo](https://github.com/JetBrains/compose-multiplatform-core) 7 | 8 | [1]: https://github.com/JetBrains/compose-multiplatform 9 | -------------------------------------------------------------------------------- /compose/cli/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | 3 | plugins { 4 | application 5 | com.gradleup.shadow 6 | dev.suresh.plugin.kotlin.mpp 7 | dev.suresh.plugin.publishing 8 | alias(libs.plugins.kotlin.compose.compiler) 9 | // alias(libs.plugins.detekt) 10 | } 11 | 12 | description = "Compose mosaic CLI app!" 13 | 14 | kotlin { 15 | jvmTarget(project) 16 | 17 | sourceSets { 18 | commonMain.dependencies { 19 | implementation(projects.shared) 20 | implementation(libs.mosaic.runtime) 21 | } 22 | } 23 | } 24 | 25 | application { 26 | mainClass = libs.versions.app.mainclass.get() 27 | applicationDefaultJvmArgs += project.runJvmArgs 28 | } 29 | -------------------------------------------------------------------------------- /compose/cli/src/commonMain/kotlin/dev/suresh/Counter.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import androidx.compose.runtime.* 4 | import com.jakewharton.mosaic.ui.Box 5 | import com.jakewharton.mosaic.ui.Column 6 | import com.jakewharton.mosaic.ui.Text 7 | import kotlinx.coroutines.delay 8 | 9 | @Composable 10 | fun Counter() { 11 | var counter by remember { mutableStateOf(0) } 12 | Text("The count is: $counter") 13 | 14 | Column { Box { Text("The count is: $counter") } } 15 | 16 | LaunchedEffect(Unit) { 17 | for (i in 1..10) { 18 | counter = i 19 | delay(1000) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /compose/cli/src/jvmMain/kotlin/dev/suresh/App.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import com.jakewharton.mosaic.runMosaicBlocking 4 | 5 | fun main() = runMosaicBlocking { GradientsUI() } 6 | -------------------------------------------------------------------------------- /compose/cmp/src/assets/common/splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/assets/common/splash.jpg -------------------------------------------------------------------------------- /compose/cmp/src/assets/icons/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/assets/icons/linux.png -------------------------------------------------------------------------------- /compose/cmp/src/assets/icons/mac.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/assets/icons/mac.icns -------------------------------------------------------------------------------- /compose/cmp/src/assets/icons/win.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/assets/icons/win.ico -------------------------------------------------------------------------------- /compose/cmp/src/assets/macos-arm64/resource.txt: -------------------------------------------------------------------------------- 1 | Desktop App for MacOS Arm64 -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/audio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 15 | 16 | 17 | 21 | 22 | 23 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/ball-triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 38 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/circles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 13 | 14 | 15 | 19 | 20 | 21 | 25 | 26 | 27 | 31 | 32 | 33 | 37 | 38 | 39 | 43 | 44 | 45 | 49 | 50 | 51 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/hearts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/oval.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/puff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 28 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/rings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 14 | 18 | 19 | 20 | 25 | 29 | 33 | 34 | 35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/tail-spin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/three-dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 12 | 13 | 14 | 18 | 22 | 23 | 24 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/ic-fluent-rocket.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/particles.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/commonMain/composeResources/drawable/particles.gif -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/commonMain/composeResources/files/.keep -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | import nav.NavRoot 3 | import ui.AppTheme 4 | 5 | @Composable 6 | fun App() { 7 | AppTheme { NavRoot() } 8 | } 9 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/nav/NavRoot.kt: -------------------------------------------------------------------------------- 1 | package nav 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.NavType 5 | import androidx.navigation.compose.NavHost 6 | import androidx.navigation.compose.composable 7 | import androidx.navigation.compose.rememberNavController 8 | import androidx.navigation.navArgument 9 | import ui.birds.BirdImages 10 | import ui.file.FileBrowser 11 | import ui.home.Home 12 | 13 | @Composable 14 | fun NavRoot() { 15 | val navController = rememberNavController() 16 | NavHost(navController = navController, startDestination = Screen.Home.route) { 17 | composable(Screen.Home.route) { 18 | Home( 19 | navToFile = { navController.navigate(Screen.FileBrowser.route) }, 20 | navToImage = { navController.navigate(Screen.Image.route) }) 21 | } 22 | composable(Screen.FileBrowser.route) { 23 | FileBrowser(navToHome = { navController.popBackStack() }) 24 | } 25 | 26 | composable(Screen.Image.route) { BirdImages(navToHome = { navController.popBackStack() }) } 27 | 28 | composable( 29 | route = Screen.Profile.route, 30 | arguments = listOf(navArgument("userId") { type = NavType.StringType })) { 31 | // val userId = it.arguments?.getString("userId") 32 | // Profile(userId = userId) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/nav/Screen.kt: -------------------------------------------------------------------------------- 1 | package nav 2 | 3 | sealed class Screen(val route: String) { 4 | data object Home : Screen("home") 5 | 6 | data object FileBrowser : Screen("fileBrowser") 7 | 8 | data object Image : Screen("image") 9 | 10 | data object Video : Screen("video") 11 | 12 | data object Profile : Screen("profile?userId={userId}") 13 | } 14 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/Image.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.rememberCoroutineScope 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.draw.drawWithContent 10 | import androidx.compose.ui.graphics.* 11 | import androidx.compose.ui.graphics.layer.drawLayer 12 | import kotlinx.coroutines.launch 13 | import org.jetbrains.compose.resources.* 14 | 15 | /** 16 | * Image decoders for [ByteArray] 17 | * 18 | * @see [JPEG, PNG, BMP, WEBP to ImageBitmap][ByteArray.decodeToImageBitmap] 19 | * @see [Vector XML file to an ImageVector][ByteArray.decodeToImageVector] 20 | * @see [SVG file to a compose Painter][ByteArray.decodeToSvgPainter] 21 | */ 22 | fun ImageBitmap.toByteArray(): ByteArray? = asSkiaBitmap().readPixels() 23 | 24 | @Composable 25 | fun SaveToBitmap( 26 | modifier: Modifier = Modifier, 27 | onSave: (ImageBitmap) -> Unit, 28 | content: @Composable () -> Unit 29 | ) { 30 | val coroutineScope = rememberCoroutineScope() 31 | val graphicsLayer = rememberGraphicsLayer() 32 | Box( 33 | modifier = 34 | modifier 35 | .drawWithContent { 36 | graphicsLayer.record { 37 | // Draw the content of the composable to the graphics layer. 38 | this@drawWithContent.drawContent() 39 | } 40 | drawLayer(graphicsLayer) 41 | } 42 | .clickable { 43 | coroutineScope.launch { 44 | val bitmap = graphicsLayer.toImageBitmap() 45 | onSave(bitmap) 46 | } 47 | } 48 | .background(Color.White)) { 49 | content() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/Modifier.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.composed 6 | import androidx.compose.ui.draw.DrawModifier 7 | import androidx.compose.ui.draw.drawWithCache 8 | import androidx.compose.ui.geometry.CornerRadius 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.graphics.PathEffect 11 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope 12 | import androidx.compose.ui.graphics.drawscope.Stroke 13 | import androidx.compose.ui.graphics.drawscope.scale 14 | import androidx.compose.ui.platform.LocalDensity 15 | import androidx.compose.ui.unit.Dp 16 | import androidx.compose.ui.unit.dp 17 | import kotlinx.coroutines.MainScope 18 | 19 | val debug = true 20 | 21 | val mainScope = MainScope() 22 | 23 | fun Modifier.debug(color: Color = Color.Red) = 24 | then(if (debug) dashedBorder(1.dp, color, 8.dp) else this) 25 | 26 | fun Modifier.dashedBorder(strokeWidth: Dp, color: Color, cornerRadius: Dp): Modifier = composed { 27 | val density = LocalDensity.current 28 | val strokeWidthPx = density.run { strokeWidth.toPx() } 29 | val cornerRadiusPx = density.run { cornerRadius.toPx() } 30 | 31 | then( 32 | Modifier.drawWithCache { 33 | onDrawBehind { 34 | val stroke = 35 | Stroke( 36 | width = strokeWidthPx, 37 | pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)) 38 | drawRoundRect(color = color, style = stroke, cornerRadius = CornerRadius(cornerRadiusPx)) 39 | } 40 | } 41 | // .border(border = BorderStroke(strokeWidth, color),shape = RoundedCornerShape(cornerRadius)) 42 | ) 43 | } 44 | 45 | @Composable 46 | fun buildModifier(builder: @Composable MutableList.() -> Unit): Modifier { 47 | val modifiers = mutableListOf() 48 | modifiers.builder() 49 | // The first Modifier is the empty, starter Modifier object 50 | return modifiers.fold(Modifier as Modifier) { acc, modifier -> acc then modifier } 51 | } 52 | 53 | fun Modifier.ifTrue(value: Boolean, builder: Modifier.() -> Modifier) = 54 | then(if (value) builder() else Modifier) 55 | 56 | fun Modifier.flipped() = then(FlippedModifier()) 57 | 58 | class FlippedModifier : DrawModifier { 59 | override fun ContentDrawScope.draw() { 60 | scale(1f, -1f) { this@draw.drawContent() } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/Theme.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.unit.dp 8 | 9 | @Composable 10 | fun AppTheme(content: @Composable () -> Unit) { 11 | MaterialTheme( 12 | colorScheme = MaterialTheme.colorScheme.copy(primary = Color.Black), 13 | shapes = 14 | MaterialTheme.shapes.copy( 15 | small = RoundedCornerShape(0.dp), 16 | medium = RoundedCornerShape(0.dp), 17 | large = RoundedCornerShape(0.dp))) { 18 | content() 19 | } 20 | } 21 | 22 | object FileColors { 23 | val default = Color.Gray 24 | val active = Color(29, 117, 223, 255) 25 | val fileItemBg = Color(233, 30, 99, 255) 26 | val fileItemFg = Color.White 27 | } 28 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/birds/BirdsScreen.kt: -------------------------------------------------------------------------------- 1 | package ui.birds 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.text.selection.SelectionContainer 6 | import androidx.compose.foundation.verticalScroll 7 | import androidx.compose.material3.CircularProgressIndicator 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.LaunchedEffect 12 | import androidx.compose.runtime.collectAsState 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.unit.dp 18 | import androidx.lifecycle.viewmodel.compose.viewModel 19 | import ui.debug 20 | import ui.home.HomeButton 21 | 22 | @Composable 23 | fun BirdImages(modifier: Modifier = Modifier, navToHome: () -> Unit) { 24 | val birdsViewModel = viewModel { BirdsViewModel() } 25 | val state by birdsViewModel.uiState.collectAsState() 26 | val scrollState = rememberScrollState() 27 | 28 | // Load the BirdUiState 29 | LaunchedEffect(birdsViewModel) { birdsViewModel.update() } 30 | 31 | Column( 32 | modifier = 33 | modifier 34 | .fillMaxSize() 35 | .padding(10.dp) 36 | .debug(color = Color.Black) 37 | .verticalScroll(scrollState), 38 | horizontalAlignment = Alignment.CenterHorizontally, 39 | verticalArrangement = Arrangement.SpaceEvenly) { 40 | if (state.images.isEmpty()) { 41 | CircularProgressIndicator( 42 | modifier = Modifier.width(64.dp), 43 | color = MaterialTheme.colorScheme.secondary, 44 | trackColor = MaterialTheme.colorScheme.surfaceVariant, 45 | ) 46 | Text("Loading images...") 47 | } else { 48 | state.images.forEach { image -> 49 | SelectionContainer { Text(image.path) } 50 | Spacer(modifier = Modifier.width(5.dp)) 51 | } 52 | } 53 | Spacer(modifier = Modifier.height(5.dp)) 54 | HomeButton(navToHome) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/birds/BirdsViewModel.kt: -------------------------------------------------------------------------------- 1 | package ui.birds 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dev.suresh.http.Image 6 | import dev.suresh.http.MediaApiClient 7 | import dev.suresh.http.Video 8 | import dev.suresh.log 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.asStateFlow 11 | import kotlinx.coroutines.flow.update 12 | import kotlinx.coroutines.launch 13 | 14 | data class BirdUiState( 15 | val images: List = emptyList(), 16 | val videos: List = emptyList() 17 | ) { 18 | 19 | val categories by lazy { images.map { it.category }.distinct() } 20 | } 21 | 22 | class BirdsViewModel : ViewModel() { 23 | init { 24 | log.info { "Creating BirdsViewModel" } 25 | } 26 | 27 | private val client = MediaApiClient() 28 | 29 | private val _uiState = MutableStateFlow(BirdUiState()) 30 | 31 | val uiState = _uiState.asStateFlow() 32 | 33 | fun update() { 34 | log.info { "Retrieving the images and vide info..." } 35 | viewModelScope.launch { 36 | val images = client.images() 37 | val videos = client.videos() 38 | log.info { "Got ${images.size} images and ${videos.size} videos" } 39 | _uiState.update { state -> state.copy(images = images, videos = videos) } 40 | } 41 | } 42 | 43 | override fun onCleared() { 44 | log.info { "Closing the client..." } 45 | super.onCleared() 46 | client.close() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/file/FileBrowser.kt: -------------------------------------------------------------------------------- 1 | package ui.file 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import ui.debug 13 | import ui.home.HomeButton 14 | 15 | @Composable expect fun DragDropListView() 16 | 17 | @Composable 18 | fun FileBrowser(modifier: Modifier = Modifier, navToHome: () -> Unit) { 19 | Column( 20 | modifier = 21 | modifier.fillMaxSize().padding(10.dp).debug(color = MaterialTheme.colorScheme.primary), 22 | horizontalAlignment = Alignment.CenterHorizontally, 23 | verticalArrangement = Arrangement.SpaceEvenly) { 24 | DragDropListView() 25 | HomeButton(navToHome) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/lottie/Animation.kt: -------------------------------------------------------------------------------- 1 | package ui.lottie 2 | 3 | import KottieAnimation 4 | import androidx.compose.runtime.* 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.ui.Modifier 8 | import dev.suresh.compose.res.Res 9 | import kottieComposition.KottieCompositionSpec 10 | import kottieComposition.animateKottieCompositionAsState 11 | import kottieComposition.rememberKottieComposition 12 | import org.jetbrains.compose.resources.ExperimentalResourceApi 13 | 14 | @OptIn(ExperimentalResourceApi::class) 15 | @Composable 16 | fun lottie(modifier: Modifier = Modifier, res: String = "files/lottie/anim.json") { 17 | var afterEffectAnim by remember { mutableStateOf("") } 18 | 19 | LaunchedEffect(Unit) { afterEffectAnim = Res.readBytes(res).decodeToString() } 20 | 21 | val composition = rememberKottieComposition(spec = KottieCompositionSpec.File(afterEffectAnim)) 22 | val animationState by 23 | animateKottieCompositionAsState( 24 | composition = composition, 25 | isPlaying = true, 26 | ) 27 | 28 | KottieAnimation( 29 | modifier = modifier, 30 | composition = composition, 31 | progress = { animationState.progress }, 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/misc/Window.kt: -------------------------------------------------------------------------------- 1 | package ui.misc 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi 7 | import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | 12 | @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) 13 | @Composable 14 | fun WindowSizeClass() { 15 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 16 | val size = calculateWindowSizeClass() 17 | Text("width = ${size.widthSizeClass}\nheight = ${size.heightSizeClass}") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /compose/cmp/src/commonTest/kotlin/ui/ComposeTest.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.material3.* 4 | import androidx.compose.runtime.* 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.platform.testTag 7 | import androidx.compose.ui.test.* 8 | import kotlin.test.Test 9 | 10 | @OptIn(ExperimentalTestApi::class) 11 | class ComposeTest { 12 | 13 | @Test 14 | fun uiTest() = runComposeUiTest { 15 | setContent { 16 | var text by remember { mutableStateOf("Hello") } 17 | Text(text = text, modifier = Modifier.testTag("txtTag")) 18 | Button(onClick = { text = "Compose" }, modifier = Modifier.testTag("btnTag")) { 19 | Text("Click me") 20 | } 21 | } 22 | 23 | onNodeWithTag("txtTag").assertTextEquals("Hello") 24 | onNodeWithTag("btnTag").performClick() 25 | onNodeWithTag("txtTag").assertTextEquals("Compose") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /compose/cmp/src/jvmMain/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalComposeUiApi::class) 2 | 3 | import androidx.compose.runtime.CompositionLocalProvider 4 | import androidx.compose.ui.Alignment 5 | import androidx.compose.ui.ExperimentalComposeUiApi 6 | import androidx.compose.ui.unit.dp 7 | import androidx.compose.ui.window.* 8 | import java.awt.Dimension 9 | import java.io.File 10 | import kotlin.jvm.optionals.getOrElse 11 | import kotlinx.datetime.Clock 12 | import kotlinx.datetime.toJavaInstant 13 | import ui.crash.windowExceptionHandlerFactory 14 | 15 | val resource by lazy { 16 | val file = 17 | File(System.getProperty("compose.application.resources.dir", ".")).resolve("resource.txt") 18 | if (file.exists()) file.readText() else "App" 19 | } 20 | 21 | fun main() = application { 22 | CompositionLocalProvider( 23 | LocalWindowExceptionHandlerFactory provides windowExceptionHandlerFactory, 24 | ) { 25 | val windowState = 26 | rememberWindowState( 27 | width = 800.dp, height = 600.dp, position = WindowPosition(Alignment.Center)) 28 | 29 | Window(title = resource, state = windowState, onCloseRequest = ::exitApplication) { 30 | // DevelopmentEntryPoint { 31 | window.minimumSize = Dimension(350, 600) 32 | App() 33 | showStartupTime() 34 | // } 35 | } 36 | } 37 | } 38 | 39 | fun showStartupTime() { 40 | val currTime = System.currentTimeMillis() 41 | val vmTime = 42 | ProcessHandle.current() 43 | .info() 44 | .startInstant() 45 | .getOrElse { Clock.System.now().toJavaInstant() } 46 | .toEpochMilli() 47 | println("Application started in ${currTime - vmTime} ms") 48 | } 49 | -------------------------------------------------------------------------------- /compose/cmp/src/jvmMain/kotlin/ui/Image.jvm.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.ui.graphics.* 4 | import androidx.compose.ui.graphics.painter.Painter 5 | import androidx.compose.ui.unit.Density 6 | import androidx.compose.ui.unit.LayoutDirection 7 | import java.awt.image.BufferedImage 8 | import java.net.URI 9 | import java.nio.file.Path 10 | import javax.imageio.ImageIO 11 | 12 | fun URI.toImageBitmap() = ImageIO.read(toURL()).toComposeImageBitmap() 13 | 14 | fun ImageBitmap.toPngImage(path: Path): Boolean = 15 | toAwtImage().let { ImageIO.write(it, "png", path.toFile()) } 16 | 17 | fun Painter.toPngImage(path: Path): Boolean { 18 | val img = toAwtImage(Density(1f), LayoutDirection.Ltr) 19 | return BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB).run { 20 | graphics.drawImage(img, 0, 0, null) 21 | ImageIO.write(this, "png", path.toFile()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /compose/cmp/src/jvmMain/kotlin/ui/crash/CrashDialog.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalComposeUiApi::class) 2 | 3 | package ui.crash 4 | 5 | import androidx.compose.ui.ExperimentalComposeUiApi 6 | import androidx.compose.ui.window.WindowExceptionHandler 7 | import androidx.compose.ui.window.WindowExceptionHandlerFactory 8 | import dev.suresh.log 9 | import java.awt.Dimension 10 | import java.awt.Font 11 | import java.io.File 12 | import javax.swing.JOptionPane 13 | import javax.swing.JScrollPane 14 | import javax.swing.JTextArea 15 | import kotlin.system.exitProcess 16 | 17 | fun showCrashDialog(logDirectory: File, exception: Throwable) { 18 | val text = buildString { 19 | appendLine("Log directory:") 20 | appendLine(logDirectory) 21 | appendLine() 22 | appendLine("Stack trace:") 23 | appendLine(exception.stackTraceToString()) 24 | } 25 | JOptionPane.showMessageDialog( 26 | null, 27 | JScrollPane( 28 | JTextArea(text).apply { 29 | font = Font(Font.MONOSPACED, Font.PLAIN, 10) 30 | lineWrap = false 31 | }, 32 | JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 33 | JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS, 34 | ) 35 | .apply { preferredSize = Dimension(640, 480) }, 36 | "App failed unexpectedly", 37 | JOptionPane.ERROR_MESSAGE, 38 | ) 39 | } 40 | 41 | val windowExceptionHandlerFactory = WindowExceptionHandlerFactory { window -> 42 | WindowExceptionHandler { ex -> 43 | window.dispose() 44 | log.error(ex) { "Application UI crash" } 45 | showCrashDialog(File("."), ex) 46 | exitProcess(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /compose/cmp/src/jvmMain/kotlin/ui/file/FileChooser.jvm.kt: -------------------------------------------------------------------------------- 1 | package ui.file 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.window.AwtWindow 5 | import java.awt.FileDialog 6 | import java.awt.Frame 7 | import java.io.File 8 | import javax.swing.JFileChooser 9 | import javax.swing.UIManager 10 | import javax.swing.filechooser.FileNameExtensionFilter 11 | import javax.swing.filechooser.FileSystemView 12 | 13 | @Composable 14 | fun FileDialog(parent: Frame? = null, onClose: (result: List) -> Unit) = 15 | AwtWindow( 16 | visible = true, 17 | create = { 18 | object : FileDialog(parent, "Choose a file", LOAD) { 19 | override fun isMultipleMode() = true 20 | 21 | override fun setVisible(visible: Boolean) { 22 | super.setVisible(visible) 23 | if (visible) { 24 | onClose(files.toList()) 25 | } 26 | } 27 | } 28 | }, 29 | dispose = FileDialog::dispose) 30 | 31 | fun fileChooser(parent: Frame? = null, onClose: (result: List) -> Unit) { 32 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) 33 | val chooser = 34 | JFileChooser(FileSystemView.getFileSystemView()).apply { 35 | currentDirectory = File(System.getProperty("user.dir")) 36 | fileSelectionMode = JFileChooser.FILES_AND_DIRECTORIES 37 | isMultiSelectionEnabled = true 38 | dialogTitle = "Select a folder" 39 | approveButtonText = "Select" 40 | approveButtonToolTipText = "Select current directory as save destination" 41 | fileFilter = FileNameExtensionFilter("JPG & GIF Images", "jpg", "gif") 42 | } 43 | 44 | val files = chooser.selectedFiles.toList() 45 | chooser.isVisible = false 46 | onClose(files) 47 | } 48 | -------------------------------------------------------------------------------- /compose/cmp/src/wasmJsMain/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.ExperimentalComposeUiApi 2 | import androidx.compose.ui.window.ComposeViewport 3 | import kotlinx.browser.document 4 | import org.w3c.dom.HTMLElement 5 | 6 | @OptIn(ExperimentalComposeUiApi::class) 7 | fun main() { 8 | hideSpinner() 9 | ComposeViewport(document.body!!) { App() } 10 | } 11 | 12 | fun hideSpinner() { 13 | val spinner = document.querySelector(".loader") as? HTMLElement 14 | spinner?.style?.display = "none" 15 | } 16 | -------------------------------------------------------------------------------- /compose/cmp/src/wasmJsMain/kotlin/ui/file/FileBrowser.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package ui.file 2 | 3 | import androidx.compose.material3.Text 4 | import androidx.compose.runtime.Composable 5 | 6 | @Composable 7 | actual fun DragDropListView() { 8 | Text("Drag and drop is not supported!") 9 | } 10 | -------------------------------------------------------------------------------- /compose/cmp/src/wasmJsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Kotlin Compose Web App 7 | 8 | 9 | 10 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /compose/cmp/src/wasmJsMain/resources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compose Web App", 3 | "icons": [ 4 | { 5 | "src": "/images/compose.svg", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | } 9 | ], 10 | "start_url": "/", 11 | "display": "standalone", 12 | "background_color": "white" 13 | } -------------------------------------------------------------------------------- /compose/html/.kobweb/conf.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | title: "Kobweb App" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/html.js" 9 | api: "build/libs/html.jar" 10 | prod: 11 | script: "build/kotlin-webpack/js/productionExecutable/html.js" 12 | siteRoot: ".kobweb/site" 13 | 14 | port: 8080 -------------------------------------------------------------------------------- /compose/html/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | dev.suresh.plugin.kotlin.mpp 3 | dev.suresh.plugin.publishing 4 | alias(libs.plugins.kotlin.compose.compiler) 5 | alias(libs.plugins.kobweb.application) 6 | alias(libs.plugins.kobwebx.markdown) 7 | } 8 | 9 | description = "Compose HTML App" 10 | 11 | kobweb { app { index { description = "Powered by Kobweb (${libs.versions.kobweb})" } } } 12 | 13 | kotlin { 14 | jsTarget(project) 15 | 16 | // configAsKobwebApplication(moduleName = project.name) 17 | 18 | sourceSets { 19 | commonMain { 20 | dependencies { 21 | implementation(projects.shared) 22 | implementation(libs.compose.runtime) 23 | } 24 | } 25 | 26 | jsMain { 27 | dependencies { 28 | implementation(libs.compose.html.core) 29 | implementation(libs.kobweb.core) 30 | implementation(libs.kobweb.silk) 31 | implementation(libs.kobwebx.markdown) 32 | implementation(libs.kobwebx.serialization) 33 | // implementation(libs.silk.icons.fa) 34 | } 35 | } 36 | 37 | // jvmMain.dependencies { 38 | // compileOnly(libs.kobweb.api) 39 | // } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /compose/html/src/jsMain/kotlin/dev/suresh/MyApp.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import com.varabyte.kobweb.compose.css.ScrollBehavior 6 | import com.varabyte.kobweb.compose.ui.modifiers.minHeight 7 | import com.varabyte.kobweb.compose.ui.modifiers.scrollBehavior 8 | import com.varabyte.kobweb.core.App 9 | import com.varabyte.kobweb.silk.SilkApp 10 | import com.varabyte.kobweb.silk.components.layout.Surface 11 | import com.varabyte.kobweb.silk.init.InitSilk 12 | import com.varabyte.kobweb.silk.init.InitSilkContext 13 | import com.varabyte.kobweb.silk.style.common.SmoothColorStyle 14 | import com.varabyte.kobweb.silk.style.toModifier 15 | import com.varabyte.kobweb.silk.theme.colors.ColorMode 16 | import kotlinx.browser.localStorage 17 | import org.jetbrains.compose.web.css.vh 18 | 19 | private const val COLOR_MODE_KEY = "playground:colorMode" 20 | 21 | @InitSilk 22 | fun initColorMode(ctx: InitSilkContext) { 23 | ctx.config.initialColorMode = 24 | localStorage.getItem(COLOR_MODE_KEY)?.let { ColorMode.valueOf(it) } ?: ColorMode.LIGHT 25 | } 26 | 27 | @App 28 | @Composable 29 | fun MyApp(content: @Composable () -> Unit) { 30 | SilkApp { 31 | val colorMode = ColorMode.current 32 | LaunchedEffect(colorMode) { localStorage.setItem(COLOR_MODE_KEY, colorMode.name) } 33 | Surface(SmoothColorStyle.toModifier().minHeight(100.vh).scrollBehavior(ScrollBehavior.Smooth)) { 34 | content() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /compose/html/src/jsMain/kotlin/dev/suresh/components/layouts/PageLayout.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | 3 | @Composable 4 | fun PageLayout(content: @Composable () -> Unit) { 5 | Column { 6 | NavHeader() 7 | content() 8 | Footer() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /compose/html/src/jsMain/kotlin/dev/suresh/components/sections/Footer.kt: -------------------------------------------------------------------------------- 1 | @Composable fun Footer() {} 2 | -------------------------------------------------------------------------------- /compose/html/src/jsMain/kotlin/dev/suresh/components/sections/NavHeader.kt: -------------------------------------------------------------------------------- 1 | @Composable fun NavHeader() {} 2 | -------------------------------------------------------------------------------- /compose/html/src/jsMain/kotlin/dev/suresh/pages/Index.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.pages 2 | 3 | import BuildConfig 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.setValue 7 | import com.varabyte.kobweb.compose.foundation.layout.Column 8 | import com.varabyte.kobweb.compose.foundation.layout.Row 9 | import com.varabyte.kobweb.compose.ui.Alignment 10 | import com.varabyte.kobweb.compose.ui.Modifier 11 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius 12 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize 13 | import com.varabyte.kobweb.compose.ui.modifiers.flexWrap 14 | import com.varabyte.kobweb.compose.ui.modifiers.padding 15 | import com.varabyte.kobweb.core.Page 16 | import com.varabyte.kobweb.silk.components.forms.Button 17 | import com.varabyte.kobweb.silk.components.icons.MoonIcon 18 | import com.varabyte.kobweb.silk.components.icons.SunIcon 19 | import com.varabyte.kobweb.silk.components.navigation.Link 20 | import com.varabyte.kobweb.silk.components.text.SpanText 21 | import com.varabyte.kobweb.silk.theme.colors.ColorMode 22 | import org.jetbrains.compose.web.css.FlexWrap 23 | import org.jetbrains.compose.web.css.percent 24 | import org.jetbrains.compose.web.css.px 25 | import org.jetbrains.compose.web.dom.H1 26 | import org.jetbrains.compose.web.dom.Text 27 | 28 | @Page 29 | @Composable 30 | fun HomePage() { 31 | Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { 32 | Row(Modifier.align(Alignment.End)) { 33 | var colorMode by ColorMode.currentState 34 | Button( 35 | onClick = { colorMode = colorMode.opposite }, 36 | Modifier.borderRadius(50.percent).padding(0.px)) { 37 | // Includes support for Font Awesome icons 38 | if (colorMode.isLight) SunIcon() else MoonIcon() 39 | } 40 | } 41 | 42 | H1 { Text("Welcome to Kobweb! ${BuildConfig.version}") } 43 | 44 | Row(Modifier.flexWrap(FlexWrap.Wrap)) { 45 | SpanText("Create rich, dynamic web apps with ease, leveraging ") 46 | Link("https://kotlinlang.org/", "Kotlin") 47 | SpanText(" and ") 48 | Link("https://github.com/JetBrains/compose-multiplatform#compose-html/", "Compose HTML") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /compose/html/src/jsMain/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/html/src/jsMain/resources/public/favicon.ico -------------------------------------------------------------------------------- /compose/html/src/jsMain/resources/public/kobweb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/html/src/jsMain/resources/public/kobweb-logo.png -------------------------------------------------------------------------------- /dep-mgmt/bom/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-platform` 3 | dev.suresh.plugin.publishing 4 | } 5 | 6 | description = "A platform (BOM) used to align all module versions" 7 | 8 | dependencies { 9 | constraints { 10 | rootProject.subprojects.filter { it.childProjects.isEmpty() }.forEach { api(it) } 11 | // api(projects.backend) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dep-mgmt/catalog/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | dev.suresh.plugin.catalog 3 | dev.suresh.plugin.publishing 4 | } 5 | 6 | description = "Publish gradle version catalog!" 7 | -------------------------------------------------------------------------------- /gradle/.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Auto format Kotlin files in the staging area 3 | CHANGED_KT_FILES="$(git --no-pager diff --name-only --cached --diff-filter=ACMRTUXB --relative | grep '\.kt[s"]\?$')" 4 | if [ -z "${CHANGED_KT_FILES}" ]; then 5 | echo "No Kotlin staged files." 6 | else 7 | echo "There area some changed files. Running spotless..." 8 | ./gradlew :spotlessApply 9 | echo "Adding files to Git." 10 | echo "${CHANGED_KT_FILES}" | while read -r file; do 11 | if [ -f "$file" ]; then 12 | git add "$file" 13 | fi 14 | done 15 | fi -------------------------------------------------------------------------------- /gradle/build-logic/README.md: -------------------------------------------------------------------------------- 1 | ### Build Logic of the projects 2 | 3 | #### Run the `Gradle Best Practices` plugin on the build logic: 4 | 5 | ```bash 6 | $ ./gradlew -p gradle/build-logic :bestPracticesBaseline 7 | $ ./gradlew checkBuildLogicBestPractices 8 | # ./gradlew -p gradle/build-logic checkBestPractices 9 | ``` 10 | 11 | #### References 12 | 13 | * [Gradle Best Practices](https://github.com/liutikas/gradle-best-practices) 14 | * [Gradle Best Practices Plugin](https://github.com/autonomousapps/gradle-best-practices-plugin) 15 | * [Stampeding Elephants](https://developer.squareup.com/blog/stampeding-elephants/) 16 | * [Stop using buildSrc](https://proandroiddev.com/stop-using-gradle-buildsrc-use-composite-builds-instead-3c38ac7a2ab3) 17 | * [Project Dependency Graph Script](https://github.com/JakeWharton/SdkSearch/blob/master/gradle/projectDependencyGraph.gradle) 18 | * Gradle Providers and Properties 19 | 20 | ```kotlin 21 | val p1: Property = project.objects.property().convention("prop") 22 | val p2: Provider = project.providers.provider { "provider" } 23 | val p3: Provider = project.providers.environmentVariable( "ENV_VAR" ) 24 | val p4: Provider = project.providers.systemProperty("sys.prop") 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /gradle/build-logic/gradle.properties: -------------------------------------------------------------------------------- 1 | ## Gradle 2 | org.gradle.jvmargs=-Xmx2g 3 | org.gradle.parallel=true 4 | org.gradle.caching=true 5 | org.gradle.daemon=true 6 | org.gradle.configureondemand=true 7 | org.gradle.configuration-cache=true 8 | org.gradle.configuration-cache.problems=warn 9 | org.gradle.kotlin.dsl.allWarningsAsErrors=true 10 | org.gradle.unsafe.isolated-projects=true 11 | # org.gradle.logging.stacktrace=all 12 | 13 | ## Kotlin 14 | kotlin.code.style=official 15 | # kotlin.daemon.jvmargs=--enable-preview 16 | # kotlin.stdlib.default.dependency=false 17 | # https://kotl.in/gradle/jvm/target-validation 18 | kotlin.jvm.target.validation.mode=warning 19 | 20 | # Snapshot Repo 21 | maven.snapshot.repo.enabled=false -------------------------------------------------------------------------------- /gradle/build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | dependencyResolutionManagement { 4 | repositories { 5 | mavenCentral() 6 | gradlePluginPortal() 7 | mavenSnapshot() 8 | } 9 | 10 | versionCatalogs { register("libs") { from(files("../libs.versions.toml")) } } 11 | repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS 12 | } 13 | 14 | fun RepositoryHandler.mavenSnapshot() { 15 | val mvnSnapshot = providers.gradleProperty("maven.snapshot.repo.enabled").orNull.toBoolean() 16 | if (mvnSnapshot) { 17 | val mvnSnapshotRepo = 18 | file(rootDir) 19 | .resolveSibling("libs.versions.toml") 20 | .readLines() 21 | .first { it.contains("repo-mvn-snapshot") } 22 | .split("\"")[1] 23 | .trim() 24 | 25 | logger.lifecycle("❖ Maven Snapshot is enabled for build-logic!") 26 | maven(url = mvnSnapshotRepo) { mavenContent { snapshotsOnly() } } 27 | } 28 | } 29 | 30 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 31 | 32 | enableFeaturePreview("STABLE_CONFIGURATION_CACHE") 33 | 34 | rootProject.name = "build-logic" 35 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/jte/BuildConfig.kte: -------------------------------------------------------------------------------- 1 | @import common.camelCase 2 | @import gg.jte.Content 3 | @import gg.jte.support.ForSupport 4 | @import kotlinx.datetime.* 5 | @import java.net.InetAddress 6 | @import com.sun.management.OperatingSystemMXBean 7 | @import java.lang.management.ManagementFactory 8 | 9 | @param className: String 10 | @param pkg: String 11 | @param projectProps: Map 12 | @param gitCommit: Map 13 | @param catalogVersions: Map 14 | @param dependencies: List 15 | 16 | !{val sysProp = { name: String -> @`${System.getProperty(name)}` }} 17 | 18 | /* GENERATED, DO NOT EDIT MANUALLY! */ 19 | @if(pkg.isNotBlank())package ${pkg}@endif 20 | 21 | import kotlinx.datetime.* 22 | 23 | data object ${className} { 24 | 25 | const val buildTimeEpochMillis = ${Clock.System.now().toEpochMilliseconds()} 26 | 27 | val buildTimeUTC = Instant.fromEpochMilliseconds(buildTimeEpochMillis).toLocalDateTime(TimeZone.UTC) 28 | 29 | val buildTimeLocal = Instant.fromEpochMilliseconds(buildTimeEpochMillis).toLocalDateTime(TimeZone.currentSystemDefault()) 30 | 31 | object Host { 32 | const val user = "${sysProp("user.name")}" 33 | 34 | const val os = "${sysProp("os.name")} ${sysProp("os.version")}-${sysProp("os.arch")}" 35 | 36 | const val name = "${InetAddress.getLocalHost().hostName}" 37 | 38 | const val cpuCores = ${Runtime.getRuntime().availableProcessors()} 39 | 40 | const val memory = ${ManagementFactory.getPlatformMXBean(OperatingSystemMXBean::class.java).totalMemorySize} 41 | 42 | const val jdkVersion = "${sysProp("java.runtime.version")}" 43 | 44 | const val jdkVendor = "${sysProp("java.vendor")}" 45 | } 46 | 47 | @for((k,v) in projectProps) 48 | const val ${k.camelCase} = "${v}" 49 | @endfor 50 | 51 | @for((k,v) in gitCommit) 52 | const val ${k} = """${v}""" 53 | @endfor 54 | 55 | @for((k,v) in catalogVersions) 56 | const val ${k.camelCase} = "${v}" 57 | @endfor 58 | } 59 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/jte/TableConfig.kte: -------------------------------------------------------------------------------- 1 | @param items: List 2 | 3 | 4 | 5 | Lang 6 | Year 7 | 8 | @for(item in items) 9 | 10 | ${item.substringBefore(",")} 11 | ${item.substringAfterLast(",")} 12 | 13 | @else 14 | 15 | The list is empty! 16 | 17 | @endfor 18 | 19 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/kotlin/common/PatchModuleArgProvider.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import org.gradle.api.Named 4 | import org.gradle.api.file.Directory 5 | import org.gradle.api.provider.Provider 6 | import org.gradle.api.tasks.* 7 | import org.gradle.process.CommandLineArgumentProvider 8 | 9 | /** 10 | * [Configure-With-JavaModules](https://kotlinlang.org/docs/gradle-configure-project.html#configure-with-java-modules-jpms-enabled) 11 | */ 12 | internal class PatchModuleArgProvider( 13 | @get:Input val moduleName: Provider, 14 | @InputFiles @PathSensitive(PathSensitivity.RELATIVE) val kotlinClasses: Provider, 15 | ) : CommandLineArgumentProvider, Named { 16 | 17 | @Internal override fun getName() = "PatchModuleArgProvider" 18 | 19 | override fun asArguments() = 20 | listOf("--patch-module", "${moduleName.get()}=${kotlinClasses.get().asFile.absolutePath}") 21 | } 22 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/kotlin/common/Platform.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | object Platform { 4 | 5 | enum class OS(val id: String) { 6 | Linux("linux"), 7 | MacOS("macos"), 8 | Windows("windows"), 9 | Unknown("unknown") 10 | } 11 | 12 | enum class Arch(val isa: String) { 13 | X64("x64"), 14 | Arm64("aarch64"), 15 | Unknown("unknown") 16 | } 17 | 18 | data class Target( 19 | val os: OS, 20 | val version: String, 21 | val arch: Arch, 22 | ) { 23 | val id 24 | get() = "${os.id}-${arch.isa}" 25 | } 26 | 27 | val currentOS by lazy { 28 | val os = System.getProperty("os.name") 29 | when { 30 | os.startsWith("Mac", ignoreCase = true) -> OS.MacOS 31 | os.startsWith("Win", ignoreCase = true) -> OS.Windows 32 | os.startsWith("Linux", ignoreCase = true) -> OS.Linux 33 | else -> error("Unsupported OS: $os") 34 | } 35 | } 36 | 37 | val currentArch by lazy { 38 | when (val osArch = System.getProperty("os.arch")) { 39 | "x86_64", 40 | "amd64" -> Arch.X64 41 | "aarch64", 42 | "arm64" -> Arch.Arm64 43 | else -> error("Unsupported OS arch: $osArch") 44 | } 45 | } 46 | 47 | val currentTarget by lazy { 48 | Target(os = currentOS, version = System.getProperty("os.version"), arch = currentArch) 49 | } 50 | 51 | val isWin 52 | get() = currentOS == OS.Windows 53 | 54 | val isMac 55 | get() = currentOS == OS.MacOS 56 | 57 | val isLinux 58 | get() = currentOS == OS.Linux 59 | 60 | val isUnix 61 | get() = isMac || isLinux 62 | 63 | val isAarch64 64 | get() = currentArch == Arch.Arm64 65 | 66 | val isAmd64 67 | get() = currentArch == Arch.X64 68 | 69 | val isCygwin 70 | get() = isWin && System.getenv("PWD") != null && System.getenv("PWD").startsWith("/") 71 | 72 | val isMSystem 73 | get() = 74 | isWin && 75 | System.getenv("MSYSTEM") != null && 76 | (System.getenv("MSYSTEM").startsWith("MINGW") || System.getenv("MSYSTEM") == "MSYS") 77 | 78 | val isWSL 79 | get() = System.getenv("WSL_DISTRO_NAME") != null 80 | 81 | val isWSL1 82 | get() = isWSL && System.getenv("WSL_INTEROP") == null 83 | 84 | val isWSL2 85 | get() = isWSL && isWSL1.not() 86 | } 87 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/kotlin/common/SettingsExtns.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import org.gradle.api.initialization.Settings 4 | import org.gradle.api.provider.Provider 5 | 6 | fun Settings.gradleBooleanProp(name: String): Provider = 7 | providers.gradleProperty(name).map(String::toBoolean).orElse(false) 8 | 9 | val Settings.isNativeTargetEnabled: Boolean 10 | get() = gradleBooleanProp("kotlin.target.native.enabled").get() 11 | 12 | val Settings.isWinTargetEnabled: Boolean 13 | get() = gradleBooleanProp("kotlin.target.win.enabled").get() 14 | 15 | val Settings.isComposeModuleEnabled: Boolean 16 | get() = gradleBooleanProp("module.compose.enabled").get() 17 | 18 | val Settings.isBootModuleEnabled: Boolean 19 | get() = gradleBooleanProp("module.boot.enabled").get() 20 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/kotlin/dev.suresh.plugin.catalog.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.libs 2 | 3 | plugins { `version-catalog` } 4 | 5 | group = libs.versions.group.get() 6 | 7 | catalog { 8 | versionCatalog { 9 | // version("kotlin", kotlinVersion.get()) 10 | // library("kotlin-stdlib", "org.jetbrains.kotlin", "kotlin-stdlib").versionRef("kotlin") 11 | from(files(project.rootProject.file("gradle/libs.versions.toml"))) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/kotlin/dev.suresh.plugin.kotlin.benchmark.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | import kotlinx.benchmark.gradle.BenchmarkTarget 3 | import kotlinx.benchmark.gradle.KotlinJvmBenchmarkTarget 4 | 5 | /** 6 | * It's not allowed to access `libs` from pre-compiled script plugins. The `plugins {}` block in the 7 | * build-logic script is executed before the version catalogs are resolved, and as a result, you 8 | * won't have access to the version catalog libraries directly within that block. 9 | * 10 | * [For more details](https://github.com/gradle/gradle/issues/15383#issuecomment-900629378) 11 | */ 12 | plugins { 13 | id("dev.suresh.plugin.kotlin.mpp") 14 | kotlin("plugin.allopen") 15 | org.jetbrains.kotlinx.benchmark 16 | } 17 | 18 | group = libs.versions.group.get() 19 | 20 | description = "Kotlin benchmarking tests" 21 | 22 | kotlin { jvmTarget(project) } 23 | 24 | allOpen { annotation("org.openjdk.jmh.annotations.State") } 25 | 26 | benchmark { 27 | targets { 28 | register("jvm") { configureJmh() } 29 | // register("desktop") { configureJmh() } 30 | // register("macosArm64") 31 | // register("macosX64") 32 | // register("linuxX64") 33 | // register("js") 34 | // register("wasmJs") 35 | } 36 | 37 | configurations { 38 | named("main") { 39 | warmups = 5 // number of warmup iterations 40 | iterations = 5 // number of iterations 41 | iterationTime = 3 // time in seconds per iteration 42 | iterationTimeUnit = "ms" 43 | advanced("jvmForks", "definedByJmh") 44 | advanced("jsUseBridge", true) 45 | } 46 | } 47 | } 48 | 49 | // val kotlin = the() 50 | kotlin.sourceSets.commonMain { dependencies { implementation(libs.kotlinx.bench.runtime) } } 51 | 52 | tasks { 53 | // withType { } 54 | } 55 | 56 | fun BenchmarkTarget.configureJmh() { 57 | this as KotlinJvmBenchmarkTarget 58 | jmhVersion = libs.versions.jmh.get() 59 | } 60 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/kotlin/plugins/GenericPlugin.kt: -------------------------------------------------------------------------------- 1 | package plugins 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.api.initialization.Settings 6 | import org.gradle.api.plugins.PluginAware 7 | import org.gradle.kotlin.dsl.apply 8 | 9 | /** A sample Gradle plugin shows how to use as a [Project] and [Settings] plugin. */ 10 | class GenericPlugin : Plugin { 11 | override fun apply(target: PluginAware) = 12 | when (target) { 13 | is Project -> target.pluginManager.apply(GenericProjectPlugin::class) 14 | is Settings -> target.pluginManager.apply(GenericSettingsPlugin::class) 15 | else -> error("GenericPlugin cannot be applied to ${target::class}") 16 | } 17 | } 18 | 19 | class GenericProjectPlugin : Plugin { 20 | override fun apply(target: Project) = 21 | with(target) { 22 | with(pluginManager) { 23 | // println(TextColors.cyan("Applied the generic project plugin for $name")) 24 | // apply("org.jetbrains.compose") 25 | } 26 | } 27 | } 28 | 29 | class GenericSettingsPlugin : Plugin { 30 | override fun apply(target: Settings) = 31 | with(target) { 32 | // println("Applied the generic settings plugin for ${rootProject.name}") 33 | gradle.beforeProject { pluginManager.apply(GenericProjectPlugin::class) } 34 | 35 | // Configure an extension when a plugin is applied. 36 | pluginManager.withPlugin("com.gradle.develocity") {} 37 | 38 | // pluginManager.withPlugin("org.graalvm.buildtools.native") { 39 | // val graalExt = extensions.findByType(GraalVMExtension::class.java) 40 | // graalExt?.ext {} 41 | // } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/kotlin/tasks/ExecTask.kt: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import org.gradle.api.file.DirectoryProperty 4 | import org.gradle.api.file.RegularFileProperty 5 | import org.gradle.api.tasks.* 6 | import org.gradle.kotlin.dsl.* 7 | 8 | /** 9 | * A sample [JavaExec] task that executes class from runtime classpath is as follows: 10 | * ```kotlin 11 | * val execTask by registering(JavaExec::class) { 12 | * doFirst { ... } 13 | * group = "build" 14 | * mainClass = "app.Main" 15 | * classpath = sourceSets.main.get().runtimeClasspath 16 | * args = listOf(....) 17 | * outputs.dir(outDir) 18 | * doLast { } 19 | * finalizedBy(copyTask) 20 | * } 21 | * ``` 22 | */ 23 | @CacheableTask 24 | abstract class ExecTask : JavaExec() { 25 | 26 | @get:InputFile 27 | @get:PathSensitive(PathSensitivity.RELATIVE) 28 | abstract val sourceFile: RegularFileProperty 29 | 30 | @get:OutputDirectory abstract val outputDir: DirectoryProperty 31 | 32 | init { 33 | group = "build" 34 | mainClass = "app.Main" 35 | argumentProviders.add { listOf(outputDir.get().asFile.path, sourceFile.get().asFile.path) } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/kotlin/tasks/SampleTask.kt: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import org.gradle.api.* 4 | import org.gradle.api.file.* 5 | import org.gradle.api.provider.* 6 | import org.gradle.api.tasks.* 7 | import org.gradle.api.tasks.options.Option 8 | 9 | @CacheableTask 10 | abstract class SampleTask : DefaultTask() { 11 | 12 | @get:Input 13 | @get:Option(option = "greeting", description = "Greetings Task Property") 14 | abstract val greeting: Property 15 | 16 | @get:[Input Optional] 17 | abstract val versions: MapProperty 18 | 19 | @get:[InputDirectory Optional] 20 | @get:PathSensitive(PathSensitivity.RELATIVE) 21 | abstract val inputDirectory: DirectoryProperty 22 | 23 | @get:[InputFile Optional] 24 | @get:PathSensitive(PathSensitivity.RELATIVE) 25 | abstract val inputFile: RegularFileProperty 26 | 27 | @get:Internal val type = "Sample Task" 28 | 29 | init { 30 | greeting.convention("Hello Kotlin!") 31 | } 32 | 33 | @TaskAction 34 | fun execute() { 35 | println("Executing task $type. Greeting: ${greeting.get()}") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradle/build-logic/src/main/resources/exec-jar-stub.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SOURCE="${BASH_SOURCE[0]}" 4 | while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink 5 | DIR="$(cd -P "$(dirname "$SOURCE")" >/dev/null && pwd)" 6 | SOURCE="$(readlink "$SOURCE")" 7 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located 8 | done 9 | DIR="$(cd -P "$(dirname "$SOURCE")" >/dev/null && pwd)" 10 | 11 | EXEC=$(basename "$0") 12 | SELF="$DIR/$EXEC" 13 | 14 | # Make sure TMPDIR is set 15 | TMPDIR=${TMPDIR:-"$(dirname "$(mktemp -u)")"} 16 | 17 | # Exec the java process 18 | exec java "$JAVA_OPTS" -jar "$SELF" "$@" 19 | exit 1 20 | -------------------------------------------------------------------------------- /gradle/gradle-daemon-jvm.properties: -------------------------------------------------------------------------------- 1 | #This file is generated by updateDaemonJvm 2 | toolchainVersion=21 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /meta/README.md: -------------------------------------------------------------------------------- 1 | ### Kotlin Compiler Plugin and Symbol Processors 2 | 3 | - [Hello Kotlin Compiler Plugin](https://betterprogramming.pub/say-hello-to-kotlin-compiler-plugin-f4e857be9a1) 4 | - [Kotlin Compiler Plugin Example](https://github.com/Foso/KotlinCompilerPluginExample) 5 | -------------------------------------------------------------------------------- /meta/compiler/plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | dev.suresh.plugin.kotlin.jvm 3 | dev.suresh.plugin.publishing 4 | } 5 | 6 | description = "Kotlin Compiler Plugin" 7 | 8 | dependencies { 9 | implementation(projects.shared) 10 | compileOnly(kotlin("compiler-embeddable")) 11 | testImplementation(kotlin("compiler-embeddable")) 12 | // testImplementation("dev.zacsweers.kctfork:ksp:+") 13 | } 14 | -------------------------------------------------------------------------------- /meta/compiler/plugin/src/test/kotlin/PluginTest.kt: -------------------------------------------------------------------------------- 1 | import kotlin.test.Test 2 | 3 | class PluginTest { 4 | 5 | @Test fun test() {} 6 | } 7 | -------------------------------------------------------------------------------- /meta/ksp/processor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.jvmTarget 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.mpp 5 | dev.suresh.plugin.publishing 6 | } 7 | 8 | description = "Kotlin KSP Processor" 9 | 10 | kotlin { 11 | jvmTarget(project) 12 | 13 | sourceSets { 14 | commonMain { dependencies { implementation(projects.shared) } } 15 | 16 | jvmMain { 17 | dependencies { 18 | implementation(libs.kotlin.ksp.api) 19 | // implementation("com.squareup:kotlinpoet-ksp") 20 | } 21 | 22 | kotlin.srcDir("src/main/kotlin") 23 | resources.srcDir("src/main/resources") 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /meta/ksp/processor/src/main/kotlin/TestProcessor.kt: -------------------------------------------------------------------------------- 1 | import com.google.auto.service.AutoService 2 | import com.google.devtools.ksp.processing.* 3 | import com.google.devtools.ksp.symbol.KSAnnotated 4 | import com.google.devtools.ksp.symbol.KSClassDeclaration 5 | import com.google.devtools.ksp.symbol.KSNode 6 | import com.google.devtools.ksp.visitor.KSTopDownVisitor 7 | import java.io.OutputStreamWriter 8 | 9 | @AutoService(SymbolProcessorProvider::class) 10 | class TestProcessorProvider : SymbolProcessorProvider { 11 | override fun create(environment: SymbolProcessorEnvironment) = 12 | TestProcessor(environment.codeGenerator, environment.logger) 13 | } 14 | 15 | class TestProcessor(val codeGenerator: CodeGenerator, val logger: KSPLogger) : SymbolProcessor { 16 | private var invoked = false 17 | 18 | override fun process(resolver: Resolver): List { 19 | val allFiles = resolver.getAllFiles().map { it.fileName } 20 | logger.warn(allFiles.toList().toString()) 21 | if (invoked) { 22 | return emptyList() 23 | } 24 | invoked = true 25 | 26 | codeGenerator 27 | .createNewFile( 28 | dependencies = Dependencies.ALL_FILES, 29 | packageName = "", 30 | fileName = "Foo", 31 | extensionName = "kt") 32 | .use { output -> 33 | OutputStreamWriter(output).use { writer -> 34 | writer.write("package com.example\n\n") 35 | writer.write("class Foo {\n") 36 | 37 | val visitor = ClassVisitor() 38 | resolver.getAllFiles().forEach { it.accept(visitor, writer) } 39 | writer.write("}\n") 40 | } 41 | } 42 | return emptyList() 43 | } 44 | } 45 | 46 | class ClassVisitor : KSTopDownVisitor() { 47 | override fun defaultHandler(node: KSNode, data: OutputStreamWriter) {} 48 | 49 | override fun visitClassDeclaration( 50 | classDeclaration: KSClassDeclaration, 51 | data: OutputStreamWriter 52 | ) { 53 | super.visitClassDeclaration(classDeclaration, data) 54 | val symbolName = classDeclaration.simpleName.asString().lowercase() 55 | data.write(" val $symbolName = true\n") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /meta/scripts/JavaSingleFile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env java --enable-preview --source 25 2 | 3 | void main() { 4 | println("Hello Java " + System.getProperty("java.version")); 5 | } -------------------------------------------------------------------------------- /meta/scripts/Mach-O-Bin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | C_CODE=$( 6 | cat < 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | int main() { 15 | int tty_fd = open("/dev/tty", O_EVTONLY | O_NONBLOCK); 16 | if (tty_fd == -1) { 17 | fprintf(stderr, "Opening $(/dev/tty) failed (%d): %s\n", errno, strerror(errno)); 18 | return 1; 19 | } 20 | 21 | struct winsize ws; 22 | int result = ioctl(tty_fd, TIOCGWINSZ, &ws); 23 | close(tty_fd); 24 | 25 | if (result == -1) { 26 | fprintf(stderr, "Getting the size failed (%d): %s\n", errno, strerror(errno)); 27 | return 1; 28 | } 29 | 30 | fprintf(stdout, "%d\n%d\n", ws.ws_col, ws.ws_row); 31 | return 0; 32 | } 33 | EOF 34 | ) 35 | 36 | # Create a temporary file to store the C code 37 | TMP_FILE=$(mktemp temp.XXXXXXXXXX) 38 | TERM_SIZE_C="$TMP_FILE.c" 39 | TERM_SIZE_BIN="term-size" 40 | 41 | mv "$TMP_FILE" "$TERM_SIZE_C" 42 | echo "$C_CODE" >"$TERM_SIZE_C" 43 | rm -f "$TERM_SIZE_BIN" 44 | 45 | # Create Mach-O universal binary with 2 architectures: x86_64 & arm64 46 | clang -Ofast -Wall -Wextra -pedantic -std=c99 -arch arm64 -arch x86_64 -o "$TERM_SIZE_BIN" "$TERM_SIZE_C" 47 | 48 | # Clean up the temporary file 49 | rm "$TERM_SIZE_C" 50 | -------------------------------------------------------------------------------- /meta/scripts/jextract/c/ioctl.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include -------------------------------------------------------------------------------- /meta/scripts/jextract/c/windows.h: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /meta/scripts/jextract/gen/set-package.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Find OS type 6 | case "$OSTYPE" in 7 | darwin*) 8 | os=macos 9 | ;; 10 | msys*) 11 | os=windows 12 | ;; 13 | linux*) 14 | os=linux 15 | ;; 16 | *) 17 | echo "Unsupported OS: $OSTYPE" 18 | exit 1 19 | ;; 20 | esac 21 | 22 | # Find CPU architecture 23 | case "$(uname -m)" in 24 | amd64 | x86_64) 25 | arch=x64 26 | ;; 27 | aarch64 | arm64) 28 | arch=aarch64 29 | ;; 30 | *) 31 | echo "Unsupported arch: $(uname -m)" 32 | exit 1 33 | ;; 34 | esac 35 | 36 | PACKAGE_NAME="dev.suresh.${os}.${arch}" 37 | echo "PACKAGE_NAME=${PACKAGE_NAME}" >> $GITHUB_ENV -------------------------------------------------------------------------------- /meta/scripts/jextract/gen/windows.sh: -------------------------------------------------------------------------------- 1 | jextract \ 2 | --output jextract/src/main/java \ 3 | --target-package "${PACKAGE_NAME}" \ 4 | --include-function GetConsoleMode \ 5 | --include-function SetConsoleMode \ 6 | --include-constant ENABLE_ECHO_INPUT \ 7 | --include-constant ENABLE_PROCESSED_INPUT \ 8 | --include-constant ENABLE_LINE_INPUT \ 9 | --include-constant ENABLE_PROCESSED_OUTPUT \ 10 | --include-typedef HANDLE \ 11 | --include-function GetConsoleScreenBufferInfo \ 12 | --include-function GetCurrentProcess \ 13 | --include-function DuplicateHandle \ 14 | --include-constant DUPLICATE_SAME_ACCESS \ 15 | --include-function GetStdHandle \ 16 | --include-constant STD_OUTPUT_HANDLE \ 17 | jextract/gen/c/windows.h -------------------------------------------------------------------------------- /meta/scripts/jextract/jextract.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # Find OS type 6 | case "$OSTYPE" in 7 | darwin*) 8 | os=macos 9 | ;; 10 | msys*) 11 | os=windows 12 | ;; 13 | linux*) 14 | os=linux 15 | ;; 16 | *) 17 | echo "Unsupported OS: $OSTYPE" 18 | exit 1 19 | ;; 20 | esac 21 | 22 | echo "Using OS: $os" 23 | download_url=$(curl -sSL "https://jdk.java.net/jextract/" | grep -m1 -Eioh "https:.*$os-.*.(tar.gz|zip)") 24 | download_file="${download_url##*/}" 25 | 26 | install_dir=${1:-"$HOME/install/openjdk"} 27 | jextract_dir="jextract" 28 | 29 | # Download the jextract EA build 30 | pushd "$install_dir" >/dev/null 31 | echo "Downloading $download_url ..." 32 | curl --progress-bar --request GET -L --url "$download_url" --output "$download_file" 33 | 34 | # Extract the jextract and cleanup old/downloaded files 35 | rm -rf "$jextract_dir" && mkdir -p "$jextract_dir" 36 | tar xvzf "$download_file" --strip-components=1 -C "$jextract_dir" 37 | rm -f "$download_file" 38 | 39 | if [ "$os" == "macos" ]; then 40 | echo "Removing the quarantine attribute..." 41 | sudo xattr -r -d com.apple.quarantine "$jextract_dir" 42 | fi 43 | 44 | printf "\nOpenJDK jextract version: " 45 | $jextract_dir/bin/jextract --version 46 | 47 | popd >/dev/null 48 | -------------------------------------------------------------------------------- /meta/scripts/jrtserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env java --enable-preview --source 25 2 | 3 | import com.sun.net.httpserver.SimpleFileServer; 4 | import com.sun.net.httpserver.SimpleFileServer.OutputLevel; 5 | 6 | import java.net.InetSocketAddress; 7 | import java.net.URI; 8 | import java.nio.file.FileSystems; 9 | 10 | void main() { 11 | var jrtFS = FileSystems.getFileSystem(URI.create("jrt:/")); 12 | // Paths.get(URI.create("jrt:/")) 13 | var modules = jrtFS.getPath("/modules"); 14 | var fileServer = SimpleFileServer.createFileServer(new InetSocketAddress(8080), modules, OutputLevel.VERBOSE); 15 | println("Visit http://localhost:" + fileServer.getAddress().getPort() + "/ to see the file server!%n"); 16 | fileServer.start(); 17 | } 18 | -------------------------------------------------------------------------------- /meta/scripts/mkdocs/mkdocs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | # Make sure mkdocs plugin is already installed. 6 | # pip3 install mkdocs-macros-plugin 7 | # pip3 install mkdocs-minify-plugin 8 | 9 | project_dir="$(dirname "$(realpath "$0")")"/../.. 10 | project_name=kotlin-mpp-playground 11 | build_dir="build/dokka" 12 | 13 | pushd "$project_dir" >/dev/null 14 | 15 | echo "Building the Dokka documentation..." 16 | ./gradlew dokkaGfm 17 | 18 | cp docs/mkdocs/mkdocs.yml ${build_dir} 19 | cp ./*.md "${build_dir}/${project_name}" 20 | cp docs/*.md "${build_dir}/${project_name}" 21 | cp -R docs/logo "${build_dir}/${project_name}" 22 | mv ${build_dir}/${project_name}/{README.md,Overview.md} 23 | sed -i "" 's/"docs\/logo/"..\/logo/g' "${build_dir}/${project_name}/Overview.md" 24 | 25 | pushd ${build_dir} >/dev/null 26 | mkdocs build 27 | popd >/dev/null 28 | 29 | echo "Copying API doc" 30 | rm -rf docs/apidoc 31 | cp -R "${build_dir}/docs" docs/apidoc 32 | 33 | echo "Committing changes" 34 | git add docs/apidoc 35 | git commit -m "doc: site update" -- docs/apidoc || echo "Nothing to commit" 36 | 37 | popd >/dev/null 38 | -------------------------------------------------------------------------------- /meta/scripts/openjdk-ea.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # set -u won't work for sdkman 4 | set -e 5 | 6 | jdk_version=${1:-25} 7 | 8 | # Find OS type 9 | case "$OSTYPE" in 10 | darwin*) 11 | os=macos 12 | ;; 13 | msys*) 14 | os=windows 15 | ;; 16 | linux*) 17 | os=linux 18 | ;; 19 | *) 20 | echo "Unsupported OS: $OSTYPE" 21 | exit 1 22 | ;; 23 | esac 24 | 25 | # Find CPU architecture 26 | case "$(uname -m)" in 27 | amd64 | x86_64) 28 | arch=x64 29 | ;; 30 | aarch64 | arm64) 31 | arch=aarch64 32 | ;; 33 | *) 34 | echo "Unsupported arch: $(uname -m)" 35 | exit 1 36 | ;; 37 | esac 38 | 39 | echo "Using OS: $os-$arch" 40 | download_url=$(curl -sSL "https://jdk.java.net/$jdk_version" | grep -m1 -Eioh "https:.*($os-$arch).*.(tar.gz|zip)") 41 | openjdk_file="${download_url##*/}" 42 | 43 | # Download the OpenJDK EA build 44 | pushd "$HOME/install/openjdk" >/dev/null 45 | echo "Openjdk-$jdk_version file: $openjdk_file" 46 | echo "Downloading $download_url ..." 47 | curl --progress-bar --request GET -L --url "$download_url" --output "$openjdk_file" 48 | 49 | # Extract the OpenJDK and cleanup old/downloaded files 50 | jdk_dir=$(tar -tzf "$openjdk_file" | head -3 | tail -1 | cut -f2 -d"/") 51 | rm -rf "$jdk_dir" && tar -xvzf "$openjdk_file" && rm -f "$openjdk_file" 52 | if [ "$os" == "darwin" ]; then 53 | echo "Removing the quarantine attribute..." 54 | sudo xattr -r -d com.apple.quarantine "$jdk_dir" 55 | fi 56 | 57 | # Install OpenJDK using sdkman 58 | sdkman_id="openjdk-ea" 59 | echo "Installing $jdk_dir ..." 60 | source "$HOME/.sdkman/bin/sdkman-init.sh" 61 | sdk rm java "$sdkman_id" 62 | sdk i java "$sdkman_id" "$jdk_dir/Contents/Home" 63 | popd >/dev/null 64 | 65 | # Set OpenJDK as default JDK in the current shell 66 | sdk u java "$sdkman_id" 67 | java --version 68 | -------------------------------------------------------------------------------- /meta/scripts/otel/OTel-Config.md: -------------------------------------------------------------------------------- 1 | ## OpenTelemetry Configurations 2 | 3 | - [OTEL Env Variables](https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix.md#environment-variables) 4 | - [OTEL Configuration & Schema](https://github.com/open-telemetry/opentelemetry-configuration/tree/main) 5 | - [OTEL Config Spec Versions](https://opentelemetry.io/docs/specs/) 6 | - [OTEL Java Contrib](https://github.com/open-telemetry/opentelemetry-java-contrib/) -------------------------------------------------------------------------------- /meta/scripts/otel/grafana/datasource.yaml: -------------------------------------------------------------------------------- 1 | # config file version 2 | apiVersion: 1 3 | 4 | datasources: 5 | - name: ClickHouse-official 6 | type: grafana-clickhouse-datasource 7 | jsonData: 8 | defaultDatabase: otel 9 | port: 9000 10 | server: clickhouse 11 | protocol: native 12 | username: 13 | tlsSkipVerify: true 14 | secureJsonData: 15 | password: 16 | 17 | - name: ClickHouse-vertamedia 18 | type: vertamedia-clickhouse-datasource 19 | url: http://clickhouse:8123 -------------------------------------------------------------------------------- /meta/scripts/otel/grafana/grafana.ini: -------------------------------------------------------------------------------- 1 | [auth] 2 | disable_login_form = true 3 | 4 | [auth.anonymous] 5 | enabled = true 6 | org_name = Main Org. 7 | org_role = Admin -------------------------------------------------------------------------------- /meta/scripts/otel/otel-collector-config.yml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | grpc: 5 | endpoint: 0.0.0.0:4317 6 | http: 7 | endpoint: 0.0.0.0:4318 8 | 9 | hostmetrics: 10 | scrapers: 11 | memory: 12 | 13 | prometheus: 14 | config: 15 | scrape_configs: 16 | - job_name: 'node_exporter' 17 | static_configs: 18 | - targets: [ 'node_exporter:9100' ] # Default node_exporter port 19 | scrape_interval: 10s 20 | 21 | processors: 22 | batch: 23 | send_batch_size: 100000 24 | timeout: 5s 25 | memory_limiter: 26 | check_interval: 2s 27 | limit_mib: 1800 28 | spike_limit_mib: 500 29 | resourcedetection: 30 | detectors: [ system ] 31 | resource: 32 | attributes: 33 | - key: service.name 34 | value: "serviceName" 35 | action: upsert 36 | 37 | exporters: 38 | debug: 39 | verbosity: detailed 40 | 41 | clickhouse: 42 | endpoint: tcp://clickhouse:9000 43 | database: otel 44 | ttl: 12h 45 | create_schema: true 46 | logs_table_name: otel_logs 47 | traces_table_name: otel_traces 48 | timeout: 10s 49 | sending_queue: 50 | queue_size: 100 51 | retry_on_failure: 52 | enabled: true 53 | initial_interval: 5s 54 | max_interval: 30s 55 | max_elapsed_time: 300s 56 | 57 | # otlphttp/traces: 58 | # endpoint: http://localhost:4418 59 | # tls: 60 | # insecure: true 61 | # debug/logs: 62 | # verbosity: detailed 63 | 64 | extensions: 65 | health_check: 66 | pprof: 67 | zpages: 68 | 69 | service: 70 | extensions: [ pprof, zpages, health_check ] 71 | telemetry: 72 | traces: 73 | metrics: 74 | logs: 75 | pipelines: 76 | logs: 77 | receivers: [ otlp ] 78 | processors: [ memory_limiter, resourcedetection, resource, batch ] 79 | exporters: [ clickhouse ] 80 | traces: 81 | receivers: [ otlp ] 82 | processors: [ memory_limiter, resourcedetection, resource, batch ] 83 | exporters: [ clickhouse ] 84 | metrics: 85 | receivers: [ otlp ] 86 | processors: [ memory_limiter, resourcedetection, resource, batch ] 87 | exporters: [ clickhouse ] 88 | # exporters: [otlphttp/metrics,debug/metrics] -------------------------------------------------------------------------------- /meta/scripts/otel/otel-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | # docker compose -f otel-compose.yml up 4 | services: 5 | clickhouse: 6 | image: clickhouse/clickhouse-server:latest-alpine 7 | restart: always 8 | volumes: 9 | - clickhouse-data:/var/lib/clickhouse 10 | - clickhouse-log:/var/log/clickhouse-server 11 | ports: 12 | - "8123:8123" 13 | - "9000:9000" 14 | - "9009:9009" 15 | ulimits: 16 | nproc: 65535 17 | memlock: 18 | soft: -1 19 | hard: -1 20 | nofile: 21 | soft: 262144 22 | hard: 262144 23 | deploy: 24 | resources: 25 | limits: 26 | memory: 4g 27 | networks: 28 | - otel-clickhouse 29 | 30 | node_exporter: 31 | image: prom/node-exporter:latest 32 | container_name: node_exporter 33 | command: 34 | - '--path.rootfs=/host' 35 | network_mode: host 36 | pid: host 37 | restart: unless-stopped 38 | volumes: 39 | - '/:/host:ro,rslave' 40 | ports: 41 | - '9100:9100' 42 | 43 | otel-collector: 44 | image: otel/opentelemetry-collector-contrib:latest 45 | network_mode: host 46 | volumes: 47 | - ./otel-collector-config.yml:/config.yaml 48 | command: 49 | - --config=file:/config.yaml 50 | 51 | grafana: 52 | image: grafana/grafana:latest 53 | volumes: 54 | - ./grafana/grafana.ini:/etc/grafana/grafana.ini 55 | - ./grafana/datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yaml 56 | environment: 57 | GF_INSTALL_PLUGINS: grafana-clickhouse-datasource,vertamedia-clickhouse-datasource 58 | GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: vertamedia-clickhouse-datasource 59 | ports: 60 | - "${GRAFANA_PORT:-3000}:3000" 61 | restart: always 62 | networks: 63 | - otel-clickhouse 64 | deploy: 65 | resources: 66 | limits: 67 | memory: 2g 68 | depends_on: 69 | - clickhouse 70 | 71 | volumes: 72 | clickhouse-data: 73 | clickhouse-log: 74 | 75 | networks: 76 | otel-clickhouse: -------------------------------------------------------------------------------- /meta/scripts/otel/systemd/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Create `/etc/systemd/system/docker-compose@.service` 4 | Copy `docker-compose.yml` to `/app/myservice/docker-compose.yml` 5 | 6 | ```bash 7 | $ systemctl start docker-compose@myservice 8 | ``` 9 | 10 | Copy `docker-cleanup.timer` to `/etc/systemd/system/docker-cleanup.timer` 11 | Copy `docker-cleanup.service` to `/etc/systemd/system/docker-cleanup.service` 12 | 13 | ```bash 14 | $ systemctl enable docker-cleanup.timer 15 | ``` 16 | 17 | Just add the following line to the /etc/docker/daemon.json: 18 | 19 | { 20 | ... 21 | "log-driver": "journald", 22 | ... 23 | } 24 | And restart your docker service. -------------------------------------------------------------------------------- /meta/scripts/otel/systemd/docker-cleanup.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Docker cleanup 3 | Requires=docker.service 4 | After=docker.service 5 | 6 | [Service] 7 | Type=oneshot 8 | WorkingDirectory=/tmp 9 | User=root 10 | Group=root 11 | ExecStart=/usr/bin/docker system prune -af 12 | 13 | [Install] 14 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /meta/scripts/otel/systemd/docker-cleanup.timer: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Docker cleanup timer 3 | 4 | [Timer] 5 | OnUnitInactiveSec=12h 6 | 7 | [Install] 8 | WantedBy=timers.target -------------------------------------------------------------------------------- /meta/scripts/otel/systemd/docker-compose@.service: -------------------------------------------------------------------------------- 1 | # /etc/systemd/system/compose-app.service 2 | # systemctl enable compose-app 3 | # Also, https://github.com/moby/moby/blob/master/contrib/init/systemd/docker.service 4 | 5 | [Unit] 6 | Description=%i docker compose service 7 | Requires=docker.service 8 | PartOf=docker.service 9 | After=docker.service 10 | StartLimitIntervalSec=60 11 | StartLimitBurst=3 12 | 13 | [Service] 14 | Type=oneshot 15 | RemainAfterExit=yes 16 | WorkingDirectory=/app/%i 17 | ExecStart=/usr/local/bin/docker-compose up -d --remove-orphans 18 | ExecStop=/usr/local/bin/docker-compose down 19 | TimeoutStartSec=0 20 | Restart=on-failure 21 | 22 | [Install] 23 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /meta/scripts/quine.jshell: -------------------------------------------------------------------------------- 1 | var s="var s=%1$s%2$s%1$s;printf(s,(char)34,s);/exit";printf(s,(char)34,s);/exit 2 | -------------------------------------------------------------------------------- /meta/scripts/rsocket.http: -------------------------------------------------------------------------------- 1 | ### RSocket demo app 2 | STREAM /rsocket 3 | Host: wss://demo.rsocket.io/rsocket 4 | Content-Type: application/json 5 | Metadata-Type: application/json 6 | Setup-Data: hello 7 | 8 | ### RSocket response 9 | RSOCKET / 10 | Host: localhost:7000 11 | Content-Type: text/plain 12 | 13 | Hello 14 | 15 | ### Request/Stream 16 | STREAM / 17 | Host: localhost:7000 18 | Content-Type: text/plain 19 | 20 | Hello 21 | 22 | ### FireAndForget 23 | FNF / 24 | Host: localhost:7000 25 | Content-Type: text/plain 26 | 27 | Hello 28 | 29 | ### Spring Boot RSocket hello 30 | RSOCKET hello 31 | Host: localhost:7000 32 | Content-Type: text/plain 33 | 34 | Hello 35 | -------------------------------------------------------------------------------- /meta/scripts/script.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S kotlin -Xplugin=/opt/homebrew/opt/kotlin/libexec/lib/kotlinx-serialization-compiler-plugin.jar 2 | // @file:Repository("https://maven.google.com") 3 | @file:DependsOn("io.ktor:ktor-client-core:3.1.3") 4 | @file:DependsOn("io.ktor:ktor-client-cio:3.1.3") 5 | @file:DependsOn("io.ktor:ktor-client-java:3.1.3") 6 | @file:DependsOn("io.ktor:ktor-client-auth:3.1.3") 7 | @file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") 8 | @file:DependsOn("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") 9 | @file:DependsOn("org.jetbrains.kotlinx:kotlinx-datetime:0.6.2") 10 | @file:DependsOn("org.slf4j:slf4j-simple:2.1.0-alpha1") 11 | @file:DependsOn("com.microsoft.playwright:playwright:1.52.0") 12 | // @file:Import("common.main.kts") 13 | 14 | @file:CompilerOptions("-jvm-target", "1.8") 15 | 16 | import com.microsoft.playwright.* 17 | import kotlinx.coroutines.* 18 | import kotlinx.serialization.* 19 | import kotlinx.serialization.json.* 20 | 21 | @Serializable data class Lang(val name: String, val version: String) 22 | 23 | val arg = args.firstOrNull() ?: "Kotlin" 24 | 25 | println("Hello $arg!") 26 | 27 | val serialized = Json.encodeToString(Lang("Kotlin", KotlinVersion.CURRENT.toString())) 28 | 29 | println(serialized) 30 | 31 | val javaVer: String = System.getProperty("java.version") 32 | val deserialized = Json.decodeFromString("""{"name" : "Java", "version": "$javaVer"}""") 33 | 34 | println(deserialized) 35 | 36 | runBlocking { 37 | delay(1000) 38 | println("Done!") 39 | } 40 | 41 | fun recordBrowser() { 42 | CLI.main(arrayOf("codegen", "-o", "record.java")) 43 | } 44 | 45 | fun playbackBrowser() { 46 | Playwright.create().use { playwright -> 47 | val browser = playwright.chromium().launch(BrowserType.LaunchOptions().setHeadless(true)) 48 | 49 | browser.newContext().newPage().apply { 50 | navigate("https://www.google.com/search?q=kotlinlang&oq=kotlinlang") 51 | click("text=Kotlin Programming Language") 52 | assert(url() == "https://kotlinlang.org/") 53 | click("#kotlin-code-example-simplest div div div div") 54 | click("text=[X]") 55 | close() 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | 3 | pluginManagement { 4 | repositories { 5 | mavenCentral() 6 | gradlePluginPortal() 7 | } 8 | includeBuild("gradle/build-logic") 9 | } 10 | 11 | plugins { id("dev.suresh.plugin.repos") } 12 | 13 | rootProject.name = "kotlin-mpp-playground" 14 | 15 | include(":shared") 16 | 17 | include(":dep-mgmt:bom") 18 | 19 | include(":dep-mgmt:catalog") 20 | 21 | include(":backend:jvm") 22 | 23 | include(":backend:security") 24 | 25 | include(":backend:data") 26 | 27 | include(":backend:profiling") 28 | 29 | include(":backend:agent:jfr") 30 | 31 | include(":backend:agent:otel") 32 | 33 | include(":web") 34 | 35 | include(":benchmark") 36 | 37 | include(":meta:ksp:processor") 38 | 39 | include(":meta:compiler:plugin") 40 | 41 | if (isNativeTargetEnabled) { 42 | include(":backend:native") 43 | } 44 | 45 | if (isComposeModuleEnabled) { 46 | include(":compose:cmp") 47 | // include(":compose:cli") 48 | // include(":compose:html") 49 | } 50 | 51 | if (isBootModuleEnabled) { 52 | include(":backend:boot") 53 | } 54 | 55 | // includeBuild("misc/build") { 56 | // dependencySubstitution { 57 | // substitute(module("dev.suresh:misc-build")).using(project(":")) 58 | // } 59 | // } 60 | -------------------------------------------------------------------------------- /shared/README.md: -------------------------------------------------------------------------------- 1 | ### Run shared JVM app 2 | 3 | ```bash 4 | $ ./gradlew :shared:run 5 | # or 6 | $ ./gradlew build 7 | $ java --enable-preview \ 8 | --add-modules=ALL-SYSTEM \ 9 | -jar shared/build/libs/common-*-all.jar 10 | ``` 11 | -------------------------------------------------------------------------------- /shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | import common.versionCatalogMapOf 4 | 5 | plugins { 6 | dev.suresh.plugin.kotlin.mpp 7 | dev.suresh.plugin.publishing 8 | `binary-compatibility-validator` 9 | com.jakewharton.`kmp-missing-targets` 10 | // alias(libs.plugins.mappie) 11 | } 12 | 13 | description = "Shared common module for all projects" 14 | 15 | buildConfig { 16 | enabled = true 17 | projectName = rootProject.name 18 | projectVersion = project.version.toString() 19 | projectDesc = rootProject.description 20 | gitCommit = semver.commits.get().first() 21 | catalogVersions = project.versionCatalogMapOf() 22 | } 23 | 24 | kotlinMissingTargets {} 25 | 26 | dependencies { 27 | // commonMainApi(libs.bundles.ajalt) 28 | // commonMainApi(libs.evas) 29 | // commonMainApi(libs.uri.kmp) 30 | // commonMainApi(libs.kotlin.bignum) 31 | // commonMainApi(libs.kotlin.bignum.serialization) 32 | // commonMainApi(libs.intellij.markdown) 33 | // commonMainApi(libs.kotlin.codepoints.deluxe) 34 | // commonMainApi(libs.kotlin.cryptography.core) 35 | // commonMainApi(libs.kotlin.cryptography.random) 36 | // commonMainApi(libs.multiplatform.settings.coroutines) 37 | // commonMainApi(libs.mappie.api) 38 | // commonMainApi(libs.bundles.json.extra) 39 | // commonMainApi(libs.urlencoder) 40 | // commonMainApi(libs.parsus) 41 | // jvmMainApi(libs.kotlin.reflect) 42 | // jvmMainApi(libs.logback.classic) 43 | } 44 | 45 | // tasks.buildConfig { outputs.upToDateWhen { false } } 46 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/dev/suresh/flow/Timer.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.flow 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.flow 5 | import kotlinx.datetime.Clock 6 | import kotlinx.datetime.TimeZone 7 | import kotlinx.datetime.toLocalDateTime 8 | 9 | // import androidx.compose.runtime.* 10 | // import app.cash.molecule.RecompositionMode 11 | // import app.cash.molecule.moleculeFlow 12 | // 13 | // @Composable 14 | // fun timer(tz: TimeZone): LocalDateTime { 15 | // var time by remember { mutableStateOf(currentTime(tz)) } 16 | // LaunchedEffect(Unit) { 17 | // while (true) { 18 | // delay(1000) 19 | // time = currentTime(tz) 20 | // } 21 | // } 22 | // return time 23 | // } 24 | // 25 | // fun timerComposeFlow(tz: TimeZone = TimeZone.currentSystemDefault()) = 26 | // moleculeFlow(RecompositionMode.Immediate) { timer(tz) } 27 | 28 | private fun currentTime(tz: TimeZone) = Clock.System.now().toLocalDateTime(tz) 29 | 30 | fun timerComposeFlow(tz: TimeZone = TimeZone.currentSystemDefault()) = flow { 31 | while (true) { 32 | delay(1000) 33 | emit(currentTime(tz)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/dev/suresh/http/Config.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.http 2 | 3 | import kotlin.time.Duration 4 | import kotlin.time.Duration.Companion.seconds 5 | import kotlinx.serialization.Serializable 6 | 7 | data class Timeout(val connection: Duration, val read: Duration, val write: Duration) { 8 | companion object { 9 | val DEFAULT = Timeout(connection = 5.seconds, read = 5.seconds, write = 5.seconds) 10 | } 11 | } 12 | 13 | data class Retry(val attempts: Int, val maxDelay: Duration) { 14 | companion object { 15 | val DEFAULT = Retry(attempts = 2, maxDelay = 5.seconds) 16 | } 17 | } 18 | 19 | @Serializable 20 | data class ErrorStatus(val code: Int, val message: String, val details: String? = null) 21 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/dev/suresh/http/CurlLogging.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.http 2 | 3 | import io.github.oshai.kotlinlogging.KLogger 4 | import io.ktor.client.plugins.api.ClientPlugin 5 | import io.ktor.client.plugins.api.createClientPlugin 6 | 7 | class CurlLoggingConfig { 8 | var enabled: Boolean = true 9 | var logger: KLogger? = null 10 | } 11 | 12 | val CurlLogging: ClientPlugin = 13 | createClientPlugin(name = "CurlLogging", createConfiguration = ::CurlLoggingConfig) { 14 | if (pluginConfig.enabled) { 15 | onRequest { req, _ -> 16 | // pluginConfig.logger.is 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/dev/suresh/lang/Features.kt: -------------------------------------------------------------------------------- 1 | import dev.suresh.log 2 | import kotlin.io.encoding.Base64 3 | import kotlin.jvm.JvmInline 4 | import kotlin.time.Duration.Companion.microseconds 5 | import kotlin.time.TimeSource 6 | import kotlin.uuid.Uuid 7 | 8 | enum class Planet(val moon: Int) { 9 | MERCURY(0), 10 | VENUS(0), 11 | EARTH(1), 12 | MARS(2), 13 | JUPITER(5), 14 | SATURN(66), 15 | URANUS(62), 16 | NEPTUNE(13) 17 | } 18 | 19 | data object MyService 20 | 21 | @JvmInline 22 | value class Person(private val name: String) { 23 | init { 24 | check(name.isNotBlank()) { "Name should not be empty" } 25 | } 26 | 27 | constructor(firstName: String, lastName: String) : this("$firstName $lastName") { 28 | check(firstName.isNotBlank()) { "First name should not be empty" } 29 | check(lastName.isNotBlank()) { "Last name should not be empty" } 30 | } 31 | } 32 | 33 | fun langFeatures() { 34 | log.info { Planet.entries.filter { it.moon == 1 } } 35 | // MyService::class.createInstance() - Throws error 36 | log.info { MyService } 37 | 38 | log.info { Person("Foo") } 39 | log.info { Person("Foo", "Bar") } 40 | } 41 | 42 | fun stdlibFeatures() { 43 | 44 | val timeSource = TimeSource.Monotonic 45 | val m1 = timeSource.markNow() 46 | 47 | val m2 = timeSource.markNow() 48 | log.info { m2.elapsedNow() + 2.microseconds } 49 | log.info { m1 + 2.microseconds } 50 | log.info { m2 - m1 } 51 | 52 | val regex = """\b(?[A-Za-z\s]+),\s(?[A-Z]{2}),\s(?[0-9]{5})\b""".toRegex() 53 | val match = regex.find("San Jose, CA, 95124")!! 54 | log.info { match.groups } 55 | log.info { match.groups["city"]?.value } 56 | log.info { match.groups["state"]?.value } 57 | log.info { match.groups["areacode"]?.value } 58 | 59 | val hexFormat = HexFormat { 60 | upperCase = true 61 | bytes { 62 | bytePrefix = "0x" 63 | byteSeparator = ":" 64 | bytesPerLine = 4 65 | bytesPerGroup = 2 66 | groupSeparator = " " 67 | } 68 | 69 | number { 70 | prefix = "0x" 71 | removeLeadingZeros = true 72 | } 73 | } 74 | 75 | log.info { 123232.toHexString(hexFormat) } 76 | val hex = "Kotlin ${KotlinVersion.CURRENT}".encodeToByteArray().toHexString(hexFormat) 77 | log.info { hex } 78 | log.info { hex.hexToByteArray(hexFormat).decodeToString() } 79 | 80 | log.info { Base64.Mime.encode("Hello Kotlin!".encodeToByteArray()) } 81 | log.info { "UUID: ${ Uuid.random()}" } 82 | } 83 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/dev/suresh/lang/HackersDelight.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.lang 2 | 3 | const val HAS_SEMI = 0x3B_3B_3B_3B_3B_3B_3B_3BL 4 | 5 | /** 6 | * Has a zero byte in a long, copied straight from 7 | * [Hackers Delight](https://doc.lagout.org/security/Hackers%20Delight.pdf) 8 | * 9 | * Unlike in Java, hexadecimal integer literals in Kotlin can't represent negative values. So we 10 | * have to use [ULong] for literal values greater than [Long.MAX_VALUE] 11 | * 12 | * Eg: 13 | * ```kotlin 14 | * val long = 0x80_80_80_80_80_80_80_80u.toLong() 15 | * println(long.toString(16)) 16 | * println(long.toString(2)) 17 | * println(long.toULong().toString(16)) 18 | * println(long.toULong().toString(2)) 19 | * ``` 20 | * 21 | * Also see - [Stanford BitHacks](https://graphics.stanford.edu/~seander/bithacks.html) 22 | */ 23 | inline val Long.hasZeroByte 24 | get() = (this - 0x0101010101010101L) and this.inv() and 0x80_80_80_80_80_80_80_80u.toLong() != 0L 25 | 26 | /** Check if a semi-column(0x3B) present in any of the eight bytes. */ 27 | inline val Long.hasSemi 28 | get() = (this xor HAS_SEMI).hasZeroByte 29 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/dev/suresh/lang/Person.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.lang 2 | 3 | // import com.javiersc.kotlin.kopy.Kopy 4 | import dev.zacsweers.redacted.annotations.Redacted 5 | 6 | data class Person(val name: Name, val address: Address, @Redacted val privateInfo: PrivateInfo) 7 | 8 | data class Name(val first: String, val last: String) 9 | 10 | data class Address(val street: String, val city: String, val state: String, val zip: String) 11 | 12 | data class PrivateInfo(@Redacted val ssn: String, val dob: String) 13 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/dev/suresh/serde/ByteArrayAsBase64Serializer.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.serde 2 | 3 | import kotlin.io.encoding.Base64 4 | import kotlinx.serialization.KSerializer 5 | import kotlinx.serialization.descriptors.* 6 | import kotlinx.serialization.encoding.* 7 | 8 | /** 9 | * A [Base64] serializer. Use with 10 | * 11 | * ```kotlin 12 | * @Serializable(with = ByteArrayAsBase64Serializer::class) 13 | * ``` 14 | */ 15 | object ByteArrayAsBase64Serializer : KSerializer { 16 | 17 | private val base64 = Base64.Default 18 | 19 | override val descriptor: SerialDescriptor 20 | get() = PrimitiveSerialDescriptor("ByteArrayAsBase64Serializer", PrimitiveKind.STRING) 21 | 22 | override fun deserialize(decoder: Decoder) = base64.decode(decoder.decodeString()) 23 | 24 | override fun serialize(encoder: Encoder, value: ByteArray) = 25 | encoder.encodeString(base64.encode(value)) 26 | } 27 | -------------------------------------------------------------------------------- /shared/src/commonMain/resources/common-main-res.txt: -------------------------------------------------------------------------------- 1 | Resource from "Shared commonMain" module ${version}! -------------------------------------------------------------------------------- /shared/src/jsMain/kotlin/dev/suresh/Extensions.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import org.khronos.webgl.ArrayBuffer 4 | import org.khronos.webgl.Int8Array 5 | 6 | /** 7 | * Convert JS [ArrayBuffer] to Kotlin [ByteArray] 8 | * 9 | * [Kotlin-JS-Types](https://kotlinlang.org/docs/js-to-kotlin-interop.html#kotlin-types-in-javascript) 10 | */ 11 | fun ArrayBuffer?.asByteArray() = this?.run { Int8Array(this).unsafeCast() } 12 | -------------------------------------------------------------------------------- /shared/src/jsMain/kotlin/dev/suresh/Platform.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlinx.browser.window 4 | 5 | actual val platform: Platform = JsPlatform 6 | 7 | object JsPlatform : Platform { 8 | override val name: String = "JS" 9 | 10 | override val osInfo: Map 11 | get() = 12 | super.osInfo + 13 | mapOf( 14 | "name" to window.navigator.platform, 15 | "userAgent" to window.navigator.userAgent, 16 | "vendor" to window.navigator.vendor, 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /shared/src/jsMain/kotlin/dev/suresh/http/HttpClient.js.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.http 2 | 3 | import io.github.oshai.kotlinlogging.KLogger 4 | import io.ktor.client.* 5 | import io.ktor.client.engine.js.* 6 | 7 | actual fun httpClient( 8 | name: String, 9 | timeout: Timeout, 10 | retry: Retry, 11 | kLogger: KLogger, 12 | config: HttpClientConfigurer 13 | ) = HttpClient(Js) { config(this) } 14 | 15 | fun getKtorEnvLogLevel(): String? = js("process.env.KTOR_LOG_LEVEL") 16 | -------------------------------------------------------------------------------- /shared/src/jsMain/kotlin/dev/suresh/tz/JsJodaTZ.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tz 2 | 3 | // @JsModule("@js-joda/timezone") @JsNonModule external object JsJodaTimeZoneModule 4 | // 5 | // private val jsJodaTz = JsJodaTimeZoneModule 6 | -------------------------------------------------------------------------------- /shared/src/jsMain/resources/common-js-res.txt: -------------------------------------------------------------------------------- 1 | Resource from "Shared commonJs" module ${version}! -------------------------------------------------------------------------------- /shared/src/jsTest/kotlin/dev/suresh/PlatformTest.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertTrue 5 | 6 | class PlatformTest { 7 | 8 | @Test 9 | fun greetings() { 10 | assertTrue(Greeting().greeting().contains("JS"), "Check js is mentioned") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /shared/src/jvmMain/java/dev/suresh/Expr.java: -------------------------------------------------------------------------------- 1 | package dev.suresh; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | @NullMarked 6 | public sealed interface Expr { 7 | record Add(Expr left, Expr right) implements Expr { 8 | } 9 | 10 | record Mul(Expr left, Expr right) implements Expr { 11 | } 12 | 13 | record Div(Expr left, Expr right) implements Expr { 14 | } 15 | 16 | record Neg(Expr e) implements Expr { 17 | } 18 | 19 | sealed interface Const extends Expr { 20 | record Int(int i) implements Const { 21 | } 22 | 23 | record Double(double d) implements Const { 24 | } 25 | 26 | record Long(long l) implements Const { 27 | } 28 | 29 | record Str(String s) implements Const { 30 | } 31 | } 32 | 33 | static long eval(Expr expr) { 34 | return switch (expr) { 35 | case Expr.Add(var l, var r) -> eval(l) + eval(r); 36 | case Expr.Mul(var l, var r) -> eval(l) * eval(r); 37 | case Expr.Div(var l, var r) -> eval(l) / eval(r); 38 | case Expr.Neg(var e) -> -eval(e); 39 | case Expr.Const c -> switch (c) { 40 | case Expr.Const.Int(var i) -> i; 41 | case Expr.Const.Double(var d) -> (long) d; 42 | case Expr.Const.Long(var l) -> l; 43 | case Expr.Const.Str(var s) -> Long.parseLong(s); 44 | }; 45 | }; 46 | } 47 | } -------------------------------------------------------------------------------- /shared/src/jvmMain/java/dev/suresh/Gatherers.java: -------------------------------------------------------------------------------- 1 | package dev.suresh; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.function.BiConsumer; 6 | import java.util.function.Function; 7 | import java.util.function.Supplier; 8 | import java.util.stream.Gatherer; 9 | import java.util.stream.Gatherer.Downstream; 10 | import java.util.stream.Gatherer.Integrator; 11 | import java.util.stream.Stream; 12 | 13 | public class Gatherers { 14 | 15 | static Gatherer map(Function f) { 16 | Integrator integrator = (_, e, ds) -> { 17 | var ir = f.apply(e); 18 | return ds.push(ir); 19 | }; 20 | return Gatherer.of(integrator); 21 | } 22 | 23 | static Gatherer, List> group(int size) { 24 | Supplier> initializer = ArrayList::new; 25 | Integrator, T, List> integrator = (list, e, ds) -> { 26 | list.add(e); 27 | if (list.size() < size) { 28 | return true; 29 | } else { 30 | var group = List.copyOf(list); 31 | list.clear(); 32 | return ds.push(group); 33 | } 34 | }; 35 | BiConsumer, Downstream super List>> finisher = (list, ds) -> { 36 | var group = List.copyOf(list); 37 | if (!group.isEmpty()) { 38 | ds.push(group); 39 | } 40 | }; 41 | return Gatherer.ofSequential(initializer, integrator, finisher); 42 | } 43 | 44 | public static void main(String[] args) { 45 | Stream.of(1, 2, 3, 4, 5).gather(map(e -> e + 1)).gather(group(2)).forEach(System.out::println); 46 | } 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /shared/src/jvmMain/java/dev/suresh/Lang.java: -------------------------------------------------------------------------------- 1 | package dev.suresh; 2 | 3 | public record Lang(String name) { 4 | } 5 | 6 | 7 | record Model(String name, float temp, int tokens) { 8 | 9 | public static ModelBuilder builder() { 10 | return new ModelBuilder(); 11 | } 12 | 13 | void main() { 14 | 15 | var b = Model.builder().name("test").build(); 16 | 17 | } 18 | } 19 | 20 | class ModelBuilder { 21 | private String name; 22 | private float temp = 0.1f; 23 | 24 | private int tokens = 100; 25 | 26 | public ModelBuilder name(String name) { 27 | this.name = name; 28 | return this; 29 | } 30 | 31 | public ModelBuilder temp(float temp) { 32 | this.temp = temp; 33 | return this; 34 | } 35 | 36 | public ModelBuilder tokens(int tokens) { 37 | this.tokens = tokens; 38 | return this; 39 | } 40 | 41 | public Model build() { 42 | return new Model(name, temp, tokens); 43 | } 44 | } -------------------------------------------------------------------------------- /shared/src/jvmMain/java/dev/suresh/Result.java: -------------------------------------------------------------------------------- 1 | package dev.suresh; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A discriminated union that encapsulates a successful outcome with a value of type T or a failure 7 | * with an arbitrary Throwable exception. 8 | * 9 | * @param Result value type. 10 | */ 11 | sealed interface Result extends Serializable { 12 | 13 | static Result success(T value) { 14 | return new Success<>(value); 15 | } 16 | 17 | static Result failure(Throwable error) { 18 | return new Failure<>(error); 19 | } 20 | 21 | default boolean isSuccess() { 22 | return this instanceof Success; 23 | } 24 | 25 | default boolean isFailure() { 26 | return this instanceof Failure; 27 | } 28 | 29 | default T getOrNull() { 30 | return this instanceof Success s ? s.value() : null; 31 | } 32 | 33 | default Throwable exceptionOrNull() { 34 | return this instanceof Failure t ? t.error() : null; 35 | } 36 | 37 | default String fString() { 38 | return """ 39 | ToString -> %1$s 40 | Result -> %2$s 41 | Success -> %3$s 42 | Failure -> %4$s 43 | Exception -> %5$s 44 | """.formatted(toString(), getOrNull(), isSuccess(), isFailure(), exceptionOrNull()); 45 | } 46 | 47 | record Success(T value) implements Result { 48 | } 49 | 50 | record Failure(Throwable error) implements Result { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /shared/src/jvmMain/kotlin/dev/suresh/FFM.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import java.lang.foreign.FunctionDescriptor 4 | import java.lang.foreign.Linker 5 | import java.lang.foreign.SymbolLookup 6 | import java.lang.invoke.MethodHandle 7 | 8 | val LINKER: Linker = Linker.nativeLinker() 9 | 10 | /** Symbols loaded via caller's class loader (System.loadLibrary) if found, else from libc */ 11 | val SYMBOL_LOOKUP: SymbolLookup by lazy { SymbolLookup.loaderLookup().or(LINKER.defaultLookup()) } 12 | 13 | val UNSAFE by lazy { 14 | sun.misc.Unsafe::class.java.getDeclaredField("theUnsafe").run { 15 | isAccessible = true 16 | get(null) as sun.misc.Unsafe 17 | } 18 | } 19 | 20 | fun downcallHandle( 21 | symbol: String, 22 | fdesc: FunctionDescriptor, 23 | vararg options: Linker.Option 24 | ): MethodHandle? = 25 | SYMBOL_LOOKUP.findOrThrow(symbol).let { LINKER.downcallHandle(it, fdesc, *options) } 26 | -------------------------------------------------------------------------------- /shared/src/jvmMain/kotlin/dev/suresh/ReentrantLazy.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import java.util.concurrent.locks.ReentrantLock 4 | import kotlin.LazyThreadSafetyMode.NONE 5 | import kotlin.LazyThreadSafetyMode.SYNCHRONIZED 6 | import kotlin.concurrent.withLock 7 | 8 | /** 9 | * Java Virtual thread friendly Kotlin lazy initialization. The implementation is based on 10 | * [Javalin Lazy](https://github.com/javalin/javalin/pull/1974) implementation. 11 | */ 12 | internal class ReentrantLazy(initializer: () -> T) : Lazy { 13 | private companion object { 14 | private object UNINITIALIZED_VALUE 15 | } 16 | 17 | private var initializer: (() -> T)? = initializer 18 | 19 | @Volatile private var lock: ReentrantLock? = ReentrantLock() 20 | 21 | @Volatile private var _value: Any? = UNINITIALIZED_VALUE 22 | 23 | override val value: T 24 | get() { 25 | lock?.withLock { 26 | if (_value === UNINITIALIZED_VALUE) { 27 | this._value = initializer!!.invoke() 28 | this.lock = null 29 | this.initializer = null 30 | } 31 | } 32 | @Suppress("UNCHECKED_CAST") 33 | return _value as T 34 | } 35 | 36 | override fun isInitialized() = _value !== UNINITIALIZED_VALUE 37 | } 38 | 39 | /** A virtual thread friendly [kotlin.lazy] implementation. */ 40 | fun vtLazy( 41 | threadSafetyMode: LazyThreadSafetyMode = NONE, 42 | initializer: () -> T 43 | ): Lazy = 44 | when (threadSafetyMode) { 45 | SYNCHRONIZED -> ReentrantLazy(initializer) 46 | else -> lazy(threadSafetyMode, initializer) 47 | } 48 | -------------------------------------------------------------------------------- /shared/src/jvmMain/kotlin/dev/suresh/cert/PemFormat.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.cert 2 | 3 | import java.security.cert.CertificateFactory 4 | import java.security.cert.X509Certificate 5 | import kotlin.io.encoding.Base64 6 | 7 | /** 8 | * PEM X.509 certificates reader & writer 9 | * 10 | * @author Suresh 11 | */ 12 | object PemFormat { 13 | 14 | /** Supported X.509 SAN OIDs */ 15 | const val ALT_RFC822_NAME = 1 16 | const val ALT_DNS_NAME = 2 17 | const val ALT_IPA_NAME = 7 18 | 19 | /** PEM certificate pattern */ 20 | private val CERT_PATTERN = 21 | """-+BEGIN\s+.*CERTIFICATE[^-]*-+(?:\s|\r|\n)+([a-z0-9+/=\r\n]+)-+END\s+.*CERTIFICATE[^-]*-+""" 22 | .toRegex(RegexOption.IGNORE_CASE) 23 | 24 | val certFactory = CertificateFactory.getInstance("X.509") 25 | 26 | /** 27 | * Checks if the given string is a PEM encoded certificate. 28 | * 29 | * @param data cert data 30 | * @return `true` if it's a `PEM` certificate. 31 | */ 32 | fun isPem(data: String) = CERT_PATTERN.containsMatchIn(data) 33 | 34 | /** 35 | * Read all X.509 certificates from the given PEM encoded certificate. 36 | * 37 | * @param certChain PEM encoded cert(s) 38 | * @return list of [X509Certificate] 39 | */ 40 | fun readCertChain(certChain: String) = 41 | try { 42 | CERT_PATTERN.findAll(certChain) 43 | .map { 44 | val base64Text = it.groupValues[1] 45 | val buffer = Base64.Mime.decode(base64Text.toByteArray(Charsets.US_ASCII)) 46 | certFactory.generateCertificate(buffer.inputStream()) as X509Certificate 47 | } 48 | .toList() 49 | } catch (e: Exception) { 50 | throw IllegalStateException("Can't read the PEM certificate, cert data is invalid", e) 51 | } 52 | 53 | /** Encodes the given [encoded] bytes to PEM format. */ 54 | fun encodePem(type: String, encoded: ByteArray) = 55 | """-----BEGIN $type----- 56 | |${Base64.encode(encoded).chunked(64).joinToString("\n")} 57 | |-----END $type----- 58 | |""" 59 | .trimMargin() 60 | } 61 | -------------------------------------------------------------------------------- /shared/src/jvmMain/kotlin/dev/suresh/cert/RootCA.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.cert 2 | 3 | /** Custom TrustAnchors */ 4 | object RootCA { 5 | 6 | const val ISRG_ROOT_X1 = "ISRG Root X1" 7 | 8 | const val ISRG_ROOT_X2 = "ISRG Root X2" 9 | 10 | val certs by lazy { 11 | val pem = 12 | ClassLoader.getSystemResource("ca/cacert.pem")?.readText(Charsets.US_ASCII) 13 | ?: error("RootCAs (ca/cacert.pem) not found!") 14 | PemFormat.readCertChain(pem).onEach { it.checkValidity() } 15 | } 16 | 17 | val commonNames 18 | get() = certs.map { it.commonName } 19 | 20 | val isrgRootX1 21 | get() = certs.first { it.commonName == ISRG_ROOT_X1 } 22 | 23 | val isrgRootX2 24 | get() = certs.first { it.commonName == ISRG_ROOT_X2 } 25 | } 26 | -------------------------------------------------------------------------------- /shared/src/jvmMain/kotlin/dev/suresh/http/HttpClient.jvm.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.http 2 | 3 | import dev.suresh.cert.RootCA 4 | import io.github.oshai.kotlinlogging.KLogger 5 | import io.github.oshai.kotlinlogging.KotlinLogging 6 | import io.ktor.client.* 7 | import io.ktor.client.engine.java.* 8 | import nl.altindag.ssl.SSLFactory 9 | 10 | val log = KotlinLogging.logger {} 11 | 12 | val customSSLFactory: SSLFactory by lazy { 13 | log.info { "Initializing TLS context with custom RootCAs..." } 14 | log.info { "Root CAs: ${RootCA.commonNames}" } 15 | SSLFactory.builder() 16 | .withDefaultTrustMaterial() 17 | .withTrustMaterial(RootCA.certs) 18 | .withSwappableTrustMaterial() 19 | .build() 20 | } 21 | 22 | actual fun httpClient( 23 | name: String, 24 | timeout: Timeout, 25 | retry: Retry, 26 | kLogger: KLogger, 27 | config: HttpClientConfigurer 28 | ) = 29 | HttpClient(Java) { 30 | config(this) 31 | engine { config { sslContext(customSSLFactory.sslContext) } } 32 | } 33 | 34 | // val cioHttpClient = HttpClient(CIO) { 35 | // config(this) 36 | // 37 | // engine { 38 | // maxConnectionsCount = 1000 39 | // endpoint { 40 | // maxConnectionsPerRoute = 100 41 | // pipelineMaxSize = 20 42 | // keepAliveTime = 5000 43 | // connectTimeout = 5000 44 | // connectAttempts = 5 45 | // } 46 | // https { 47 | // serverName = "suresh.dev" 48 | // cipherSuites = CIOCipherSuites.SupportedSuites 49 | // trustManager = myCustomTrustManager 50 | // random = mySecureRandom 51 | // addKeyStore(myKeyStore, myKeyStorePassword) 52 | // } 53 | // } 54 | // } 55 | -------------------------------------------------------------------------------- /shared/src/jvmMain/resources/common-jvm-res.txt: -------------------------------------------------------------------------------- 1 | Resource from "Shared commonJVM" module ${version}! -------------------------------------------------------------------------------- /shared/src/jvmTest/kotlin/dev/suresh/PlatformTest.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlin.test.assertTrue 4 | import org.junit.jupiter.api.Test 5 | 6 | class PlatformTest { 7 | 8 | @Test 9 | fun greetings() { 10 | assertTrue(Greeting().greeting().contains("JVM"), message = "JVM platform check failed!") 11 | DOP.run() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /shared/src/nativeMain/kotlin/dev/suresh/Platform.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlin.native.Platform as KNPlatform 4 | import kotlinx.cinterop.* 5 | import platform.posix.* 6 | 7 | actual val platform: Platform = NativePlatform 8 | 9 | object NativePlatform : Platform { 10 | override val name: String = "Native" 11 | 12 | override fun env(key: String, def: String?) = getenv(key)?.toKStringFromUtf8() ?: def 13 | 14 | override val osInfo: Map 15 | get() = 16 | super.osInfo + 17 | super.osInfo + 18 | mapOf( 19 | "name" to KNPlatform.osFamily.name, 20 | "version" to env("OSTYPE", "n/a"), 21 | "arch" to KNPlatform.cpuArchitecture.name, 22 | "user" to env("USER"), 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /shared/src/nativeMain/kotlin/dev/suresh/http/HttpClient.native.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.http 2 | 3 | import io.github.oshai.kotlinlogging.KLogger 4 | import io.ktor.client.* 5 | import io.ktor.client.engine.curl.* 6 | 7 | actual fun httpClient( 8 | name: String, 9 | timeout: Timeout, 10 | retry: Retry, 11 | kLogger: KLogger, 12 | config: HttpClientConfigurer 13 | ) = 14 | HttpClient(Curl) { 15 | config(this) 16 | engine { 17 | // https://youtrack.jetbrains.com/issue/KTOR-8339 18 | val cacertPath = "/etc/ssl/certs" 19 | if (Platform.osFamily == OsFamily.LINUX) { 20 | caPath = cacertPath 21 | kLogger.warn { "Setting CA path to $caPath" } 22 | } 23 | sslVerify = true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /shared/src/nativeMain/resources/common-native-res.txt: -------------------------------------------------------------------------------- 1 | Resource from "Shared native" module ${version}! -------------------------------------------------------------------------------- /shared/src/nativeTest/kotlin/dev.suresh/PlatformTest.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertTrue 5 | 6 | class PlatformTest { 7 | @Test 8 | fun greetings() { 9 | assertTrue(Greeting().greeting().contains("Native"), "Check Native is mentioned") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /shared/src/wasmJsMain/kotlin/dev/suresh/Extensions.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlin.wasm.unsafe.UnsafeWasmMemoryApi 4 | import kotlin.wasm.unsafe.withScopedMemoryAllocator 5 | import org.khronos.webgl.ArrayBuffer 6 | import org.khronos.webgl.Int8Array 7 | 8 | /** JavaScript console class */ 9 | external class Console : JsAny { 10 | fun log(message: String?) 11 | 12 | fun log(message: JsAny?) 13 | } 14 | 15 | /** JavaScript console object */ 16 | external val console: Console 17 | 18 | fun currentTimeMillis(): Long = currentTimeMillisJs().toLong() 19 | 20 | private fun currentTimeMillisJs(): Double = js("new Date().getTime()") 21 | 22 | fun ArrayBuffer?.toByteArray() = 23 | this?.run { 24 | val source = Int8Array(this, 0, byteLength) 25 | jsInt8ArrayToKotlinByteArray(source) 26 | } 27 | 28 | @JsFun( 29 | """ (src, size, dstAddr) => { 30 | const mem8 = new Int8Array(wasmExports.memory.buffer, dstAddr, size); 31 | mem8.set(src); 32 | } 33 | """) 34 | internal external fun jsExportInt8ArrayToWasm(src: Int8Array, size: Int, dstAddr: Int) 35 | 36 | internal fun jsInt8ArrayToKotlinByteArray(x: Int8Array): ByteArray { 37 | val size = x.length 38 | 39 | @OptIn(UnsafeWasmMemoryApi::class) 40 | return withScopedMemoryAllocator { allocator -> 41 | val memBuffer = allocator.allocate(size) 42 | val dstAddress = memBuffer.address.toInt() 43 | jsExportInt8ArrayToWasm(x, size, dstAddress) 44 | ByteArray(size) { i -> (memBuffer + i).loadByte() } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /shared/src/wasmJsMain/kotlin/dev/suresh/Platform.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlinx.browser.window 4 | 5 | actual val platform: Platform = WasmPlatform 6 | 7 | object WasmPlatform : Platform { 8 | override val name: String = "Wasm" 9 | 10 | override val osInfo: Map 11 | get() = 12 | super.osInfo + 13 | mapOf( 14 | "name" to window.navigator.platform, 15 | "userAgent" to window.navigator.userAgent, 16 | "vendor" to window.navigator.vendor, 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /shared/src/wasmJsMain/kotlin/dev/suresh/http/HttpClient.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.http 2 | 3 | import io.github.oshai.kotlinlogging.KLogger 4 | import io.ktor.client.* 5 | import io.ktor.client.engine.js.* 6 | 7 | actual fun httpClient( 8 | name: String, 9 | timeout: Timeout, 10 | retry: Retry, 11 | kLogger: KLogger, 12 | config: HttpClientConfigurer, 13 | ) = HttpClient(Js) { config(this) } 14 | -------------------------------------------------------------------------------- /shared/src/wasmJsMain/resources/common-wasm-res.txt: -------------------------------------------------------------------------------- 1 | Resource from "Shared commonWasm" module ${version}! -------------------------------------------------------------------------------- /shared/src/wasmJsTest/kotlin/dev/suresh/PlatformTest.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertTrue 5 | 6 | class PlatformTest { 7 | 8 | @Test 9 | fun greetings() { 10 | assertTrue(Greeting().greeting().contains("Wasm"), "Check Wasm is mentioned") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /web/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.mpp 5 | dev.suresh.plugin.publishing 6 | } 7 | 8 | description = "Kotlin JS/Wasm Web application" 9 | 10 | val sharedJsRes by configurations.creating 11 | val sharedWasmRes by configurations.creating 12 | 13 | dependencies { 14 | commonMainImplementation(projects.shared) 15 | jsMainImplementation(npm("highlight.js", libs.versions.npm.highlightjs.get())) 16 | jsMainImplementation(npm("@xterm/xterm", libs.versions.npm.xtermjs.get())) 17 | jsMainImplementation(libs.kotlin.cryptography.webcrypto) 18 | wasmJsMainImplementation(libs.kotlin.cryptography.webcrypto) 19 | 20 | sharedJsRes(project(path = projects.shared.path, configuration = "sharedJsResources")) 21 | sharedWasmRes(project(path = projects.shared.path, configuration = "sharedWasmResources")) 22 | } 23 | 24 | tasks { 25 | val copySharedJsResources by 26 | registering(Sync::class) { 27 | from(sharedJsRes) 28 | into(jsProcessResources.map { it.destinationDir }) 29 | includeEmptyDirs = false 30 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 31 | } 32 | val copySharedWasmResources by 33 | registering(Sync::class) { 34 | from(sharedWasmRes) 35 | into(wasmJsProcessResources.map { it.destinationDir }) 36 | includeEmptyDirs = false 37 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 38 | } 39 | 40 | jsProcessResources { mustRunAfter(copySharedJsResources) } 41 | wasmJsProcessResources { mustRunAfter(copySharedWasmResources) } 42 | } 43 | 44 | artifacts { 45 | val jsApp by configurations.consumable("jsApp") 46 | add(jsApp.name, tasks.jsBrowserDistribution) 47 | val wasmApp by configurations.consumable("wasmApp") 48 | add(wasmApp.name, tasks.wasmJsBrowserDistribution) 49 | } 50 | -------------------------------------------------------------------------------- /web/src/jsMain/kotlin/hljs/Highlight.kt: -------------------------------------------------------------------------------- 1 | package hljs 2 | 3 | import org.w3c.dom.* 4 | 5 | @JsName("hljs") 6 | @JsModule("highlight.js") 7 | @JsNonModule 8 | external class HighlightJs { 9 | companion object { 10 | fun highlightElement(block: HTMLElement) 11 | 12 | fun highlightAll() 13 | 14 | fun listLanguages(): List 15 | 16 | fun autoDetection(languageName: String): Boolean 17 | 18 | val versionString: String 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/src/jsMain/kotlin/interop/JsInterop.kt: -------------------------------------------------------------------------------- 1 | package interop 2 | 3 | /** Top level JS functions are defined in index.html script tag */ 4 | external fun topLevelJsFun(): dynamic 5 | -------------------------------------------------------------------------------- /web/src/jsMain/kotlin/play/KotlinPlayground.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("FunctionName") 2 | 3 | @JsModule("kotlin-playground") @JsNonModule external fun KotlinPlayground(selector: String) 4 | -------------------------------------------------------------------------------- /web/src/jsMain/kotlin/xterm/lib.es5.kt: -------------------------------------------------------------------------------- 1 | package xterm 2 | 3 | typealias Pick = Any 4 | 5 | // inline operator fun IEvent.invoke(noinline listener: (arg1: T, arg2: U) -> Any) : 6 | // IDisposable{ 7 | // return asDynamic()(listener).unsafeCast() 8 | // } 9 | -------------------------------------------------------------------------------- /web/src/jsMain/resources/css/app.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | text-align: center; 3 | padding: 20px; 4 | background-color: #f2f2f2; 5 | color: #333; 6 | } 7 | 8 | .footer div { 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | margin: 10px 0; 13 | } 14 | 15 | .footer a { 16 | text-decoration: none; 17 | color: #007bff; /* Link color */ 18 | } 19 | 20 | .footer a:hover { 21 | color: #0056b3; /* Hover color */ 22 | } 23 | 24 | .svg-icon { 25 | width: 20px; 26 | height: 20px; 27 | margin-right: 5px; 28 | vertical-align: middle; 29 | } -------------------------------------------------------------------------------- /web/src/jsMain/resources/css/hljs/github-dark.min.css: -------------------------------------------------------------------------------- 1 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! 2 | Theme: GitHub Dark 3 | Description: Dark theme as seen on github.com 4 | Author: github.com 5 | Maintainer: @Hirse 6 | Updated: 2021-05-15 7 | 8 | Outdated base version: https://github.com/primer/github-syntax-dark 9 | Current colors taken from GitHub's CSS 10 | */.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c} -------------------------------------------------------------------------------- /web/src/jsMain/resources/css/hljs/github.min.css: -------------------------------------------------------------------------------- 1 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! 2 | Theme: GitHub 3 | Description: Light theme as seen on github.com 4 | Author: github.com 5 | Maintainer: @Hirse 6 | Updated: 2021-05-15 7 | 8 | Outdated base version: https://github.com/primer/github-syntax-light 9 | Current colors taken from GitHub's CSS 10 | */.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} -------------------------------------------------------------------------------- /web/src/jsMain/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/web/src/jsMain/resources/favicon.ico -------------------------------------------------------------------------------- /web/src/jsMain/resources/logo/kotlin-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /web/src/jsTest/kotlin/AppTest.kt: -------------------------------------------------------------------------------- 1 | import kotlin.test.Test 2 | import kotlin.test.assertEquals 3 | import kotlinx.browser.document 4 | import kotlinx.html.dom.create 5 | import kotlinx.html.js.div 6 | 7 | class AppTest { 8 | 9 | @Test 10 | fun testKotlinVersion() { 11 | val container = document.create.div {} 12 | container.sayHello() 13 | assertEquals("Hello Kotlin ${KotlinVersion.CURRENT}", container.textContent) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /web/src/wasmJsMain/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/web/src/wasmJsMain/resources/favicon.ico -------------------------------------------------------------------------------- /web/src/wasmJsMain/resources/logo/kodee-excited.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/web/src/wasmJsMain/resources/logo/kodee-excited.png -------------------------------------------------------------------------------- /web/src/wasmJsMain/resources/logo/kodee-loving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/web/src/wasmJsMain/resources/logo/kodee-loving.png -------------------------------------------------------------------------------- /web/src/wasmJsMain/resources/logo/kodee-walking.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/web/src/wasmJsMain/resources/logo/kodee-walking.gif -------------------------------------------------------------------------------- /web/src/wasmJsMain/resources/logo/kotlin-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------