【Maven Central】Jetpack Compose用ライブラリ公開で401エラーにハマった話

## はじめに

Android開発において、ユーザーの操作を元に戻す機能（Undo/Redo）は重要な機能の一つです。
しかし、Jetpack Composeにはこの機能を提供する公式APIが存在せず、また、サードパーティ製のライブラリもほとんど見つけることができませんでした。
そこで、この機能を簡単に実装可能なライブラリを自作してOSSとして公開しました。

本記事では、作成したライブラリ「undoable-compose」のご紹介と、Maven Centralへの公開で遭遇した401エラーのトラブル解決、そして現在のSonatype Centralを使った公開プロセスについてまとめたいと思います。

## 作成したライブラリの紹介

### undoable-compose とは

undoable-composeは、Jetpack Compose向けのundo/redo状態管理ライブラリです。
以下の特徴があります：

- **Jetpack Compose Native**: Composeのリアクティブな状態更新に特化
- **Thread-Safe**: 非同期/同期API両方でマルチスレッド対応
- **Memory Efficient**: 履歴サイズの自動制限でメモリリーク防止
- **Customizable**: 複雑なデータ型に対応するカスタム等価性チェック
- **Performance Optimized**: ArrayDequeによるO(1)操作

---

### 使用方法

基本的な使用方法は非常にシンプルで、`rememberUndoableState`を使って状態を管理し、`undoSync()`や`redoSync()`でUndo/Redo操作を行います。
インストール方法や詳細な使用例、カスタム等価性チェックなどの高度な機能については、[GitHubリポジトリ](https://github.com/YuukiYoshida/undoable-compose)のREADMEをご確認ください。

## Maven Central公開時の苦労話

### 頻発する401エラー

ライブラリを公開する際、アップロード時に401エラーが頻発し、めちゃくちゃハマりました…
後述のトークンも正しく、GPGキーを使用した署名も問題なし、アップロード先のURLも間違っていない、そんな状況で延々と出続ける401エラーに打ちのめされました。笑
また、従来のissues.sonatype.orgベースの情報では解決できず、最新のSonatype Centralの情報を探すのに大変苦労しました。

---

### 解決の鍵

2024年3月にissues.sonatype.orgからcentral.sonatype.comへの移行が行われ、多くの開発者が古いリソースや乏しいガイダンスに苦労していました。

最終的に、[gradle-maven-publish-plugin](https://github.com/vanniktech/gradle-maven-publish-plugin)を使用することで解決できました。
このプラグインは現在のSonatype Centralに対応しており、とても使いやすかったです。

そこで以下に、現在のSonatype Centralを使った正しい公開手順をまとめます。
同じような問題で困っている方の助けになれば嬉しいです。

## Sonatype Centralでの公開フロー

### 1. Sonatype Centralアカウントの作成

[central.sonatype.com](https://central.sonatype.com/)でアカウントを作成します。

---

### 2. 名前空間の作成と検証

#### 名前空間の追加

`UserIcon` > `View Namespaces` より名前空間の登録に進みます。

![スクリーンショット 2025-06-12 16.40.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/99f12d78-df80-4915-8ed8-35da8748f395.png)

![スクリーンショット 2025-06-12 16.38.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/67e1e85c-c072-4fc7-b206-199df321d4ea.png)

GitHubを使用する場合：
- `io.github.<GITHUB_USERNAME>` の形式で名前空間を作成
- GitHubリポジトリベースの検証を利用

#### GitHubリポジトリベースの検証プロセス

![namespace.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/139f7d98-c7bc-4daf-8195-109ca0ddf01d.png)

1. Verification Keyに記載されている検証キーを使用して、一時的なパブリックGitHubプロジェクトを作成
2. 検証キーをプロジェクト名として設定
3. Sonatype Centralで「Verify Namespace」をクリック
4. 検証完了後、一時プロジェクトを削除

![スクリーンショット 2025-06-12 16.47.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/1257f14b-6ed0-4a0c-8775-6aa6d62f1511.png)

:::note info
正しく認証されると、上記のようにVerifiedと表示されます。
認証自体はVerify Namespaceをクリックした時点ですぐに行われ、思っていたより早くて拍子抜けしました。
:::

---

### 3. 認証トークンの生成

`UserIcon` > `ViewAccount`と進み、「Generate User Token」を選択し、UsernameとPasswordトークンを取得・保存します。

![スクリーンショット 2025-06-12 16.39.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/35d41d12-d663-4d94-9656-cca99ed3b260.png)

:::note warn
一度作成すると古いものは除去されます。
また、表示期間は1分で、それ以降はウィンドウが閉じてしまうので注意してください。
:::

---

### 4. GPGキーの作成と設定

:::note info
最初の設定は少々手間がかかりますが、一度設定すれば使い回せるので楽になります。
:::

#### GPGのインストール（macOS）

```bash
brew install gpg
```

#### GPGキーの生成

```bash
gpg --full-gen-key
```

設定値：
- RSAとRSA（オプション1）
- キーサイズ：4096
- 有効期限：任意（例：5日）
- 実名とメールアドレスを入力
- パスフレーズを設定（忘れないようにメモしておくことをおすすめします）

#### 公開キーの配布

```bash
gpg --keyserver keyserver.ubuntu.com --send-keys <GPG_PUBLIC_KEY_ID>
```

#### 秘密キーのエクスポート

```bash
gpg --armor --export-secret-keys <GPG_PUBLIC_KEY_ID> | pbcopy
echo "<ASCII_FORMATTED_DATA>" | gpg --dearmor > ~/secring.gpg
```

### 5. Gradleプロジェクトの設定

#### プラグインの追加

```kotlin
plugins {
    id("com.vanniktech.maven.publish") version "0.28.0"
}
```

#### gradle.propertiesの設定

:::note warn
secretKeyRingFileのパスの指定を間違えると401エラーになります、パスを間違えないよう注意して設定してください。
:::

```properties
mavenCentralUsername=<YOUR_CENTRAL_SONATYPE_TOKEN_USERNAME>
mavenCentralPassword=<YOUR_CENTRAL_SONATYPE_TOKEN_PASSWORD>
signing.keyId=<GPG_PUBLIC_KEY_ID>
signing.password=<GPG_PASSPHRASE>
signing.secretKeyRingFile=<HOME>/secring.gpg
```

#### build.gradle.ktsのPOM設定

```kotlin
import com.vanniktech.maven.publish.SonatypeHost

mavenPublishing {
    coordinates(
        groupId = "<YOUR_SONATYPE_CENTRAL_NAMESPACE>",
        artifactId = "your-library-name",
        version = "1.0.0"
    )
    
    pom {
        name.set("Your Library Name")
        description.set("Library description")
        inceptionYear.set("2024")
        url.set("https://github.com/<GITHUB_USERNAME>/your-repo")
        
        licenses {
            license {
                name.set("Apache-2.0")
                url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
            }
        }
        
        developers {
            developer {
                id.set("<GITHUB_USERNAME>")
                name.set("Your Name")
                email.set("your.email@example.com")
            }
        }
        
        scm {
            url.set("https://github.com/<GITHUB_USERNAME>/your-repo")
        }
    }
    
    publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
    signAllPublications()
}
```

### 6. 公開の実行

以下のコマンドで公開できます。

```bash
./gradlew publishAndReleaseToMavenCentral --no-configuration-cache
```

### 7. 公開状況の確認

公開が完了したら、[central.sonatype.com](https://central.sonatype.com/)のプロフィールから「View Deployments」で状況を確認できます。

:::note info
通常、公開後数分〜数時間でMaven Centralで利用可能とのことです。
:::

![スクリーンショット 2025-06-12 16.52.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/f9927aa9-fe3f-4e44-a5d7-4e914477fe28.png)

公開が完了すると上記のような表示になります、お疲れ様でした！

## トラブルシューティング

### 401エラーの主な原因

1. **古い認証情報の使用**: issues.sonatype.org時代の認証情報を使用
2. **名前空間の未検証**: 名前空間の検証が完了していない
3. **トークンの設定ミス**: gradle.propertiesでの設定値の間違い
4. **GPGキーの問題**: GPGキーの設定や秘密キーファイルのパスの問題

--- 

### 解決のポイント

401エラーが発生した際には以下のポイントを押さえることが重要です：

- 最新のSonatype Central（central.sonatype.com）を使用
- gradle-maven-publish-pluginの活用
- 適切なGPGキーの設定
- 名前空間の確実な検証

## まとめ

Maven Centralへの公開は、特に認証周りで苦労することが多いですが、正しい手順と最新の情報を把握することで確実に公開できます。
特に2024年3月のSonatype移行後は、新しいcentral.sonatype.comベースの手順に従うことが重要です。

今回作成したundoable-composeライブラリは、Jetpack Composeでのundo/redo機能実装を簡単にします。
是非プロジェクトでご活用ください。

## 参考資料

- [undoable-compose GitHub Repository](https://github.com/YuukiYoshida/undoable-compose)
- [Sonatype Central Portal](https://central.sonatype.com/)
- [gradle-maven-publish-plugin](https://github.com/vanniktech/gradle-maven-publish-plugin)