Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/scripts/publish-jsr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Publish @kolektiv/themes to JSR via GitHub Actions OIDC (trusted publishing).
# Link the package to this repo on jsr.io — no JSR_TOKEN required in CI.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${ROOT}/packages/themes"

if [ ! -f jsr.json ]; then
echo "::error::packages/themes/jsr.json missing"
exit 1
fi

# Sync version from package.json
node -e 'const fs=require("fs");const p=JSON.parse(fs.readFileSync("package.json","utf8"));const j=JSON.parse(fs.readFileSync("jsr.json","utf8"));j.version=p.version;fs.writeFileSync("jsr.json",JSON.stringify(j,null,2)+"\n");'

if [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
echo "::warning::Not running under Actions OIDC; skipping JSR publish (link package on jsr.io for trusted publishing)"
exit 0
fi

echo "publishing to JSR via OIDC (npx jsr publish)"
npx --yes jsr publish --allow-slow-types
38 changes: 38 additions & 0 deletions .github/scripts/publish-maven.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Publish themes Maven artifacts to GitHub Packages (when token present) and Yuri Capital.
set -uo pipefail

VERSION="$(grep -E '^[[:space:]]*version[[:space:]]*=' build.gradle.kts | head -n1 | sed -E 's/.*version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/' | tr -d '[:space:]')"
if [ -z "${VERSION}" ]; then
# allprojects version in root build.gradle.kts
VERSION="$(rg -oN 'version = "([^"]+)"' build.gradle.kts | head -1 | sed -E 's/version = "([^"]+)"/\1/')"
fi
if [ -z "${VERSION}" ]; then
echo "::error::could not read version from build.gradle.kts"
exit 1
fi

echo "publishing dev.kolektiv.themes:themes:${VERSION}"

GRADLE_ARGS=(:themes:publish --continue)
export GRADLE_ARGS

LOG="$(mktemp)"
trap 'rm -f "${LOG}"' EXIT

./gradlew "${GRADLE_ARGS[@]}" 2>&1 | tee "${LOG}"
status="${PIPESTATUS[0]}"

if [ "${status}" -eq 0 ]; then
echo "Maven publish succeeded"
exit 0
fi

# Tolerate 409 conflicts (already published)
if grep -qE "Received status code 409" "${LOG}"; then
echo "::warning::Maven publish hit 409 conflict(s); treating as already published"
exit 0
fi

echo "::error::Maven publish failed"
exit 1
87 changes: 51 additions & 36 deletions .github/scripts/publish-npm.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,42 +1,57 @@
#!/usr/bin/env bash
# Publish @kolektiv/themes to the hosted Nexus repo (brand-npm).
# The repo `.npmrc` maps `@kolektiv` to the `npm-public` group for fetching, so
# we temporarily append the hosted `brand-npm` mapping (with auth).
# Dual-publish @kolektiv/themes to Yuri Capital brand-npm and GitHub Packages (when token present).
set -euo pipefail

: "${YURI_CAPITAL_REPO_USERNAME:?set YURI_CAPITAL_REPO_USERNAME}"
: "${YURI_CAPITAL_REPO_PASSWORD:?set YURI_CAPITAL_REPO_PASSWORD}"

HOST="repo.yuri.capital"
REGISTRY="https://${HOST}/repository/brand-npm/"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
VERSION="$(node -p "require('${ROOT}/packages/themes/package.json').version")"
PACKAGES=(@kolektiv/themes)
VERSION="$(node -p "require('./packages/themes/package.json').version")"

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
NPMRC="${root}/.npmrc"
backup="$(mktemp)"
cp "${NPMRC}" "${backup}"
trap 'cp "${backup}" "${NPMRC}"; rm -f "${backup}"' EXIT

AUTH="$(printf '%s:%s' "${YURI_CAPITAL_REPO_USERNAME}" "${YURI_CAPITAL_REPO_PASSWORD}" | openssl base64 -A)"
path="${REGISTRY#https://}"
path="${path#http://}"

{
printf '\n@kolektiv:registry=%s\n' "${REGISTRY}"
printf '//%s:_auth=%s\n' "${path}" "${AUTH}"
printf '//%s:always-auth=true\n' "${path}"
} >>"${NPMRC}"

already_published() {
npm view "$1@${VERSION}" --registry "${REGISTRY}" >/dev/null 2>&1

publish_one_registry() {
local name="$1"
local registry="$2"
local auth_userconfig_lines="$3"

local NPMRC_PATH="${ROOT}/.npmrc"
local backup
backup="$(mktemp)"
cp "${NPMRC_PATH}" "${backup}"
restore() { cp "${backup}" "${NPMRC_PATH}"; rm -f "${backup}"; }
trap restore EXIT

{
printf '\n'
printf '%s\n' "${auth_userconfig_lines}"
} >>"${NPMRC_PATH}"

already_published() {
npm view "$1@${VERSION}" --registry "${registry}" >/dev/null 2>&1
}

echo "publishing @kolektiv/themes@${VERSION} to ${name} (${registry})"
for pkg in "${PACKAGES[@]}"; do
if already_published "${pkg}"; then
echo "already published ${pkg}@${VERSION} on ${name}, skipping"
continue
fi
pnpm --filter "${pkg}" publish --no-git-checks --access public --registry "${registry}"
done

restore
trap - EXIT
}

echo "publishing @kolektiv/themes@${VERSION} to ${REGISTRY}"
for pkg in "${PACKAGES[@]}"; do
if already_published "${pkg}"; then
echo "already published ${pkg}@${VERSION}, skipping"
continue
fi
pnpm --filter "${pkg}" publish --no-git-checks --access public --registry "${REGISTRY}"
done
: "${YURI_CAPITAL_REPO_USERNAME:?set YURI_CAPITAL_REPO_USERNAME}"
: "${YURI_CAPITAL_REPO_PASSWORD:?set YURI_CAPITAL_REPO_PASSWORD}"
YURI_REGISTRY="https://repo.yuri.capital/repository/brand-npm/"
YURI_AUTH="$(printf '%s:%s' "${YURI_CAPITAL_REPO_USERNAME}" "${YURI_CAPITAL_REPO_PASSWORD}" | openssl base64 -A)"
YURI_PATH="${YURI_REGISTRY#https://}"
YURI_PATH="${YURI_PATH#http://}"
publish_one_registry "yuri-capital" "${YURI_REGISTRY}" "$(printf '@kolektiv:registry=%s\n//%s:_auth=%s\n//%s:always-auth=true' "${YURI_REGISTRY}" "${YURI_PATH}" "${YURI_AUTH}" "${YURI_PATH}")"

if [ -n "${GITHUB_TOKEN:-}" ] || [ -n "${NODE_AUTH_TOKEN:-}" ]; then
TOKEN="${NODE_AUTH_TOKEN:-${GITHUB_TOKEN}}"
GH_REGISTRY="https://npm.pkg.github.com/"
publish_one_registry "github-packages" "${GH_REGISTRY}" "$(printf '@kolektiv:registry=%s\n//npm.pkg.github.com/:_authToken=%s\n//npm.pkg.github.com/:always-auth=true' "${GH_REGISTRY}" "${TOKEN}")"
else
echo "::warning::GITHUB_TOKEN/NODE_AUTH_TOKEN unset; skipping npm GitHub Packages publish"
fi
13 changes: 11 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
node_modules/
dist/
*.iml
*.log
*.tgz
.DS_Store
.gradle/
.idea/
.kotlin/
build/
dist/
kotlin-js-store/
local.properties
node_modules/
out/
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.0.1-SNAPSHOT.3] - 2026-09-15

### Added

- Compose Multiplatform module (`dev.kolektiv.themes:themes`) with `DaisyColors`,
`KolektivTheme`, and the seven palettes generated from `tokens.json`.
- Gradle wrapper so Kalendee / Blawk can `includeBuild` this repo.

## [0.0.1-SNAPSHOT.2] - 2026-09-13

### Changed
Expand Down
51 changes: 48 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ targets from one source:
| `@kolektiv/themes/theme.css` | daisyUI-compatible `[data-theme]` colour variables |
| `@kolektiv/themes/shiki` | Shiki theme registrations (Kolektiv light/dark) |
| `@kolektiv/themes/tokens.json` | raw tokens for Compose / other platforms |
| `dev.kolektiv.themes:themes` | Compose Multiplatform `DaisyColors` + `KolektivTheme` |

**Colour-only by design.** A theme declares `color-scheme` and the twenty daisyUI colour
variables; each app owns its radii, sizes, borders and density. Palettes: `nord`,
`catppuccin-latte/frappe/macchiato/mocha` (extracted from Keel), and
`kolektivcomputer-lig/dark`. Each also maps to a Shiki analog via `shikiThemeFor`
`kolektiv-light` / `kolektiv-dark` (CSS aliases `kolektivcomputer-lig` / `kolektivcomputer-dark`). Each also maps to a Shiki analog via `shikiThemeFor`
(`light`/`dark` → GitHub light/dark, `nord` → `nord`, catppuccin → catppuccin,
kolektiv → kolektiv).

## Publishing

See [docs/PUBLISHING.md](./docs/PUBLISHING.md). Maven coordinates: `dev.kolektiv.themes:themes` (Yuri Capital + GitHub Packages). npm: `` `@kolektiv/themes` `` (Yuri `brand-npm` + GitHub Packages). JSR: `` `@kolektiv/themes` `` when linked.

Until the first Maven publish lands, Compose consumers can still use `includeBuild`

## Using it

```bash
Expand All @@ -39,12 +46,49 @@ import { kolektivDark } from '@kolektiv/themes/shiki';

Set `<html data-theme="catppuccin-mocha">` (etc.) and every daisyUI component follows.


## Compose

Colour-only palettes as `dev.kolektiv.themes:themes` (version `0.0.1-SNAPSHOT.3`):

```kotlin
implementation("dev.kolektiv.themes:themes:0.0.1-SNAPSHOT.3")

KolektivTheme(KolektivThemes.kolektivDark) {
val colors = KolektivTheme.colors
}
```

Until Maven is published to `repo.yuri.capital`, consume via `includeBuild`:

```kotlin
// settings.gradle.kts
includeBuild("../themes") {
dependencySubstitution {
substitute(module("dev.kolektiv.themes:themes"))
.using(project(":themes"))
}
}
```

Radii and density stay in the consuming app (Blawk's `BlawkMetrics`, Kalendee's
pack CSS, etc.). Regenerate `Palettes.kt` after a token change:

```bash
python3 compose/scripts/generate_palettes.py packages/themes/dist/tokens.json
```

`tokens.json` must include sRGB `hex` per colour key (the preview dump / SNAPSHOT.3
shape). The twenty oklch tokens in `packages/themes/src/tokens.mjs` remain the
source of truth for CSS and Shiki.

## Adding a palette

1. Add it to `packages/themes/src/tokens.mjs` (id, label, scheme, `shiki`, and the twenty
colour keys).
2. `pnpm --filter @kolektiv/themes build`.
3. Bump the version, add a `CHANGELOG.md` entry, and push a `v*` tag.
3. Regenerate Compose palettes (`python3 compose/scripts/generate_palettes.py`).
4. Bump the version, add a `CHANGELOG.md` entry, and push a `v*` tag.

## Development

Expand All @@ -53,11 +97,12 @@ pnpm install
pnpm typecheck
pnpm test
pnpm build
./gradlew :themes:jvmTest
```

## Releases

Tag-driven, mirroring Keel: `git tag vX.Y.Z && git push origin vX.Y.Z` runs the Release
workflow (GitHub Release from the changelog) and the Publish workflow (publishes to
`brand-npm`, then verifies resolution from `npm-public`).
Secrets: `YURI_CAPITAL_REPO_USERNAME`, `YURI_CAPITAL_REPO_PASSWORD`.
Secrets: `YURI_CAPITAL_REPO_USERNAME`, `YURI_CAPITAL_REPO_PASSWORD`.
10 changes: 10 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
}

allprojects {
group = "dev.kolektiv.themes"
version = "0.0.1-SNAPSHOT.3"
}
109 changes: 109 additions & 0 deletions compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
`maven-publish`
}

kotlin {
jvm()
iosArm64()
iosSimulatorArm64()

sourceSets {
commonMain.dependencies {
api(libs.compose.runtime)
api(libs.compose.ui)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}

publishing {
publications {
// KMP creates target publications; add POM metadata to all
}
repositories {
// GitHub Packages (when GITHUB_TOKEN + GITHUB_ACTOR present)
val ghActor = providers.environmentVariable("GITHUB_ACTOR")
val ghToken = providers.environmentVariable("GITHUB_TOKEN")
if (ghActor.isPresent && ghToken.isPresent) {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/KolektivComputer/themes")
credentials {
username = ghActor.get()
password = ghToken.get()
}
}
}

val yuriUser = providers.environmentVariable("YURI_CAPITAL_REPO_USERNAME")
val yuriPass = providers.environmentVariable("YURI_CAPITAL_REPO_PASSWORD")
if (yuriUser.isPresent && yuriPass.isPresent) {
val user = yuriUser.get()
val pass = yuriPass.get()
val versionString = project.version.toString()
val isCanonicalSnapshot = versionString.endsWith("-SNAPSHOT")
val isPrerelease = versionString.contains("SNAPSHOT")
if (isCanonicalSnapshot) {
maven {
name = "yuriSnapshots"
url = uri("https://repo.yuri.capital/repository/maven-snapshots/")
credentials {
username = user
password = pass
}
}
} else if (!isPrerelease) {
maven {
name = "yuriReleases"
url = uri("https://repo.yuri.capital/repository/maven-releases/")
credentials {
username = user
password = pass
}
}
} else {
// Unique pre-release (e.g. 0.0.1-SNAPSHOT.3) — releases/snapshots policy may reject;
// publish to maven-releases with allow or use snapshots host; prefer releases URL and tolerate policy in script.
maven {
name = "yuriReleases"
url = uri("https://repo.yuri.capital/repository/maven-releases/")
credentials {
username = user
password = pass
}
}
}
}
}
}

publishing.publications.withType<MavenPublication>().configureEach {
pom {
name.set("Kolektiv Themes (${project.name})")
description.set("Colour-only Kolektiv design tokens for Compose Multiplatform.")
url.set("https://github.com/KolektivComputer/themes")
licenses {
license {
name.set("MIT")
url.set("https://opensource.org/licenses/MIT")
}
}
developers {
developer {
id.set("kolektiv-computer")
name.set("Kolektiv Computing")
organization.set("Kolektiv Computing")
}
}
scm {
url.set("https://github.com/KolektivComputer/themes")
connection.set("scm:git:https://github.com/KolektivComputer/themes.git")
developerConnection.set("scm:git:https://github.com/KolektivComputer/themes.git")
}
}
}
Loading
Loading