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
69 changes: 69 additions & 0 deletions .github/scripts/generate-channels.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//> using scala 3.3.6
//> using jvm 17
//> using dep com.lihaoyi::os-lib:0.11.8
//> using dep com.lihaoyi::ujson:4.4.3

// Aggregates the app descriptors of each channel (apps/resources/*.json,
// apps-contrib/resources/*.json) into a single JSON file per channel
// (listings/apps.json, listings/apps-contrib.json).
//
// coursier consumes those aggregated files as URL-based channels, see
// https://get-coursier.io/docs/cli-appdescriptors#url-based-channels
//
// Usage:
// scala-cli run .github/scripts/generate-channels.sc # (re-)generate the files
// scala-cli run .github/scripts/generate-channels.sc -- --check # only check they're up-to-date

val check = args.contains("--check")

val root = os.pwd
val listingsDir = root / "listings"
val channels = Seq("apps", "apps-contrib")

def channelContent(channel: String): String = {
val resourcesDir = root / channel / "resources"
val entries = os.list(resourcesDir)
.filter(p => os.isFile(p) && p.last.endsWith(".json"))
.sortBy(_.last)
.map { path =>
val name = path.last.stripSuffix(".json")
val json =
try ujson.read(os.read(path))
catch {
case e: ujson.ParseException =>
sys.error(s"Error parsing $path: ${e.getMessage}")
}
json match {
case obj: ujson.Obj => name -> (obj: ujson.Value)
case _ => sys.error(s"$path: expected a JSON object at the root")
}
}
ujson.write(ujson.Obj.from(entries), indent = 2) + System.lineSeparator()
}

var outdated = List.empty[os.Path]

for (channel <- channels) {
val dest = listingsDir / s"$channel.json"
val content = channelContent(channel)
if (check) {
val current = if (os.exists(dest)) Some(os.read(dest)) else None
if (!current.contains(content))
outdated = dest :: outdated
}
else {
os.write.over(dest, content, createFolders = true)
System.err.println(s"Wrote $dest")
}
}

if (outdated.nonEmpty) {
System.err.println(
outdated.reverse.map(_.relativeTo(root)).mkString(
"Outdated channel file(s): ",
", ",
"\nRun 'scala-cli run .github/scripts/generate-channels.sc' to update them."
)
)
sys.exit(1)
}
6 changes: 5 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ jobs:
- uses: coursier/setup-action@v3.0
with:
jvm: 8
apps: sbt
apps: sbt scala-cli
- run: sbt publishLocal
# Validates the app descriptors, by aggregating them the way the
# update-channels workflow does (it doesn't check that the files under
# listings/ are up-to-date, CI updates them upon merge on main)
- run: scala-cli run .github/scripts/generate-channels.sc
45 changes: 45 additions & 0 deletions .github/workflows/check-pr-scope.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: check-pr-scope
on:
pull_request: {}

# The files under listings/ are generated from the app descriptors by
# .github/scripts/generate-channels.sc, and updated on main by the
# update-channels workflow. Mixing generated and hand-written changes in a
# single PR makes them conflict with each other, so a PR must either update
# listings/, or everything else, but not both.

jobs:
check-pr-scope:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Check changed files
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
changed="$(git diff --name-only "$BASE_SHA...$HEAD_SHA")"
listings="$(printf '%s\n' "$changed" | grep '^listings/' || true)"
others="$( printf '%s\n' "$changed" | grep -v '^listings/' || true)"
if [ -n "$listings" ] && [ -n "$others" ]; then
echo "This PR changes both listings/ and other files:"
echo
echo "Under listings/:"
printf '%s\n' "$listings" | sed 's/^/ /'
echo
echo "Elsewhere:"
printf '%s\n' "$others" | sed 's/^/ /'
echo
echo "The files under listings/ are generated (see" \
".github/scripts/generate-channels.sc) and updated on main by the" \
"update-channels workflow. Please split those changes in two PRs."
exit 1
fi
if [ -n "$listings" ]; then
echo "OK: this PR only changes files under listings/"
else
echo "OK: this PR doesn't change any file under listings/"
fi
53 changes: 53 additions & 0 deletions .github/workflows/update-channels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: update-channels
on:
push:
branches:
- main
paths:
- apps/resources/**
- apps-contrib/resources/**
- .github/scripts/generate-channels.sc
- .github/workflows/update-channels.yml
workflow_dispatch: {}

# Regenerates listings/apps.json and listings/apps-contrib.json (the
# URL-based channels read by coursier) from the app descriptors under
# apps/resources and apps-contrib/resources, and opens (or updates) a pull
# request with the changes, if any.
concurrency: update-channels

jobs:
update-channels:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v7
- uses: coursier/cache-action@v8.1
- uses: coursier/setup-action@v3.0
with:
jvm: 17
apps: scala-cli
- run: scala-cli run .github/scripts/generate-channels.sc
# Note: PRs opened with the default GITHUB_TOKEN don't trigger the
# build workflow. Pass a PAT or GitHub App token via `token` if CI
# should run on them.
- uses: peter-evans/create-pull-request@v7
with:
add-paths: |
listings/apps.json
listings/apps-contrib.json
commit-message: Update channel JSON files
author: Github Actions <actions@github.com>
committer: Github Actions <actions@github.com>
branch: update-channels
delete-branch: true
base: main
title: Update channel JSON files
body: |
Regenerates `listings/apps.json` and `listings/apps-contrib.json`
from the app descriptors under `apps/resources` and
`apps-contrib/resources`.

Automatically opened by the `update-channels` workflow.
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,30 @@ You can find information about creating your own application to be installed
with `cs` [here on the
website](https://get-coursier.io/docs/cli-install.html#creating-your-own-applications).

Each channel is made of one JSON file per app, under `apps/resources` (Main
channel) and `apps-contrib/resources` (Contrib channel). Those are aggregated
into a single JSON file per channel, [`listings/apps.json`](listings/apps.json)
and [`listings/apps-contrib.json`](listings/apps-contrib.json), that coursier
reads as URL-based channels:

- Main: https://raw.githubusercontent.com/coursier/apps/main/listings/apps.json
- Contrib: https://raw.githubusercontent.com/coursier/apps/main/listings/apps-contrib.json

Those aggregated files are generated by the
`.github/scripts/generate-channels.sc` Scala CLI script. Please don't update
them directly: upon pushes to the `main` branch, CI regenerates them and opens
a pull request with the changes, if any. Locally, you can regenerate them with
```
scala-cli run .github/scripts/generate-channels.sc
```

The channels are also published as JARs on Maven Central, as
`io.get-coursier:apps` and `io.get-coursier:apps-contrib`, for former
coursier versions.

## Main
These are the apps in the default main JAR-based channel, `io.get-coursier:app`
which is used with `cs install`
These are the apps in the default Main channel, used by `cs install` and
`cs launch`.

- almond
- ammonite
Expand Down Expand Up @@ -53,7 +74,8 @@ which is used with `cs install`
- stc

## Contrib
These apps are available by passing `--contrib` to the `cs install` command.
These apps are available by passing `--contrib` to the `cs install` and
`cs launch` commands.
Feel free to send in a PR to add your application here!

- amm-runner
Expand Down Expand Up @@ -128,9 +150,9 @@ Feel free to send in a PR to add your application here!
- zookeeper

### Updating the README
This README is auto-generated by the `scripts/generate-readme.sc` Ammonite
script Please don't update the README directly, but rather update the
`README.template.md`. Note that you don't need to manually add your app to it,
This README is auto-generated by the `.github/scripts/generate-readme.sc`
Scala CLI script. Please don't update the README directly, but rather update
the `README.template.md`. Note that you don't need to manually add your app to it,
it will be automatically added in CI.

### Testing a change
Expand All @@ -140,3 +162,6 @@ works correctly, you can do the following while in the workspace.
```
cs launch --default-channels=false --channel ./(apps|apps-contrib)/resources <app name>
```

This reads the app descriptors straight from the `resources` directories, so
that you don't need to regenerate the files under `listings/` when testing.
37 changes: 31 additions & 6 deletions README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,46 @@ You can find information about creating your own application to be installed
with `cs` [here on the
website](https://get-coursier.io/docs/cli-install.html#creating-your-own-applications).

Each channel is made of one JSON file per app, under `apps/resources` (Main
channel) and `apps-contrib/resources` (Contrib channel). Those are aggregated
into a single JSON file per channel, [`listings/apps.json`](listings/apps.json)
and [`listings/apps-contrib.json`](listings/apps-contrib.json), that coursier
reads as URL-based channels:

- Main: https://raw.githubusercontent.com/coursier/apps/main/listings/apps.json
- Contrib: https://raw.githubusercontent.com/coursier/apps/main/listings/apps-contrib.json

Those aggregated files are generated by the
`.github/scripts/generate-channels.sc` Scala CLI script. Please don't update
them directly: upon pushes to the `main` branch, CI regenerates them and opens
a pull request with the changes, if any. Locally, you can regenerate them with
```
scala-cli run .github/scripts/generate-channels.sc
```

The channels are also published as JARs on Maven Central, as
`io.get-coursier:apps` and `io.get-coursier:apps-contrib`, for former
coursier versions.

## Main
These are the apps in the default main JAR-based channel, `io.get-coursier:app`
which is used with `cs install`
These are the apps in the default Main channel, used by `cs install` and
`cs launch`.

```scala mdoc:list:apps
```

## Contrib
These apps are available by passing `--contrib` to the `cs install` command.
These apps are available by passing `--contrib` to the `cs install` and
`cs launch` commands.
Feel free to send in a PR to add your application here!

```scala mdoc:list:apps-contrib
```

### Updating the README
This README is auto-generated by the `scripts/generate-readme.sc` Ammonite
script Please don't update the README directly, but rather update the
`README.template.md`. Note that you don't need to manually add your app to it,
This README is auto-generated by the `.github/scripts/generate-readme.sc`
Scala CLI script. Please don't update the README directly, but rather update
the `README.template.md`. Note that you don't need to manually add your app to it,
it will be automatically added in CI.

### Testing a change
Expand All @@ -35,3 +57,6 @@ works correctly, you can do the following while in the workspace.
```
cs launch --default-channels=false --channel ./(apps|apps-contrib)/resources <app name>
```

This reads the app descriptors straight from the `resources` directories, so
that you don't need to regenerate the files under `listings/` when testing.