Compare commits
10 Commits
v1.4.2
..
4c8b0da3da
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c8b0da3da | |||
| 9a8a014795 | |||
| 9640d336a6 | |||
| 12ce015d83 | |||
| f455bf4736 | |||
| 9bc66c7cf3 | |||
| e9022de150 | |||
| cb327b8073 | |||
| 1c354d18bb | |||
| 0ed88691c2 |
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# .githooks/pre-push — invokes preflight.sh (A/B/C/D=build).
|
||||||
|
exec scripts/preflight.sh
|
||||||
@@ -23,7 +23,6 @@ https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new
|
|||||||
- [ ] Documentation only
|
- [ ] Documentation only
|
||||||
- [ ] Translation update
|
- [ ] Translation update
|
||||||
- [ ] Build, CI or tooling change
|
- [ ] Build, CI or tooling change
|
||||||
- [ ] Upstream cherry-pick from Chat 2
|
|
||||||
|
|
||||||
## Linked issue
|
## Linked issue
|
||||||
|
|
||||||
@@ -53,7 +52,6 @@ new commands, new translations, removed behaviour. If none, write
|
|||||||
bump and is it covered by the existing migration tests?
|
bump and is it covered by the existing migration tests?
|
||||||
- Does this change the schema in MessageStore?
|
- Does this change the schema in MessageStore?
|
||||||
- Does this change the repo.json or HellionChat.yaml manifest fields?
|
- Does this change the repo.json or HellionChat.yaml manifest fields?
|
||||||
- Does this affect the upstream cherry-pick path? See docs/UPSTREAM_SYNC.md.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|||||||
@@ -8,19 +8,19 @@ Dalamud main plugin repo. To install:
|
|||||||
|
|
||||||
1. In XIVLauncher: **Settings → Experimental → Custom Plugin Repositories**
|
1. In XIVLauncher: **Settings → Experimental → Custom Plugin Repositories**
|
||||||
2. Add the URL:
|
2. Add the URL:
|
||||||
`https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/repo.json`
|
`https://gitea.com/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
|
||||||
3. Enable, save, then `/xlplugins` → search **Hellion Chat** → install
|
3. Enable, save, then `/xlplugins` → search **Hellion Chat** → install
|
||||||
|
|
||||||
## Project documents
|
## Project documents
|
||||||
|
|
||||||
- [README](https://github.com/JonKazama-Hellion/HellionChat/blob/main/README.md) — features, architecture, build
|
- [README](https://gitea.com/JonKazama-Hellion/HellionChat/src/branch/main/README.md) — features, architecture, build
|
||||||
- [Privacy notice](https://github.com/JonKazama-Hellion/HellionChat/blob/main/PRIVACY.md) — what the plugin stores and sends
|
- [Privacy notice](https://gitea.com/JonKazama-Hellion/HellionChat/src/branch/main/PRIVACY.md) — what the plugin stores and sends
|
||||||
- [Third-party notices](https://github.com/JonKazama-Hellion/HellionChat/blob/main/docs/THIRD_PARTY_NOTICES.md) — dependencies and licences
|
- [Third-party notices](https://gitea.com/JonKazama-Hellion/HellionChat/src/branch/main/docs/THIRD_PARTY_NOTICES.md) — dependencies and licences
|
||||||
- [Security policy](https://github.com/JonKazama-Hellion/HellionChat/blob/main/SECURITY.md) — vulnerability reporting
|
- [Security policy](https://gitea.com/JonKazama-Hellion/HellionChat/src/branch/main/SECURITY.md) — vulnerability reporting
|
||||||
- [Support](https://github.com/JonKazama-Hellion/HellionChat/blob/main/SUPPORT.md) — bug reports, questions, contact paths
|
- [Support](https://gitea.com/JonKazama-Hellion/HellionChat/src/branch/main/SUPPORT.md) — bug reports, questions, contact paths
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
[EUPL-1.2](https://github.com/JonKazama-Hellion/HellionChat/blob/main/LICENSE).
|
[EUPL-1.2](https://gitea.com/JonKazama-Hellion/HellionChat/src/branch/main/LICENSE).
|
||||||
Based on [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna,
|
Based on [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna,
|
||||||
also EUPL-1.2.
|
also EUPL-1.2.
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ name: Build
|
|||||||
# Verifies that every push to main and every PR still builds against the
|
# Verifies that every push to main and every PR still builds against the
|
||||||
# current Dalamud staging branch. Does not produce release artefacts; the
|
# current Dalamud staging branch. Does not produce release artefacts; the
|
||||||
# release workflow handles that on tag.
|
# release workflow handles that on tag.
|
||||||
|
#
|
||||||
|
# Linux runner: gitea.com Cloud Actions provides ubuntu-latest. The plugin
|
||||||
|
# csproj targets net10.0-windows, but `dotnet build` cross-compiles on
|
||||||
|
# Linux as long as the Dalamud staging assemblies are present at the
|
||||||
|
# expected lookup path ($(HOME)/.xlcore/dalamud/Hooks/dev/, which the
|
||||||
|
# Dalamud SDK 15 uses on Linux).
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -21,7 +27,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build (Release)
|
name: Build (Release)
|
||||||
runs-on: windows-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -34,23 +40,14 @@ jobs:
|
|||||||
dotnet-version: 10.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Download Dalamud staging
|
- name: Download Dalamud staging
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
mkdir -p "$hooks"
|
||||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
unzip -oq dalamud.zip -d "$hooks"
|
||||||
|
|
||||||
- name: Restore
|
- name: Restore
|
||||||
run: dotnet restore HellionChat/HellionChat.csproj
|
run: dotnet restore HellionChat/HellionChat.csproj
|
||||||
|
|
||||||
- name: Build (Release)
|
- name: Build (Release)
|
||||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
||||||
|
|
||||||
- name: Upload build output
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: HellionChat-build-${{ github.run_number }}
|
|
||||||
path: HellionChat/bin/Release/**/HellionChat/**
|
|
||||||
if-no-files-found: warn
|
|
||||||
retention-days: 14
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
name: CodeQL
|
|
||||||
|
|
||||||
# Replaces the GitHub default-setup CodeQL scan. The default setup runs
|
|
||||||
# without resolving the Dalamud assemblies (they live in a user-AppData
|
|
||||||
# path) and reports "Low C# analysis quality" because call-target
|
|
||||||
# resolution sits at ~64%. This workflow downloads the Dalamud staging
|
|
||||||
# distribution before the build, runs a manual dotnet build, and then
|
|
||||||
# lets CodeQL analyse the fully-resolved compilation. Quality climbs
|
|
||||||
# back above the 85% thresholds.
|
|
||||||
#
|
|
||||||
# This workflow only consumes trusted inputs: the tag/branch ref via
|
|
||||||
# the standard checkout action, and the Dalamud distribution URL which
|
|
||||||
# is pinned to a goatcorp-controlled GitHub Pages target. No user-
|
|
||||||
# controlled event payload (issue title, PR body, commit message) flows
|
|
||||||
# into a run-step.
|
|
||||||
#
|
|
||||||
# Disable the default setup in the repo before this workflow lands:
|
|
||||||
# Settings -> Code security -> Code scanning -> "CodeQL analysis" tile
|
|
||||||
# -> Switch to advanced.
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
schedule:
|
|
||||||
- cron: '17 6 * * 1'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze-csharp:
|
|
||||||
name: Analyze (csharp)
|
|
||||||
runs-on: windows-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Setup .NET 10
|
|
||||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
|
||||||
with:
|
|
||||||
dotnet-version: 10.0.x
|
|
||||||
|
|
||||||
- name: Download Dalamud staging
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
|
||||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
|
||||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
|
||||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
||||||
with:
|
|
||||||
languages: csharp
|
|
||||||
build-mode: manual
|
|
||||||
queries: security-extended
|
|
||||||
|
|
||||||
- name: Restore
|
|
||||||
run: dotnet restore HellionChat/HellionChat.csproj
|
|
||||||
|
|
||||||
- name: Build (Release)
|
|
||||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
|
||||||
|
|
||||||
- name: Perform CodeQL analysis
|
|
||||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
||||||
with:
|
|
||||||
category: /language:csharp
|
|
||||||
|
|
||||||
analyze-actions:
|
|
||||||
name: Analyze (actions)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
||||||
with:
|
|
||||||
languages: actions
|
|
||||||
build-mode: none
|
|
||||||
|
|
||||||
- name: Perform CodeQL analysis
|
|
||||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
||||||
with:
|
|
||||||
category: /language:actions
|
|
||||||
@@ -34,10 +34,9 @@ jobs:
|
|||||||
announce:
|
announce:
|
||||||
name: Post changelog to Hellion Forge
|
name: Post changelog to Hellion Forge
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# The DISCORD_FORGE_WEBHOOK secret lives under Settings → Environments
|
# The DISCORD_FORGE_WEBHOOK secret is set as a repo-level Actions Secret
|
||||||
# → Webhook (case-sensitive). Without this declaration the secret is
|
# on Gitea (Settings → Actions → Secrets). Repo-level secrets are in
|
||||||
# not in scope for the job.
|
# scope for every job by default, no environment: declaration needed.
|
||||||
environment: Webhook
|
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -134,7 +133,7 @@ jobs:
|
|||||||
# ---------- Embed-Payload bauen ----------
|
# ---------- Embed-Payload bauen ----------
|
||||||
$payload = [ordered]@{
|
$payload = [ordered]@{
|
||||||
username = "Forge Herald"
|
username = "Forge Herald"
|
||||||
avatar_url = "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/icon.png"
|
avatar_url = "https://gitea.com/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png"
|
||||||
content = "<@&1500489631555260446>"
|
content = "<@&1500489631555260446>"
|
||||||
allowed_mentions = [ordered]@{
|
allowed_mentions = [ordered]@{
|
||||||
parse = @()
|
parse = @()
|
||||||
@@ -143,7 +142,7 @@ jobs:
|
|||||||
embeds = @(
|
embeds = @(
|
||||||
[ordered]@{
|
[ordered]@{
|
||||||
title = $title
|
title = $title
|
||||||
url = "https://github.com/JonKazama-Hellion/HellionChat/releases/tag/$tag"
|
url = "https://gitea.com/JonKazama-Hellion/HellionChat/releases/tag/$tag"
|
||||||
color = 12730636
|
color = 12730636
|
||||||
description = $description
|
description = $description
|
||||||
footer = [ordered]@{ text = $footerText }
|
footer = [ordered]@{ text = $footerText }
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ name: Release
|
|||||||
|
|
||||||
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
||||||
# current Dalamud staging branch, locates the latest.zip produced by
|
# current Dalamud staging branch, locates the latest.zip produced by
|
||||||
# DalamudPackager and attaches it to the matching GitHub Release.
|
# DalamudPackager and attaches it to the matching Gitea Release.
|
||||||
#
|
#
|
||||||
# User-controlled inputs touched by this workflow:
|
# User-controlled inputs touched by this workflow:
|
||||||
# - the tag name (filtered by on.tags = v*, validated again at runtime
|
# - the tag name (filtered by on.tags = v*, validated again at runtime
|
||||||
# against ^v\d+\.\d+\.\d+$ before being used in any string)
|
# against ^v\d+\.\d+\.\d+$ before being used in any string)
|
||||||
# All other values are either repo-controlled (paths under
|
# All other values are either repo-controlled (paths under
|
||||||
# HellionChat/bin/Release derived from Get-ChildItem) or pinned URLs to
|
# HellionChat/bin/Release derived from find / Get-ChildItem) or pinned
|
||||||
# goatcorp / GitHub. Nothing from a webhook event payload (issue/PR
|
# URLs to goatcorp / gitea. Nothing from a webhook event payload (issue/PR
|
||||||
# titles, commit messages, etc.) flows into a run-step.
|
# titles, commit messages, etc.) flows into a run-step.
|
||||||
|
#
|
||||||
|
# Linux runner: gitea.com Cloud Actions only ships ubuntu-latest. The
|
||||||
|
# plugin csproj targets net10.0-windows, `dotnet build` cross-compiles on
|
||||||
|
# Linux when the Dalamud staging assemblies sit under $(HOME)/.xlcore/...
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -33,7 +37,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Build and attach release ZIP
|
name: Build and attach release ZIP
|
||||||
runs-on: windows-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -52,27 +56,25 @@ jobs:
|
|||||||
dotnet-version: 10.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Download Dalamud staging
|
- name: Download Dalamud staging
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
mkdir -p "$hooks"
|
||||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
unzip -oq dalamud.zip -d "$hooks"
|
||||||
|
|
||||||
- name: Build (Release)
|
- name: Build (Release)
|
||||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
||||||
|
|
||||||
- name: Locate latest.zip
|
- name: Locate latest.zip
|
||||||
id: locate
|
id: locate
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
$zip = Get-ChildItem -Path HellionChat\bin\Release -Recurse -Filter latest.zip | Select-Object -First 1
|
zip="$(find HellionChat/bin/Release -name latest.zip -print -quit)"
|
||||||
if (-not $zip)
|
if [ -z "$zip" ]; then
|
||||||
{
|
echo "latest.zip not found under HellionChat/bin/Release" >&2
|
||||||
throw "latest.zip not found under HellionChat\bin\Release"
|
exit 1
|
||||||
}
|
fi
|
||||||
Write-Host "Found: $($zip.FullName)"
|
echo "Found: $zip"
|
||||||
"path=$($zip.FullName)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
echo "path=$zip" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# Build a release body from the matching changelog block in
|
# Build a release body from the matching changelog block in
|
||||||
# HellionChat.yaml plus a static install / docs footer. Fails the
|
# HellionChat.yaml plus a static install / docs footer. Fails the
|
||||||
@@ -150,8 +152,13 @@ jobs:
|
|||||||
Write-Host $body
|
Write-Host $body
|
||||||
Write-Host "----------------------------------------"
|
Write-Host "----------------------------------------"
|
||||||
|
|
||||||
- name: Attach to GitHub release
|
# Gitea-native release action. Creates the release if the tag has no
|
||||||
uses: softprops/action-gh-release@v3
|
# release yet, or updates the existing one. body_path provides the
|
||||||
|
# generated release body, files attaches latest.zip. The auto-injected
|
||||||
|
# GITHUB_TOKEN on Gitea Actions has Gitea-API scope and is sufficient
|
||||||
|
# for release write.
|
||||||
|
- name: Attach to Gitea release
|
||||||
|
uses: https://gitea.com/actions/release-action@main
|
||||||
with:
|
with:
|
||||||
# Explicit tag_name so the action targets the correct release in
|
# Explicit tag_name so the action targets the correct release in
|
||||||
# both push:tags (auto) and workflow_dispatch (manual recovery)
|
# both push:tags (auto) and workflow_dispatch (manual recovery)
|
||||||
@@ -160,5 +167,4 @@ jobs:
|
|||||||
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
|
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
|
||||||
files: ${{ steps.locate.outputs.path }}
|
files: ${{ steps.locate.outputs.path }}
|
||||||
body_path: release-body.md
|
body_path: release-body.md
|
||||||
fail_on_unmatched_files: true
|
api_key: ${{ secrets.GITHUB_TOKEN }}
|
||||||
generate_release_notes: false
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
.envrc
|
.envrc
|
||||||
!.env.example
|
!.env.example
|
||||||
.vscode/
|
.vscode/
|
||||||
scripts/
|
scripts/setup-dev-env.sh
|
||||||
|
|
||||||
# Local test project (stays out of the published plugin repo;
|
# Local test project (stays out of the published plugin repo;
|
||||||
# pure-function safety net for refactor cycles)
|
# pure-function safety net for refactor cycles)
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ to make a contribution land smoothly.
|
|||||||
- Read the [README](README.md) so you understand the scope: a
|
- Read the [README](README.md) so you understand the scope: a
|
||||||
privacy-focused, EUPL-1.2-licensed Dalamud plugin that intentionally
|
privacy-focused, EUPL-1.2-licensed Dalamud plugin that intentionally
|
||||||
removes the upstream webinterface and ships privacy-first defaults.
|
removes the upstream webinterface and ships privacy-first defaults.
|
||||||
- Read [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md). Cherry-picks
|
- Read [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md). Active
|
||||||
from upstream Chat 2 are selective and deliberate; not everything
|
cherry-picking from upstream Chat 2 has ended in the v1.4.x cycle;
|
||||||
that lands there belongs here.
|
HellionChat continues as an independent codebase. Existing
|
||||||
|
upstream-derived code keeps its attribution. New contributions
|
||||||
|
stand on their own and do not need to be cherry-pick-compatible.
|
||||||
- Read [`SECURITY.md`](SECURITY.md). Anything security-sensitive goes
|
- Read [`SECURITY.md`](SECURITY.md). Anything security-sensitive goes
|
||||||
through a private advisory, never a public issue or PR.
|
through a private advisory, never a public issue or PR.
|
||||||
- Read the [Code of Conduct](CODE_OF_CONDUCT.md).
|
- Read the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||||
@@ -43,9 +45,11 @@ to make a contribution land smoothly.
|
|||||||
"Was gegenüber Chat 2 fehlt".
|
"Was gegenüber Chat 2 fehlt".
|
||||||
- Features that bypass the privacy filter or weaken the default
|
- Features that bypass the privacy filter or weaken the default
|
||||||
retention behaviour without an explicit, documented opt-in.
|
retention behaviour without an explicit, documented opt-in.
|
||||||
- Sweeping refactors that touch large parts of the codebase. They make
|
- Sweeping refactors that touch large parts of the codebase. The
|
||||||
selective upstream cherry-picks much harder and the maintenance cost
|
maintenance cost outweighs the benefit for a one-person project.
|
||||||
outweighs the benefit for a one-person project.
|
(This used to be doubly important because of the upstream
|
||||||
|
cherry-pick path; that path is closed now, but the rule still
|
||||||
|
holds on its own merits.)
|
||||||
- AI-generated code dropped in without disclosure or human review. See
|
- AI-generated code dropped in without disclosure or human review. See
|
||||||
[`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) for how I handle
|
[`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) for how I handle
|
||||||
AI assistance on my side; I expect comparable transparency from
|
AI assistance on my side; I expect comparable transparency from
|
||||||
@@ -117,9 +121,15 @@ Hellion-specific strings live in
|
|||||||
direct pull requests.
|
direct pull requests.
|
||||||
|
|
||||||
The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx`
|
The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx`
|
||||||
are **not** translated here. They are owned by the upstream project
|
are **not** translated here. They are kept as-is from the last
|
||||||
and synced in via cherry-pick. Please contribute those to
|
upstream sync and remain the work of the Chat 2 Crowdin community.
|
||||||
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo) instead.
|
Active cherry-picking from upstream ended in the v1.4.x cycle (see
|
||||||
|
[`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md)), so future
|
||||||
|
translation improvements to those upstream strings will not flow
|
||||||
|
into HellionChat automatically anymore. If you have improvements
|
||||||
|
for the original Chat 2 strings, please contribute them to
|
||||||
|
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo)
|
||||||
|
directly.
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
@@ -145,3 +155,19 @@ I respond on weekdays during European business hours and take weekends
|
|||||||
and FFXIV patch days off. A pull request that sits for a few days has
|
and FFXIV patch days off. A pull request that sits for a few days has
|
||||||
not been ignored. Pinging once after a week is fine; please do not
|
not been ignored. Pinging once after a week is fine; please do not
|
||||||
ping daily.
|
ping daily.
|
||||||
|
|
||||||
|
## First-time setup
|
||||||
|
|
||||||
|
After cloning, run once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-hooks.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This wires `core.hooksPath` to `.githooks/`. The pre-push hook runs preflight
|
||||||
|
(versions/manifest/changelog/build).
|
||||||
|
|
||||||
|
### Test suite
|
||||||
|
|
||||||
|
The plugin's test suite lives in a separate local repository and is not part of
|
||||||
|
this codebase. If you need access for development, contact the maintainer.
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ namespace HellionChat.Branding;
|
|||||||
// Centralised so a future invite rotation only touches one file. The same
|
// Centralised so a future invite rotation only touches one file. The same
|
||||||
// link is currently hard-coded in repo.json, README.md, SUPPORT.md,
|
// link is currently hard-coded in repo.json, README.md, SUPPORT.md,
|
||||||
// CONTRIBUTORS.md and HellionChat.yaml — those will be migrated to consume
|
// CONTRIBUTORS.md and HellionChat.yaml — those will be migrated to consume
|
||||||
// this constant in a separate housekeeping sweep, but that's out of scope
|
// this constant in a separate housekeeping sweep
|
||||||
// for this Cycle.
|
|
||||||
internal static class BrandingLinks
|
internal static class BrandingLinks
|
||||||
{
|
{
|
||||||
public const string HellionForgeDiscordInvite = "https://discord.gg/X9V7Kcv5gR";
|
public const string HellionForgeDiscordInvite = "https://discord.gg/X9V7Kcv5gR";
|
||||||
|
|||||||
@@ -16,6 +16,18 @@ public unsafe class ChatBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void SendMessage(string message)
|
public static void SendMessage(string message)
|
||||||
|
{
|
||||||
|
var bytes = ValidateMessage(message);
|
||||||
|
SendMessageUnsafe(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation split out so the deterministic checks (UTF-8 length, sanitise
|
||||||
|
// round-trip) can run in xUnit without ClientStructs game memory. The
|
||||||
|
// sanitiser is injectable so tests can pin throw behaviour without invoking
|
||||||
|
// Utf8String->SanitizeString, which only resolves in-process. Returns the
|
||||||
|
// already-encoded bytes so SendMessage doesn't pay GetBytes twice.
|
||||||
|
// TEST-MIRROR: ../../../Hellion Build test/GameFunctions/ChatBoxTests.cs
|
||||||
|
internal static byte[] ValidateMessage(string message, Func<string, string>? sanitiserOverride = null)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.UTF8.GetBytes(message);
|
var bytes = Encoding.UTF8.GetBytes(message);
|
||||||
if (bytes.Length == 0)
|
if (bytes.Length == 0)
|
||||||
@@ -24,10 +36,11 @@ public unsafe class ChatBox
|
|||||||
if (bytes.Length > 500)
|
if (bytes.Length > 500)
|
||||||
throw new ArgumentException(Language.ChatBox_Error_Too_Long, nameof(message));
|
throw new ArgumentException(Language.ChatBox_Error_Too_Long, nameof(message));
|
||||||
|
|
||||||
if (message.Length != SanitiseText(message).Length)
|
var sanitiser = sanitiserOverride ?? SanitiseText;
|
||||||
|
if (message.Length != sanitiser(message).Length)
|
||||||
throw new ArgumentException(Language.ChatBox_Error_Invalid, nameof(message));
|
throw new ArgumentException(Language.ChatBox_Error_Invalid, nameof(message));
|
||||||
|
|
||||||
SendMessageUnsafe(bytes);
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string SanitiseText(string text)
|
private static string SanitiseText(string text)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
[PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
|
[PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
|
||||||
[PluginService] public static IPlayerState PlayerState { get; private set; } = null!;
|
[PluginService] public static IPlayerState PlayerState { get; private set; } = null!;
|
||||||
[PluginService] public static ISeStringEvaluator Evaluator { get; private set; } = null!;
|
[PluginService] public static ISeStringEvaluator Evaluator { get; private set; } = null!;
|
||||||
|
[PluginService] public static ISelfTestRegistry SelfTestRegistry { get; private set; } = null!;
|
||||||
|
|
||||||
public static Configuration Config = null!;
|
public static Configuration Config = null!;
|
||||||
public static FileDialogManager FileDialogManager { get; private set; } = null!;
|
public static FileDialogManager FileDialogManager { get; private set; } = null!;
|
||||||
@@ -348,6 +349,8 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TEST-MIRROR: Hellion Build test/_Helpers/MigrationLogic.cs (local-only repo)
|
||||||
|
// If you change the formula here, change the mirror — tests assert the contract.
|
||||||
if (oldWindowAlpha != 100f
|
if (oldWindowAlpha != 100f
|
||||||
&& Math.Abs(Config.WindowOpacity - 0.85f) < 0.001f)
|
&& Math.Abs(Config.WindowOpacity - 0.85f) < 0.001f)
|
||||||
{
|
{
|
||||||
@@ -452,6 +455,12 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
ThemeRegistry = new Themes.ThemeRegistry(customThemesDir);
|
ThemeRegistry = new Themes.ThemeRegistry(customThemesDir);
|
||||||
ThemeRegistry.Switch(Config.Theme);
|
ThemeRegistry.Switch(Config.Theme);
|
||||||
|
|
||||||
|
// SelfTest hooks live alongside the live registry — the steps
|
||||||
|
// poll Active per frame and need the registry already wired.
|
||||||
|
SelfTestRegistry.RegisterTestSteps([
|
||||||
|
new SelfTests.ThemeSwitchSelfTestStep(this),
|
||||||
|
]);
|
||||||
|
|
||||||
// Plugin integrations register their IPC subscribers up-front so
|
// Plugin integrations register their IPC subscribers up-front so
|
||||||
// Ready/Disposing events from the target plugins are caught from
|
// Ready/Disposing events from the target plugins are caught from
|
||||||
// the very first frame, even if the user's Honorific reloads
|
// the very first frame, even if the user's Honorific reloads
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Plugin.SelfTest;
|
||||||
|
using HellionChat.Themes;
|
||||||
|
|
||||||
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
|
// Validates the runtime theme-switch contract from the user side. The
|
||||||
|
// caller toggles the active theme via Settings -> Theme & Layout, the
|
||||||
|
// step polls ThemeRegistry.Active per frame and only passes once the
|
||||||
|
// slug has moved away from the initial value and back. The ABGR cache
|
||||||
|
// is sanity-checked on every frame: a freshly switched theme must carry
|
||||||
|
// a populated cache, otherwise Switch() forgot the recompute and the UI
|
||||||
|
// would still draw, just with all-transparent slots.
|
||||||
|
internal sealed class ThemeSwitchSelfTestStep : ISelfTestStep
|
||||||
|
{
|
||||||
|
private readonly Plugin plugin;
|
||||||
|
private string? initialSlug;
|
||||||
|
private bool switchedAway;
|
||||||
|
|
||||||
|
public ThemeSwitchSelfTestStep(Plugin plugin)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => "Hellion Chat - Theme switch";
|
||||||
|
|
||||||
|
public SelfTestStepResult RunStep()
|
||||||
|
{
|
||||||
|
var registry = this.plugin.ThemeRegistry;
|
||||||
|
if (registry is null)
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
|
||||||
|
var active = registry.Active;
|
||||||
|
if (active is null)
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
|
||||||
|
if (!HasPopulatedCache(active))
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
|
||||||
|
if (this.initialSlug is null)
|
||||||
|
{
|
||||||
|
this.initialSlug = active.Slug;
|
||||||
|
ImGui.Text($"Initial theme: \"{this.initialSlug}\". Open Settings -> Theme & Layout and pick a different theme.");
|
||||||
|
return SelfTestStepResult.Waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.switchedAway)
|
||||||
|
{
|
||||||
|
if (!string.Equals(active.Slug, this.initialSlug, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
this.switchedAway = true;
|
||||||
|
return SelfTestStepResult.Waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Text($"Switch the active theme away from \"{this.initialSlug}\".");
|
||||||
|
return SelfTestStepResult.Waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(active.Slug, this.initialSlug, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ImGui.Text($"Switch back to \"{this.initialSlug}\" to finish the test.");
|
||||||
|
return SelfTestStepResult.Waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SelfTestStepResult.Pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUp()
|
||||||
|
{
|
||||||
|
this.initialSlug = null;
|
||||||
|
this.switchedAway = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any non-zero slot proves the cache was actually recomputed for the
|
||||||
|
// current theme. We don't compare against a reference, because custom
|
||||||
|
// themes can legitimately share slot values with a built-in.
|
||||||
|
private static bool HasPopulatedCache(Theme theme)
|
||||||
|
{
|
||||||
|
var cache = theme.AbgrCache;
|
||||||
|
return (cache.Primary
|
||||||
|
| cache.WindowBg
|
||||||
|
| cache.TextPrimary
|
||||||
|
| cache.Border) != 0u;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using HellionChat._Helpers;
|
||||||
using HellionChat.Code;
|
using HellionChat.Code;
|
||||||
using HellionChat.Util;
|
using HellionChat.Util;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
@@ -89,60 +90,42 @@ public sealed class ChatInputBar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SubmitCompact(Tab tab)
|
// TEST-MIRROR: ../_Helpers/CompactInputSubmitter.cs
|
||||||
{
|
private void SubmitCompact(Tab tab) =>
|
||||||
if (string.IsNullOrWhiteSpace(_state.Buffer))
|
CompactInputSubmitter.TrySubmit(_state, tab, _host.SendChatBoxFromExternal);
|
||||||
return;
|
|
||||||
|
|
||||||
var text = _state.Buffer;
|
// History-navigation callback for the compact input. Cursor math is
|
||||||
_state.Buffer = string.Empty;
|
// delegated to CompactInputHistoryNavigator; only the ImGui buffer
|
||||||
_state.HistoryCursor = -1;
|
// splice stays here because it needs the live callback data.
|
||||||
_host.SendChatBoxFromExternal(tab, text);
|
// TEST-MIRROR: ../_Helpers/CompactInputHistoryNavigator.cs
|
||||||
}
|
|
||||||
|
|
||||||
// History-navigation callback for the compact input. Mirrors the main
|
|
||||||
// window's logic but operates on _state.HistoryCursor and the shared
|
|
||||||
// InputHistoryService. Index semantics match v0.5.x InputBacklog:
|
|
||||||
// 0 = oldest, Count-1 = newest.
|
|
||||||
private int CompactCallback(scoped ref ImGuiInputTextCallbackData data)
|
private int CompactCallback(scoped ref ImGuiInputTextCallbackData data)
|
||||||
{
|
{
|
||||||
if (data.EventFlag != ImGuiInputTextFlags.CallbackHistory)
|
if (data.EventFlag != ImGuiInputTextFlags.CallbackHistory)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var prev = _state.HistoryCursor;
|
var direction = data.EventKey switch
|
||||||
switch (data.EventKey)
|
|
||||||
{
|
{
|
||||||
case ImGuiKey.UpArrow:
|
ImGuiKey.UpArrow => CompactInputHistoryNavigator.Direction.Up,
|
||||||
switch (_state.HistoryCursor)
|
ImGuiKey.DownArrow => CompactInputHistoryNavigator.Direction.Down,
|
||||||
{
|
_ => (CompactInputHistoryNavigator.Direction?)null,
|
||||||
case -1:
|
};
|
||||||
var offset = 0;
|
if (direction is null)
|
||||||
if (!string.IsNullOrWhiteSpace(_state.Buffer))
|
|
||||||
{
|
|
||||||
InputHistoryService.Push(_state.Buffer);
|
|
||||||
offset = 1;
|
|
||||||
}
|
|
||||||
_state.HistoryCursor = InputHistoryService.Count - 1 - offset;
|
|
||||||
break;
|
|
||||||
case > 0:
|
|
||||||
_state.HistoryCursor--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ImGuiKey.DownArrow:
|
|
||||||
if (_state.HistoryCursor != -1)
|
|
||||||
if (++_state.HistoryCursor >= InputHistoryService.Count)
|
|
||||||
_state.HistoryCursor = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev == _state.HistoryCursor)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var historyStr = InputHistoryService.GetByCursor(_state.HistoryCursor) ?? string.Empty;
|
var (cursor, replacement) = CompactInputHistoryNavigator.Navigate(
|
||||||
data.DeleteChars(0, data.BufTextLen);
|
direction.Value,
|
||||||
data.InsertChars(0, historyStr);
|
_state.HistoryCursor,
|
||||||
|
_state.Buffer,
|
||||||
|
() => InputHistoryService.Count,
|
||||||
|
InputHistoryService.Push,
|
||||||
|
InputHistoryService.GetByCursor);
|
||||||
|
|
||||||
|
_state.HistoryCursor = cursor;
|
||||||
|
if (replacement is null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
data.DeleteChars(0, data.BufTextLen);
|
||||||
|
data.InsertChars(0, replacement);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HellionChat._Helpers;
|
||||||
|
|
||||||
|
// Pure-helper mirror of the compact pop-out history-navigation cursor
|
||||||
|
// math. The original CompactCallback was tangled with ImGuiInputTextCallbackData
|
||||||
|
// (DeleteChars/InsertChars), which can't be exercised in xUnit. The
|
||||||
|
// ImGui buffer mutation stays at the call site; only the deterministic
|
||||||
|
// cursor-and-replacement decision lives here.
|
||||||
|
//
|
||||||
|
// Index semantics match InputHistoryService:
|
||||||
|
// index 0 = oldest entry
|
||||||
|
// index Count - 1 = newest entry
|
||||||
|
// cursor == -1 = "not browsing history"
|
||||||
|
//
|
||||||
|
// TEST-MIRROR: ../../../Hellion Build test/Ui/CompactInputHistoryNavigatorTests.cs
|
||||||
|
public static class CompactInputHistoryNavigator
|
||||||
|
{
|
||||||
|
public enum Direction { Up, Down }
|
||||||
|
|
||||||
|
// replacement == null means: caller must NOT touch the buffer. This
|
||||||
|
// distinguishes "cursor unchanged, leave the user's typing alone"
|
||||||
|
// from "cursor moved to an empty slot, clear the buffer".
|
||||||
|
public static (int cursor, string? replacement) Navigate(
|
||||||
|
Direction direction,
|
||||||
|
int currentCursor,
|
||||||
|
string currentBuffer,
|
||||||
|
Func<int> getCount,
|
||||||
|
Action<string> push,
|
||||||
|
Func<int, string?> getByCursor)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(getCount);
|
||||||
|
ArgumentNullException.ThrowIfNull(push);
|
||||||
|
ArgumentNullException.ThrowIfNull(getByCursor);
|
||||||
|
|
||||||
|
var prev = currentCursor;
|
||||||
|
var next = currentCursor;
|
||||||
|
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case Direction.Up:
|
||||||
|
if (currentCursor == -1)
|
||||||
|
{
|
||||||
|
// First Up press from a fresh buffer: stash whatever
|
||||||
|
// the user typed so they can recover it after browsing.
|
||||||
|
var offset = 0;
|
||||||
|
if (!string.IsNullOrWhiteSpace(currentBuffer))
|
||||||
|
{
|
||||||
|
push(currentBuffer);
|
||||||
|
offset = 1;
|
||||||
|
}
|
||||||
|
next = getCount() - 1 - offset;
|
||||||
|
}
|
||||||
|
else if (currentCursor > 0)
|
||||||
|
{
|
||||||
|
next--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Direction.Down:
|
||||||
|
if (currentCursor != -1)
|
||||||
|
{
|
||||||
|
next++;
|
||||||
|
if (next >= getCount())
|
||||||
|
next = -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev == next)
|
||||||
|
return (next, null);
|
||||||
|
|
||||||
|
var replacement = getByCursor(next) ?? string.Empty;
|
||||||
|
return (next, replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using HellionChat.Ui;
|
||||||
|
|
||||||
|
namespace HellionChat._Helpers;
|
||||||
|
|
||||||
|
// Pure-helper mirror of the compact pop-out submit flow. ChatInputBar's
|
||||||
|
// SubmitCompact used to inline this against a sealed ChatLogWindow, which
|
||||||
|
// blocks Moq-based isolation. Lifting the deterministic part into a POCO
|
||||||
|
// keeps the production call site a one-liner while letting xUnit assert
|
||||||
|
// the buffer/cursor reset and the sender contract directly.
|
||||||
|
// TEST-MIRROR: ../../../Hellion Build test/Ui/CompactInputSubmitterTests.cs
|
||||||
|
public static class CompactInputSubmitter
|
||||||
|
{
|
||||||
|
public static bool TrySubmit(InputState state, Tab tab, Action<Tab, string> sender)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(state);
|
||||||
|
ArgumentNullException.ThrowIfNull(tab);
|
||||||
|
ArgumentNullException.ThrowIfNull(sender);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(state.Buffer))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var text = state.Buffer;
|
||||||
|
state.Buffer = string.Empty;
|
||||||
|
state.HistoryCursor = -1;
|
||||||
|
sender(tab, text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 45 KiB |
@@ -35,9 +35,19 @@ edits minimal, isolated to clearly-marked Hellion files, and reversible.
|
|||||||
|
|
||||||
Concrete example: when API 15 hit, I cherry-picked your fix for the
|
Concrete example: when API 15 hit, I cherry-picked your fix for the
|
||||||
BetterTTV emote regression with `git cherry-pick -x` so authorship and
|
BetterTTV emote regression with `git cherry-pick -x` so authorship and
|
||||||
co-author trail stay intact. That is the standard I want to keep using as
|
co-author trail stay intact. That was the standard I held to as long
|
||||||
long as both projects are alive. You should never have to look at this
|
as cherry-picking was viable, and you should never have to look at
|
||||||
fork and wonder if I quietly ate your work.
|
this fork and wonder if I quietly ate your work.
|
||||||
|
|
||||||
|
With ChatTwo entering its rework cycle, the active cherry-pick
|
||||||
|
pipeline is closed since v1.4.x — see [docs/UPSTREAM_SYNC.md](docs/UPSTREAM_SYNC.md)
|
||||||
|
for the full reasoning. The attribution standard stays exactly the
|
||||||
|
same: every existing `(cherry picked from commit ...)` line remains
|
||||||
|
in the git history, the EUPL-1.2 anchor lines in source files are
|
||||||
|
untouched, and this NOTICE.md remains canonical. If anything from
|
||||||
|
this point forward originates from Chat 2 it will be a hand-port at
|
||||||
|
most, called out as such in the commit message and source comments,
|
||||||
|
not a `git cherry-pick`.
|
||||||
|
|
||||||
If anything in this fork ever steps on something you would not be okay
|
If anything in this fork ever steps on something you would not be okay
|
||||||
with, please reach out and I will fix it. Genuinely. The list of contacts
|
with, please reach out and I will fix it. Genuinely. The list of contacts
|
||||||
@@ -62,8 +72,10 @@ full-history-by-default position fits a much larger one, including the
|
|||||||
roleplaying community where chat archive is part of the play experience.
|
roleplaying community where chat archive is part of the play experience.
|
||||||
Trying to upstream HellionChat's defaults would have meant arguing that
|
Trying to upstream HellionChat's defaults would have meant arguing that
|
||||||
Chat 2's defaults are wrong, and they are not. They are right for the
|
Chat 2's defaults are wrong, and they are not. They are right for the
|
||||||
user base ChatTwo serves. So I keep the fork separate, attribute clearly,
|
user base ChatTwo serves. So I keep the fork separate and attribute
|
||||||
and pull selected upstream patches when they apply.
|
clearly. Active cherry-picking from upstream stopped in the v1.4.x
|
||||||
|
cycle once Chat 2's rework made selective patches no longer portable;
|
||||||
|
the existing cherry-pick trail stays in the git history.
|
||||||
|
|
||||||
## Why HellionChat left the GitHub fork network
|
## Why HellionChat left the GitHub fork network
|
||||||
|
|
||||||
@@ -72,8 +84,9 @@ that a fork is either a development branch or a dead mirror. HellionChat
|
|||||||
is neither. It is an independently-maintained EUPL-1.2 fork with its own
|
is neither. It is an independently-maintained EUPL-1.2 fork with its own
|
||||||
release cadence, its own custom repo, its own user base. Detaching the
|
release cadence, its own custom repo, its own user base. Detaching the
|
||||||
fork-network relation just makes the situation honest. The git history,
|
fork-network relation just makes the situation honest. The git history,
|
||||||
the cherry-pick trail, and the attribution stay exactly the same. The
|
the existing cherry-pick trail, and the attribution stay exactly the
|
||||||
only thing that changes is the GitHub UI no longer says "forked from".
|
same. The only thing that changes is the GitHub UI no longer says
|
||||||
|
"forked from".
|
||||||
|
|
||||||
## Trademarks and naming
|
## Trademarks and naming
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Hellion Chat ist ein Privacy-First-Plugin auf dem Chat-2-Fundament. Der größte
|
|||||||
|
|
||||||
Der Daten-Handling-Fokus liegt auf den DSGVO/EU-, US- und JP-Regelungen, soweit für ein Chat-Plugin praktisch umsetzbar: Speicherzeit pro Kanal, granulare Filter, Selbstauskunft per Export. Eine ausführliche Auflistung steht in [`PRIVACY.md`](PRIVACY.md).
|
Der Daten-Handling-Fokus liegt auf den DSGVO/EU-, US- und JP-Regelungen, soweit für ein Chat-Plugin praktisch umsetzbar: Speicherzeit pro Kanal, granulare Filter, Selbstauskunft per Export. Eine ausführliche Auflistung steht in [`PRIVACY.md`](PRIVACY.md).
|
||||||
|
|
||||||
Eigenständiges Repository, EUPL-1.2-lizenziert. Mit v1.0.0 ist der Standalone-Cut abgeschlossen: eigener Namespace `HellionChat.*`, eigene IPC-Kanäle, eigene Source-Tree-Struktur. Distribution über Custom-Repo. Selektive Cherry-Picks von Upstream-Chat-2 nach Bedarf, dokumentiert in [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md).
|
Eigenständiges Repository, EUPL-1.2-lizenziert. Mit v1.0.0 ist der Standalone-Cut abgeschlossen: eigener Namespace `HellionChat.*`, eigene IPC-Kanäle, eigene Source-Tree-Struktur. Distribution über Custom-Repo. Aktiver Upstream-Sync ist mit dem v1.4.x-Cycle beendet: Chat 2 befindet sich in einem grundlegenden Rework und Cherry-Picks sind nicht mehr portierbar. Hellion Chat geht ab da als unabhängige Codebase weiter, Hintergrund und Attribution in [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md).
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ Im Repo-Root liegen die Standard-Repository-Dokumente, vertiefende Dokumentation
|
|||||||
| [`docs/LEARNING-JOURNEY.md`](docs/LEARNING-JOURNEY.md) | Entwicklungsgeschichte, vom Web-Stack zu C# / Dalamud, was ich aus dem Fork gelernt habe. |
|
| [`docs/LEARNING-JOURNEY.md`](docs/LEARNING-JOURNEY.md) | Entwicklungsgeschichte, vom Web-Stack zu C# / Dalamud, was ich aus dem Fork gelernt habe. |
|
||||||
| [`docs/IPC.md`](docs/IPC.md) | IPC-Kanal-Reference, Tuple-Payload-Felder, Migrations-Diff für Drittplugins. |
|
| [`docs/IPC.md`](docs/IPC.md) | IPC-Kanal-Reference, Tuple-Payload-Felder, Migrations-Diff für Drittplugins. |
|
||||||
| [`docs/THEME-AUTHORING.md`](docs/THEME-AUTHORING.md) | Theme-Engine-Authoring-Guide (EN): JSON-Schema, Color- und Layout-Slots, Channel-Identity-Regeln, Validierung. |
|
| [`docs/THEME-AUTHORING.md`](docs/THEME-AUTHORING.md) | Theme-Engine-Authoring-Guide (EN): JSON-Schema, Color- und Layout-Slots, Channel-Identity-Regeln, Validierung. |
|
||||||
| [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md) | Cherry-Pick-Policy gegenüber Chat 2. |
|
| [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md) | Upstream-Sync-Stand: Cherry-Pick-Pipeline seit v1.4.x geschlossen, Attribution intakt. |
|
||||||
| [`docs/THIRD_PARTY_NOTICES.md`](docs/THIRD_PARTY_NOTICES.md) | NuGet-Dependencies mit Lizenzen, Bundled Assets, Network-Status pro Komponente. |
|
| [`docs/THIRD_PARTY_NOTICES.md`](docs/THIRD_PARTY_NOTICES.md) | NuGet-Dependencies mit Lizenzen, Bundled Assets, Network-Status pro Komponente. |
|
||||||
| [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) | Offenlegung der KI-Unterstützung im Entwicklungsprozess. |
|
| [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) | Offenlegung der KI-Unterstützung im Entwicklungsprozess. |
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ aktuellen Stand getestet.
|
|||||||
|
|
||||||
Hellion Chat ist EUPL-1.2-lizenziert. Konzept-Imports aus AGPL-3.0-Plugins
|
Hellion Chat ist EUPL-1.2-lizenziert. Konzept-Imports aus AGPL-3.0-Plugins
|
||||||
(z.B. XIV Instant Messenger) sind ausschließlich architektonische
|
(z.B. XIV Instant Messenger) sind ausschließlich architektonische
|
||||||
Inspiration, kein Code-Port. Imports aus dem GPL-3.0-kompatiblen
|
Inspiration, kein Code-Port. Code-Imports aus dem Upstream-Bestand
|
||||||
Upstream-Bestand laufen weiter über
|
sind seit v1.4.x abgeschlossen, weil Chat 2 in einem grundlegenden
|
||||||
[`UPSTREAM_SYNC.md`](UPSTREAM_SYNC.md).
|
Rework ist und selektive Patches nicht mehr sauber portierbar sind.
|
||||||
|
Stand und Begründung in [`UPSTREAM_SYNC.md`](UPSTREAM_SYNC.md).
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ HellionChat is a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo)
|
|||||||
by Infiziert90 (Infi) and Anna Clemens, also licensed under EUPL-1.2.
|
by Infiziert90 (Infi) and Anna Clemens, also licensed under EUPL-1.2.
|
||||||
The bulk of the code, including the message store architecture, the
|
The bulk of the code, including the message store architecture, the
|
||||||
channel logic, the hook system and the ImGui chat window, originates
|
channel logic, the hook system and the ImGui chat window, originates
|
||||||
from upstream. See `../NOTICE.md` and `UPSTREAM_SYNC.md` for the
|
from upstream. See `../NOTICE.md` for the attribution; `UPSTREAM_SYNC.md`
|
||||||
attribution and the cherry-pick policy.
|
documents the upstream-sync history, including the close of active
|
||||||
|
cherry-picking in the v1.4.x cycle.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
HellionChat is a standalone EUPL-1.2 plugin that originated from
|
HellionChat is a standalone EUPL-1.2 plugin that originated from
|
||||||
[Chat 2](https://github.com/Infiziert90/ChatTwo). Since v1.0.0 it
|
[Chat 2](https://github.com/Infiziert90/ChatTwo). Since v1.0.0 it
|
||||||
lives under its own namespace, IPC channels and source tree. I no
|
lives under its own namespace, IPC channels and source tree. The
|
||||||
longer track upstream as a Git fork, but I do monitor Chat 2 commits
|
active cherry-pick pipeline from upstream Chat 2 is closed since
|
||||||
regularly and cherry-pick selectively where it makes sense.
|
the v1.4.x cycle.
|
||||||
|
|
||||||
This document covers how that works so anyone (including future-me)
|
This document covers what that means, why I closed it, and what
|
||||||
can do it cleanly.
|
stays in place.
|
||||||
|
|
||||||
## A Word on Intent
|
## A Word on Intent
|
||||||
|
|
||||||
@@ -28,99 +28,77 @@ new UI from scratch and making deliberate architectural decisions that
|
|||||||
pull in a different direction. Some upstream patches will simply stop
|
pull in a different direction. Some upstream patches will simply stop
|
||||||
applying cleanly and that is expected.
|
applying cleanly and that is expected.
|
||||||
|
|
||||||
## One-Time Setup
|
## Why Cherry-Picking Stopped in v1.4.x
|
||||||
|
|
||||||
Add the upstream repo as a remote on a fresh clone:
|
Two things converged:
|
||||||
|
|
||||||
```bash
|
1. **Chat 2 is in a rework cycle.** Infi mentioned directly that
|
||||||
git remote add upstream https://github.com/Infiziert90/ChatTwo.git
|
parts of ChatTwo are being reworked and "stuff may not be able to
|
||||||
git fetch upstream
|
be cherry picked anymore." Once the upstream code paths I would
|
||||||
```
|
pull from no longer exist in the same shape, `git cherry-pick`
|
||||||
|
stops being a meaningful tool — what would land would not be the
|
||||||
|
change Infi wrote, it would be a hand-port of his concept.
|
||||||
|
2. **HellionChat has drifted enough that selective patches require
|
||||||
|
adaptation anyway.** The UI is being rebuilt, the theme engine
|
||||||
|
sits on top of HellionStyle which has no upstream equivalent, the
|
||||||
|
privacy filter changes how messages flow through MessageManager.
|
||||||
|
Even before the rework was announced, more and more upstream
|
||||||
|
patches needed adaptation rather than a clean apply.
|
||||||
|
|
||||||
Verify both remotes are wired up:
|
Together those two points mean continuing to call this an "active
|
||||||
|
cherry-pick pipeline" was no longer honest. So I closed it.
|
||||||
|
|
||||||
```bash
|
## What Closing the Pipeline Means in Practice
|
||||||
git remote -v
|
|
||||||
# origin https://github.com/JonKazama-Hellion/HellionChat.git (fetch)
|
|
||||||
# origin https://github.com/JonKazama-Hellion/HellionChat.git (push)
|
|
||||||
# upstream https://github.com/Infiziert90/ChatTwo.git (fetch)
|
|
||||||
# upstream https://github.com/Infiziert90/ChatTwo.git (push)
|
|
||||||
```
|
|
||||||
|
|
||||||
`upstream` is read-only. Never push to it.
|
- The `upstream` git remote was removed locally on 2026-05-08.
|
||||||
|
Anyone setting up a fresh clone does **not** add it back.
|
||||||
|
- New commits will not carry `(cherry picked from commit ...)`
|
||||||
|
trailers. Anything that originates from Chat 2 from this point
|
||||||
|
forward will be a hand-port at most, and it gets called out as
|
||||||
|
such in its own commit message and in the relevant source comments.
|
||||||
|
- The existing cherry-pick trail stays in the git history exactly as
|
||||||
|
it is. Every `(cherry picked from commit ...)` line that was added
|
||||||
|
with `-x` in earlier releases remains intact; that is the
|
||||||
|
attribution paper trail and removing it would be wrong.
|
||||||
|
|
||||||
## Reviewing What Is New Upstream
|
## What Does Not Change
|
||||||
|
|
||||||
Before any feature cycle I run a quick check:
|
- **EUPL-1.2 anchor lines in source files.** Files that originated
|
||||||
|
from Chat 2 keep their licence headers and any "based on
|
||||||
|
Infiziert90/ChatTwo" notice exactly as they are. The licence
|
||||||
|
obligations under EUPL-1.2 do not lapse because cherry-picking
|
||||||
|
stopped.
|
||||||
|
- **NOTICE.md** stays canonical. Attribution to Infi and Anna for the
|
||||||
|
message store, channel logic, hook system, ImGui chat window and
|
||||||
|
the localisation infrastructure remains the foundation statement of
|
||||||
|
this fork.
|
||||||
|
- **README acknowledgements.** The Acknowledgements section in
|
||||||
|
`README.md`, the maintainer thanks in the About tab, and the
|
||||||
|
`Language.*.resx` Crowdin translator credit list all stay as they
|
||||||
|
are.
|
||||||
|
- **The original `Language.*.resx` files** remain in the source tree
|
||||||
|
in their last upstream-sync state. They are the work of the Chat 2
|
||||||
|
Crowdin community and the existing translations stay valuable. They
|
||||||
|
will not receive automatic upstream updates anymore — see
|
||||||
|
CONTRIBUTING.md for what that means for translators.
|
||||||
|
|
||||||
```bash
|
## What Could Re-Open Later
|
||||||
git fetch upstream
|
|
||||||
git log --oneline main..upstream/main | head -30
|
|
||||||
```
|
|
||||||
|
|
||||||
That shows every commit Infi or contributors landed since the last
|
If Chat 2's rework lands and stabilises, and there is a piece of
|
||||||
sync. I read the messages and decide which ones apply to HellionChat.
|
upstream code that I genuinely want in HellionChat, the path forward
|
||||||
|
is **study and re-implement**, not cherry-pick. That means:
|
||||||
|
|
||||||
## What I Cherry-Pick
|
- Read the upstream change, understand the design, port the concept
|
||||||
|
to HellionChat's actual code paths.
|
||||||
|
- Credit the upstream author in the commit message and, if the
|
||||||
|
ported code is non-trivial, in a source-file comment.
|
||||||
|
- Pre-clear with Infi if the port is large enough to warrant a
|
||||||
|
conversation.
|
||||||
|
|
||||||
**Always:** security fixes, Dalamud API compatibility patches,
|
This is heavier than `git cherry-pick -x` and that is the point.
|
||||||
BetterTTV and emote-cache fixes, regression fixes for upstream
|
Cherry-picking was light because both codebases shared structure;
|
||||||
behaviour HellionChat still relies on.
|
once they do not, the proper attribution costs a real conversation
|
||||||
|
rather than a flag on a git command.
|
||||||
**Sometimes:** small bug fixes in `MessageManager.cs`,
|
|
||||||
`MessageStore.cs`, `ChatLogWindow.cs`, the Tabs system. These come in
|
|
||||||
when they touch code I have not heavily modified.
|
|
||||||
|
|
||||||
**Never:** webinterface changes (the entire webinterface tree is gone
|
|
||||||
in HellionChat), changes that conflict with the privacy filter, changes
|
|
||||||
that re-add upstream defaults I deliberately reversed (full-history
|
|
||||||
logging, Tell Exclusive defaults, etc.).
|
|
||||||
|
|
||||||
As HellionChat's UI moves further from the Chat 2 baseline, upstream
|
|
||||||
patches will increasingly require adaptation rather than a clean
|
|
||||||
apply. If a patch cannot be ported without breaking HellionChat
|
|
||||||
behaviour or the privacy model, I skip it rather than force a
|
|
||||||
compromised version in.
|
|
||||||
|
|
||||||
## How I Cherry-Pick
|
|
||||||
|
|
||||||
Always with `-x` so authorship and the original commit hash stay
|
|
||||||
visible:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b sync/upstream-<topic> main
|
|
||||||
git cherry-pick -x <upstream-commit-sha>
|
|
||||||
```
|
|
||||||
|
|
||||||
`-x` appends a `(cherry picked from commit <sha>)` line to the commit
|
|
||||||
message. That preserves upstream-author credit and lets anyone reading
|
|
||||||
`git log` trace the change back to Chat 2. Commit messages stay
|
|
||||||
identical to the upstream original; I do not rewrite them to match the
|
|
||||||
HellionChat format.
|
|
||||||
|
|
||||||
## Conflict Handling
|
|
||||||
|
|
||||||
When a cherry-pick conflicts:
|
|
||||||
|
|
||||||
1. Resolve by hand. Do not rewrite upstream code to match HellionChat
|
|
||||||
conventions; that is what the merge marker showed.
|
|
||||||
2. If the conflict is fundamental (touches code that no longer exists
|
|
||||||
in HellionChat), abort the cherry-pick and note why in the
|
|
||||||
relevant GitHub issue or backlog item. Some upstream patches are
|
|
||||||
simply not portable and that is fine.
|
|
||||||
3. After a clean resolve the commit message stays as-is, with the
|
|
||||||
`-x` footer Git appends automatically.
|
|
||||||
|
|
||||||
## Pushing the Sync
|
|
||||||
|
|
||||||
Cherry-picked commits go through the same review as any other change.
|
|
||||||
The sync branch lands in `main` via a no-fast-forward merge, then gets
|
|
||||||
a release tag if user-visible behaviour changed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout main
|
|
||||||
git merge --no-ff sync/upstream-<topic> -m "merge: upstream sync — <topic>"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing Back
|
## Contributing Back
|
||||||
|
|
||||||
@@ -138,17 +116,12 @@ A few things to note about that process:
|
|||||||
not push that decision onto his codebase.
|
not push that decision onto his codebase.
|
||||||
- This is not guaranteed for every change, only where it makes sense
|
- This is not guaranteed for every change, only where it makes sense
|
||||||
and where I am confident the fix is clean and self-contained.
|
and where I am confident the fix is clean and self-contained.
|
||||||
|
- Whether it gets accepted is Infi's call, and a "no" is fine.
|
||||||
## When Upstream Goes Silent
|
|
||||||
|
|
||||||
If Chat 2 stops receiving updates the remote stays configured and this
|
|
||||||
workflow stays documented. The moment maintenance picks back up I am
|
|
||||||
ready to pull again.
|
|
||||||
|
|
||||||
## When Upstream Takes a Direction I Cannot Follow
|
## When Upstream Takes a Direction I Cannot Follow
|
||||||
|
|
||||||
If a future Chat 2 release breaks compatibility with the HellionChat
|
If a future Chat 2 release breaks compatibility with the HellionChat
|
||||||
privacy philosophy in a way that cannot be resolved (mandatory cloud
|
privacy philosophy in a way that cannot be resolved (mandatory cloud
|
||||||
sync, removal of the local message store, an incompatible license
|
sync, removal of the local message store, an incompatible licence
|
||||||
change), HellionChat continues from the last compatible cherry-pick.
|
change), HellionChat continues from where it is. The inherited
|
||||||
The inherited history stays under EUPL-1.2 and stays attributed.
|
history stays under EUPL-1.2 and stays attributed.
|
||||||
|
|||||||
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# preflight.sh — pre-push gate. Blocks A/B/C verify config drift; Block D is a
|
||||||
|
# headless `dotnet build` to catch compile-time API drift. Test execution lives
|
||||||
|
# in the local Build-Suite repo and is NOT part of this preflight.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
echo "==> preflight: Block A — version consistency"
|
||||||
|
./scripts/verify-version-consistency.sh
|
||||||
|
|
||||||
|
echo "==> preflight: Block B — manifest shape"
|
||||||
|
./scripts/verify-manifest-shape.sh
|
||||||
|
|
||||||
|
echo "==> preflight: Block C — changelog sync"
|
||||||
|
./scripts/verify-changelog-sync.sh
|
||||||
|
|
||||||
|
echo "==> preflight: Block D — plugin compile health"
|
||||||
|
dotnet build HellionChat/HellionChat.csproj --configuration Release --nologo --verbosity quiet
|
||||||
|
|
||||||
|
echo "==> preflight: ALL GREEN"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# setup-hooks.sh — installs pre-push hook via core.hooksPath. Idempotent.
|
||||||
|
# Note: NO pre-commit hook — test execution is local-only in the Build-Suite repo,
|
||||||
|
# so the plugin repo's pre-commit cannot run tests. Versions/manifest/changelog
|
||||||
|
# checks happen on pre-push only.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
chmod +x .githooks/pre-push
|
||||||
|
echo "setup-hooks: core.hooksPath set to .githooks. pre-push live."
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-changelog-sync.sh — Block C.
|
||||||
|
# Strips .0 suffix from repo.json AssemblyVersion to derive the 3-digit tag/version.
|
||||||
|
# yaml.changelog is a single multi-line block with **Hellion Chat X.Y.Z** subblocks.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
YAML="$ROOT/HellionChat/HellionChat.yaml"
|
||||||
|
REPO_JSON="$ROOT/repo.json"
|
||||||
|
FORGE_DIR="$ROOT/.github/forge-posts"
|
||||||
|
|
||||||
|
fail() { echo "verify-changelog-sync: FAIL — $1" >&2; exit 1; }
|
||||||
|
ok() { echo "verify-changelog-sync: OK — $1"; }
|
||||||
|
|
||||||
|
VER="$(jq -r '.[0].AssemblyVersion' "$REPO_JSON" | sed -E 's/\.0$//')"
|
||||||
|
TAG="v$VER"
|
||||||
|
|
||||||
|
grep -qE "^[[:space:]]*\*\*Hellion Chat ${VER}" "$YAML" \
|
||||||
|
|| fail "$YAML changelog missing **Hellion Chat ${VER}** subblock. Fix: add the v${VER} block at the top of the changelog field."
|
||||||
|
|
||||||
|
jq -r '.[0].Changelog' "$REPO_JSON" | grep -qE "^[[:space:]]*\*\*Hellion Chat ${VER}" \
|
||||||
|
|| fail "$REPO_JSON Changelog missing **Hellion Chat ${VER}** subblock. Fix: copy the yaml changelog over."
|
||||||
|
|
||||||
|
FORGE_FILE="$FORGE_DIR/${TAG}.md"
|
||||||
|
[ -f "$FORGE_FILE" ] || fail "$FORGE_FILE missing. Fix: create from previous tag's file as template."
|
||||||
|
|
||||||
|
SUBTITLE="$(awk '/^---$/{f=!f; next} f && /^subtitle:/' "$FORGE_FILE" | sed -E 's/^subtitle:[[:space:]]*//' | tr -d '\"')"
|
||||||
|
[ -n "$SUBTITLE" ] || fail "$FORGE_FILE frontmatter missing 'subtitle'."
|
||||||
|
[ "${#SUBTITLE}" -le 60 ] || fail "$FORGE_FILE subtitle is ${#SUBTITLE} chars (max 60)."
|
||||||
|
|
||||||
|
NATUR="$(awk '/^---$/{f=!f; next} f && /^versionsnatur:/' "$FORGE_FILE" | sed -E 's/^versionsnatur:[[:space:]]*//' | tr -d '\"')"
|
||||||
|
[ -n "$NATUR" ] || fail "$FORGE_FILE frontmatter missing 'versionsnatur'."
|
||||||
|
[ "${#NATUR}" -le 40 ] || fail "$FORGE_FILE versionsnatur is ${#NATUR} chars (max 40)."
|
||||||
|
|
||||||
|
BODY="$(awk '/^---$/{f++; next} f==2' "$FORGE_FILE")"
|
||||||
|
TITLE_LEN=$((${#VER} + 16 + ${#SUBTITLE}))
|
||||||
|
FOOTER_LEN=80
|
||||||
|
TOTAL=$((TITLE_LEN + ${#BODY} + FOOTER_LEN))
|
||||||
|
[ "$TOTAL" -le 5500 ] || fail "Forge embed total ~${TOTAL} chars > 5500 cap."
|
||||||
|
|
||||||
|
YAML_VERSIONS="$(grep -cE '^[[:space:]]*\*\*Hellion Chat' "$YAML" || true)"
|
||||||
|
[ "$YAML_VERSIONS" -le 4 ] || fail "$YAML changelog has $YAML_VERSIONS version subblocks (max 4). Fix: move oldest to docs/CHANGELOG.md."
|
||||||
|
|
||||||
|
ok "yaml/repo.json/forge-post in sync for $TAG, embed-total ~${TOTAL}/5500, $YAML_VERSIONS/4 subblocks"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-manifest-shape.sh — Block B. Required fields in lowercase yaml,
|
||||||
|
# DalamudApiLevel sanity in repo.json, no own DalamudPackager.targets,
|
||||||
|
# Icon AND every ImageUrl reachable.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
YAML="$ROOT/HellionChat/HellionChat.yaml"
|
||||||
|
REPO_JSON="$ROOT/repo.json"
|
||||||
|
TARGETS_OWN="$ROOT/HellionChat/DalamudPackager.targets"
|
||||||
|
|
||||||
|
fail() { echo "verify-manifest-shape: FAIL — $1" >&2; exit 1; }
|
||||||
|
ok() { echo "verify-manifest-shape: OK — $1"; }
|
||||||
|
|
||||||
|
for FIELD in name author punchline description repo_url icon_url image_urls tags changelog; do
|
||||||
|
grep -qE "^${FIELD}:" "$YAML" || fail "$YAML missing required field: $FIELD"
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -f "$TARGETS_OWN" ] && fail "Own DalamudPackager.targets at $TARGETS_OWN strips Icon/ImageUrls. DELETE it; SDK default works."
|
||||||
|
|
||||||
|
API_LEVEL="$(jq -r '.[0].DalamudApiLevel' "$REPO_JSON")"
|
||||||
|
case "$API_LEVEL" in
|
||||||
|
''|null) fail "$REPO_JSON missing DalamudApiLevel" ;;
|
||||||
|
esac
|
||||||
|
[[ "$API_LEVEL" =~ ^[0-9]+$ ]] || fail "$REPO_JSON DalamudApiLevel must be integer, got: $API_LEVEL"
|
||||||
|
[ "$API_LEVEL" -ge 12 ] || fail "$REPO_JSON DalamudApiLevel=$API_LEVEL is below SDK 15 floor (12)."
|
||||||
|
|
||||||
|
if [ "${HOOKS_OFFLINE:-0}" != "1" ]; then
|
||||||
|
ICON_URL="$(jq -r '.[0].IconUrl' "$REPO_JSON")"
|
||||||
|
curl -fsI "$ICON_URL" > /dev/null || fail "IconUrl unreachable: $ICON_URL"
|
||||||
|
ok "IconUrl reachable: $ICON_URL"
|
||||||
|
|
||||||
|
COUNT="$(jq -r '.[0].ImageUrls | length' "$REPO_JSON")"
|
||||||
|
[ "$COUNT" -ge 1 ] || fail "repo.json ImageUrls is empty"
|
||||||
|
for i in $(seq 0 $((COUNT - 1))); do
|
||||||
|
URL="$(jq -r ".[0].ImageUrls[$i]" "$REPO_JSON")"
|
||||||
|
curl -fsI "$URL" > /dev/null || fail "ImageUrls[$i] unreachable: $URL"
|
||||||
|
done
|
||||||
|
ok "$COUNT ImageUrl(s) reachable, DalamudApiLevel=$API_LEVEL"
|
||||||
|
else
|
||||||
|
ok "skipped URL reachability (HOOKS_OFFLINE=1), DalamudApiLevel=$API_LEVEL"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-version-consistency.sh — Block A of preflight.
|
||||||
|
# csproj <Version> is 3-digit SemVer; repo.json AssemblyVersion is 4-digit (.0 suffix).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
CSPROJ="$ROOT/HellionChat/HellionChat.csproj"
|
||||||
|
REPO_JSON="$ROOT/repo.json"
|
||||||
|
|
||||||
|
fail() { echo "verify-version-consistency: FAIL — $1" >&2; exit 1; }
|
||||||
|
ok() { echo "verify-version-consistency: OK — $1"; }
|
||||||
|
|
||||||
|
CSPROJ_VER="$(grep -oE '<Version>[^<]+</Version>' "$CSPROJ" | head -1 | sed -E 's/<[^>]+>//g')"
|
||||||
|
[ -n "$CSPROJ_VER" ] || fail "$CSPROJ has no <Version> element"
|
||||||
|
|
||||||
|
EXPECTED_4DIGIT="${CSPROJ_VER}.0"
|
||||||
|
|
||||||
|
REPO_VER="$(jq -r '.[0].AssemblyVersion' "$REPO_JSON")"
|
||||||
|
[ "$REPO_VER" = "$EXPECTED_4DIGIT" ] \
|
||||||
|
|| fail "csproj=$CSPROJ_VER expects repo.json AssemblyVersion=$EXPECTED_4DIGIT but got $REPO_VER. Fix: align in $REPO_JSON."
|
||||||
|
|
||||||
|
TEST_VER="$(jq -r '.[0].TestingAssemblyVersion' "$REPO_JSON")"
|
||||||
|
[ "$TEST_VER" = "$EXPECTED_4DIGIT" ] \
|
||||||
|
|| fail "TestingAssemblyVersion=$TEST_VER must match $EXPECTED_4DIGIT. Fix: align in $REPO_JSON."
|
||||||
|
|
||||||
|
TAG="v$CSPROJ_VER"
|
||||||
|
for KEY in DownloadLinkInstall DownloadLinkUpdate DownloadLinkTesting; do
|
||||||
|
URL="$(jq -r ".[0].$KEY" "$REPO_JSON")"
|
||||||
|
case "$URL" in
|
||||||
|
*"/$TAG/"*) ;;
|
||||||
|
*) fail "$KEY=$URL does not contain tag $TAG. Fix: update $REPO_JSON $KEY to releases/download/$TAG/latest.zip." ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
ok "csproj=$CSPROJ_VER, repo.json=$EXPECTED_4DIGIT, tag $TAG present in DownloadLinks"
|
||||||