Skip to content
Draft
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
59 changes: 58 additions & 1 deletion .fernignore
Original file line number Diff line number Diff line change
@@ -1 +1,58 @@
# Specify files that shouldn't be modified by Fern
# Files Fern must not modify or delete when regenerating this repo.
#
# Fern prunes anything it did not generate, and it reads this file from the pull request's
# base branch — so an entry only protects a file once it is on main.
#
# publish-main.yml is hand-written: it releases com.whop.api:whop-java when a merge to main
# lands a gradle.properties version that is not yet published, and it is meant to be the
# only publish path. It has two targets behind one set of guards — GitHub Packages
# (whopio-internal, authenticated with the run's own GITHUB_TOKEN, no namespace to
# register) and the Sonatype Central Portal (public, blocked on DNS verification and a
# signing key). RELEASE_TARGET sets the default, a workflow_dispatch input overrides it,
# and every pull request rehearses both so neither path rots while the other is the live
# one.
#
# stamp-version.py is that workflow's version stamp: gradle.properties, every source file
# carrying X-Fern-SDK-Version, and .fern/metadata.json. It is a file rather than a script
# inlined into one job because both publish paths build the same commit and have to stamp
# it identically.
#
# gradle.properties is the version of record and the release trigger. The Fern Java
# generator writes no version and no group into build.gradle at all — `gradle properties`
# on the delivered tree reports `version: unspecified`, an empty group and an artifact name
# taken from the checkout directory — so without this file the project has no Maven
# coordinate. Gradle reads `group` and `version` from gradle.properties natively, which
# keeps the coordinate out of the generated build.gradle and lets Fern keep owning the
# dependency list there.
#
# release.gradle is the Gradle init script that adds the MavenPublication: the POM (name,
# description, url, licenses, developers, scm) that Maven Central requires, the whop-java
# archive name, a repository that stages the bundle to build/bundle instead of uploading
# it, and the GitHub Packages repository the internal release publishes to. Applying it
# with -I rather than editing build.gradle means a regeneration cannot silently drop it
# and Fern keeps ownership of the build file — including the four `api` dependencies,
# which are the reason a real POM and not a bare jar is what has to reach consumers.
#
# ci.yml is hand-written for a security reason, not a stylistic one. The Fern Java
# generator's GithubWorkflowGenerator emits a tag-gated `publish` job — `if:
# github.event_name == 'push' && contains(github.ref, 'refs/tags/')`, running `./gradlew
# publish` or `./gradlew sonatypeCentralUpload` with MAVEN_USERNAME, MAVEN_PASSWORD,
# MAVEN_SIGNATURE_KID, MAVEN_SIGNATURE_SECRET_KEY and MAVEN_SIGNATURE_PASSWORD in the same
# environment as Gradle's dependency resolution — whenever the generator is configured
# with a Maven registry. The java-release group in the monorepo's sdks/fern/generators.yml
# has no maven output today, so the ci.yml Fern delivered in whopsdk-java#1 is compile+test
# only. Adding that output is the obvious next step for anyone wiring Maven publishing
# through Fern, and it would introduce a second unreviewed publish path that also fires on
# the tag publish-main.yml creates. Because Fern reads this file from the base branch, the
# entry has to exist before that regeneration, not after it. Ours is the generated file
# with the publish job that cannot appear, modernised off actions/setup-java@v1.
#
# LICENSE: Fern does not carry over a LICENSE, and the POM this repo publishes declares
# Apache-2.0 — the same license the python-release and ruby-release groups pass to Fern.
# Without this entry the first regeneration would delete the file the POM points at.
.github/workflows/ci.yml
.github/workflows/publish-main.yml
.github/release.gradle
.github/stamp-version.py
gradle.properties
LICENSE
67 changes: 67 additions & 0 deletions .github/release.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
gradle.projectsLoaded {
gradle.rootProject { root ->
root.afterEvaluate {
root.base.archivesName = 'whop-java'

root.publishing {
publications {
maven(MavenPublication) {
groupId = root.group
artifactId = 'whop-java'
version = root.version
from root.components.java

pom {
name = 'Whop Java SDK'
description = 'The official Java SDK for the Whop API.'
url = 'https://github.com/whopio/whopsdk-java'
licenses {
license {
name = 'Apache License, Version 2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'whop'
name = 'Whop'
email = 'support@whop.com'
organization = 'Whop'
organizationUrl = 'https://whop.com'
}
}
scm {
url = 'https://github.com/whopio/whopsdk-java'
connection = 'scm:git:https://github.com/whopio/whopsdk-java.git'
developerConnection = 'scm:git:ssh://git@github.com/whopio/whopsdk-java.git'
}
}
}
}

repositories {
maven {
name = 'bundle'
url = root.layout.buildDirectory.dir('bundle')
}

// Same publication, same POM, same coordinate as Central — only the
// transport differs, so a consumer who later moves to Central changes
// a repository URL and not a dependency line. Credentials are read
// from the environment rather than a property so that the empty
// default keeps `-I` usable for bundle staging, where the task that
// needs them is never invoked; Gradle only demands them when
// publishMavenPublicationToGitHubPackagesRepository actually runs.
maven {
name = 'gitHubPackages'
url = System.getenv('GITHUB_PACKAGES_URL') ?: 'https://maven.pkg.github.com/whopio/whopsdk-java'
credentials {
username = System.getenv('GITHUB_ACTOR') ?: ''
password = System.getenv('GITHUB_TOKEN') ?: ''
}
}
}
}
}
}
}
101 changes: 101 additions & 0 deletions .github/stamp-version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Stamp one version across every file in the tree that reports one.

Two jobs publish the same commit to two registries, so the stamp has to be a single
artifact both can run rather than a script inlined into one of them. It is deliberately
fail-loud: Fern owns every file it touches, so a rename or a reshaped header should stop
a release rather than ship a jar that reports the generator's constant on the wire.
"""

import json
import pathlib
import re
import sys

HEADER = "X-Fern-SDK-Version"


def fail(message):
raise SystemExit("::error::" + message)


def stamp_version_file(path, version):
text = path.read_text()
text, count = re.subn(
r"(?m)^([ \t]*version[ \t]*=[ \t]*).*$", r"\g<1>" + version, text, count=1
)
if count == 0:
fail("no version property found in " + str(path))
path.write_text(text)
print("stamped " + str(path))


def stamp_sources(version):
carriers = [
path
for path in pathlib.Path("src/main").rglob("*.java")
if HEADER in path.read_text()
]

if not carriers:
fail(
"nothing under src/main carries "
+ HEADER
+ "; the published jar would report the generator's constant"
)

for path in carriers:
source = path.read_text()
source, hits = re.subn(
r'("' + HEADER + r'"\s*,\s*")[^"]*', r"\g<1>" + version, source
)
if hits == 0:
fail(
str(path)
+ " names "
+ HEADER
+ " but the rewrite matched nothing; the header shape changed"
)
path.write_text(source)
print("stamped " + str(path))


def stamp_metadata(path, version):
if not path.exists():
return
metadata = json.loads(path.read_text())
for key in ("requestedVersion", "sdkVersion"):
if key in metadata:
metadata[key] = version
path.write_text(json.dumps(metadata, indent=2) + "\n")
print("stamped " + str(path))


def assert_no_stale_version(version):
semver = re.compile(r"\d+\.\d+\.\d+")
stale = []
for path in pathlib.Path("src/main").rglob("*.java"):
for number, line in enumerate(path.read_text().splitlines(), 1):
if HEADER in line and semver.search(line) and version not in line:
stale.append(str(path) + ":" + str(number) + ": " + line.strip())

if stale:
fail(
"the source tree still reports a version other than "
+ version
+ ":\n"
+ "\n".join(stale)
)


def main():
version, version_file, metadata_file = sys.argv[1:4]

stamp_version_file(pathlib.Path(version_file), version)
stamp_sources(version)
stamp_metadata(pathlib.Path(metadata_file), version)
assert_no_stale_version(version)
print("every version stamp reports " + version)


if __name__ == "__main__":
main()
58 changes: 36 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,56 @@
name: ci
name: CI

on: [push]
on:
push:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
JAVA_VERSION: "17"

jobs:
compile:
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false

- name: Set up Java
id: setup-jre
uses: actions/setup-java@v1
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961
with:
java-version: "11"
architecture: x64
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}

- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb
with:
validate-wrappers: true

- name: Compile
run: ./gradlew compileJava
run: ./gradlew --no-daemon compileJava

test:
needs: [ compile ]
needs: [compile]
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false

- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}

- name: Set up Java
id: setup-jre
uses: actions/setup-java@v1
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb
with:
java-version: "11"
architecture: x64
validate-wrappers: true

- name: Test
run: ./gradlew test
run: ./gradlew --no-daemon test
Loading
Loading