Kei Merge + 1.6 + NF Update to 1.6 - #146
Draft
Rojikku wants to merge 45 commits into
Draft
Conversation
The modern PluginExtension plugin was already registered against ns.plugins.extension in build-logic, but ns.versions.toml never declared it - only extension-legacy existed. Gradle configuration failed the moment anything touched the modern plugin id.
ns.plugins.extension needs .asProvider() to disambiguate from the extension-legacy group accessor Gradle generates (extension-legacy is treated as a nested extension.legacy path, colliding with the top-level extension alias). PluginExtensionLegacy also had no gradlePlugin registration at all, despite ns.versions.toml already declaring its id and real extensions (e.g. calibre) applying it - every legacy extension currently fails to resolve its own plugin.
…lassName PluginExtensionLegacy.kt imported keiyoushi.gradle.extensions.baseVersionCode for theme.baseVersionCode, but no such property was ever defined in this repo's history - it now bridges to the theme's KeiyoushiMultisrcExtension, matching how themes are configured today (e.g. lib-multisrc/bakkin). GenerateKeepRulesTask's property was renamed from extClass to className at some point, but PluginExtensionLegacy's call site was never updated.
libVersion is a new concept - existing extensions and themes (e.g. lib-multisrc/bakkin) predate it and never set it, so keiyoushi.libVersion had no value and configuration failed for every one of them. Defaulting to "1.4" keeps everything that doesn't care building exactly as before; only extensions that actually need 1.6 have to opt in explicitly.
resolve_ext() only matched the modern DSL's theme= property in build.gradle.kts, and only looked at build.gradle.kts files at all. 141 of 142 real extensions are still on legacy Groovy build.gradle using themePkg=, so a themed extension's rebuild would silently stop triggering when its shared multisrc theme changed.
create-repo.py was rewritten to read tachiyomix.name/contentWarning/ extensionLib from every extension's manifest, but :core's shared AndroidManifest.xml (used by all 141+ legacy Groovy extensions) only ever emitted the old tachiyomi.novelextension.* keys - the new script would crash on the first real APK it processed. Followed upstream kei's own migration commit (7fb92dc88) as a reference: they added the new tachiyomix.* keys alongside the old ones rather than replacing them, then separately did a mass conversion of every extension to the modern DSL. We're keeping PluginExtensionLegacy (141 extensions haven't been converted), so the additive keys plus new placeholders on the legacy plugin's manifestPlaceholders is the equivalent bridge. extensionLib is hardcoded to "1.4" for legacy extensions since they're not allowed to declare libVersion at all (see the assertWithoutFlag guard in PluginExtensionLegacy). Verified end-to-end via calibre's merged manifest.
…rge-repo.py Reads the new manifest metadata (tachiyomix.name/contentWarning/extensionLib) via aapt badging instead of the old flat index fields, and writes both the new protobuf-backed index.json/index.pb and a reconstructed index.min.json for backwards compatibility with existing installed clients (whose repo URL still points at index.min.json, per commit-repo.sh's jsdelivr purge). Fixed two bugs found while validating this against the real repo: - APK_BASE_URL/ICON_BASE_URL in create-repo.py pointed at upstream keiyoushi/extensions instead of this repo's own novelsourcery/extensions. Since the new merge-repo.py no longer reconstructs these URLs itself (it now merges already-built Extension messages directly), whatever create-repo.py writes flows straight through to the published index. - merge-repo.py's final index_pb2.Index(...) construction still referenced legacy_index and extract_extension_lib, both removed earlier in the same rewrite - guaranteed NameError on first run. Replaced with the already-merged, already-typed all_extensions list.
:core needs Call.awaitSuccess(), only available in extensions-lib 1.6+ (tagged v1.6.0-1, which now also has RateLimited - see extensions-lib commit c81a5f8). But the 141 existing legacy extensions and the lib-multisrc themes were built against extensions-lib 1.4.4-r60-3, which has RefreshContext support that the 1.6 line doesn't carry yet - bumping the single shared `tachiyomi-lib` pin globally would have kept :core compiling while silently breaking every RefreshContext-using extension (e.g. NovelFire). Added a second `tachiyomi-lib-16`/`common16` bundle pinned at v1.6.0-1, alongside the original `tachiyomi-lib`/`common` (still v1.4.4-r60-3). :core now compiles specifically against common16 (it doesn't touch RefreshContext, so this is safe and independent of what individual extensions compile against). PluginExtension/PluginMultiSrc now pick common vs common16 based on the declared keiyoushi.libVersion, resolved in afterEvaluate since Gradle's dependency-notation parser needs the bundle passed as its own directly-typed accessor (a .flatMap'd Provider loses whatever internal type tag makes bundle expansion work - confirmed via UnsupportedNotationException when that was tried first). Extended VALID_LIB_VERSIONS to allow "1.6". Verified end-to-end: :core, calibre (legacy, 1.4), and bakkin (theme, 1.4) all compile; calibre's full debug APK builds and its aapt badging output shows tachiyomix.extensionLib as 0x3fb33333 - the correct IEEE-754 hex encoding of 1.4, confirming create-repo.py's hex_to_float() will decode it correctly.
Preparing to flip the default in PluginMultiSrc from 1.4 to 1.6 (we plan to phase 1.4 out entirely) - every existing theme needs to keep its current, unverified-for-1.6 behavior explicitly rather than silently inheriting whatever the new default becomes. Two of these (madaranovel, readnovelfull) genuinely use RefreshContext, which only exists in the 1.4 line - confirmed via compile that this preserves their behavior exactly.
Same reasoning as the multisrc theme commit - every one of the 141 existing legacy Groovy extensions gets an explicit libVersion so flipping the modern DSL's default away from 1.4 doesn't silently change what any of them compile against. Also prepares each file for PluginExtensionLegacy actually requiring and using this value.
…onLegacy
Flips keiyoushi.libVersion's convention from "1.4" to "1.6" in both
PluginExtension and PluginMultiSrc, now that every existing consumer
has an explicit "1.4" stamp preserving its current behavior. New
extensions/themes that don't declare libVersion now default to 1.6.
PluginExtensionLegacy previously forbade declaring libVersion at all
(assertWithoutFlag(!extra.has("libVersion"))) and hardcoded everything
to 1.4. Since every legacy extension now declares it explicitly, this
plugin requires it instead, and actually uses the declared value:
- versionName is "$libVersion.$versionCode" instead of a hardcoded "1.4.*"
- tachiyomix.extensionLib manifest metadata uses the real value
- compileOnly picks common vs common16 based on the declared value
(in afterEvaluate, same reason as PluginExtension/PluginMultiSrc -
a bundle Provider needs to stay its own directly-typed accessor for
Gradle's dependency-notation parser to expand it)
- a themed extension's libVersion must match its theme's, mirroring
the existing baseVersionCode/theme consistency check
Added a bare Project.libVersion accessor (mirroring baseVersionCode)
for themes, which use the flat property style rather than the nested
keiyoushi{} DSL block.
Verified end-to-end: full ./gradlew help configuration succeeds across
all 220 restamped files; :core (1.6), calibre (legacy, no theme),
madaranovel/readnovelfull (themes using RefreshContext, 1.4), and
azora/freewebnovel (real extensions consuming those themes) all
compile. Built azora's full debug APK - its manifest shows
versionName=1.4.4 and tachiyomix.extensionLib=0x3fb33333 (1.4 in hex),
confirming the whole legacy+theme+libVersion chain works correctly
end-to-end.
paginatedChapterList only ever read context.existingChapters - never mangaId, lastFetchTime, or forceRefresh - so there's no reason for it to depend on RefreshContext at all. Takes existingChapters: List<SChapter> directly, matching getMangaUpdate's own chapters parameter, which is what every caller now has on hand post-migration.
RefreshContext (and the getChapterList(manga, context) override it required) is fork-only and deprecated (tsundoku PR #325) in favor of upstream's getMangaUpdate, which takes existing chapters directly as a parameter - and that method doesn't exist at all on the extensions-lib 1.4 line, so this requires the libVersion bump. The manga-details half of getMangaUpdate calls mangaDetailsRequest/mangaDetailsParse directly (both deprecated-but-present on 1.6) since the plain getMangaDetails(manga) suspend shortcut was removed entirely in favor of the combined method. Verified via a real debug APK build: manifest shows versionName=1.6.8 and tachiyomix.extensionLib=0x3fcccccd (1.6 in hex).
Same migration as NovelFire - RefreshContext-based getChapterList override replaced with a getMangaUpdate override, using the passed-in chapters parameter and mangaDetailsRequest/mangaDetailsParse directly.
…ibVersion 1.6 Same RefreshContext -> getMangaUpdate migration as NovelFire/LightNovelWorld, applied to the shared readnovelfull theme. Since it's a shared base class, bumping its libVersion to 1.6 requires every extension using it to match (per the theme/extension libVersion consistency check) even though most of them (allnovel, allnovelfull, freewebnovel, libread, lightnovelplus, novelbin, novelfull, readnovelfull) never override getChapterList themselves and needed no code changes - only the version bump. NovelLive extends the theme but had its own separate getChapterList override (a different chapter-list strategy), migrated the same way.
….Builder Adds rateLimit(minimumDelayMillis, permits) on top of the existing rule-based rateLimit(permits, period, interval, shouldLimit), mapping minimumDelayMillis onto period (not interval) - an interval floor would force a minimum gap between every dispatch regardless of the permits budget, silently defeating any permits value above 1. Bundled in :core, so it's dexed into each extension's own APK and keeps working even if the host app's own rate limiting is missing, disabled, or stripped by a hostile fork - the whole point of RateLimited sources self-enforcing rather than relying solely on the app.
Defaults chosen from evidence already in this file: NovelFire actively detects and surfaces the site's own "You are being rate limited" response (NovelFireThrottlingError), so these are a real floor, not a guess. - minimumDelayMillis = 1000 (respects the site's own detected throttling) - recommendedDelayMillis = 1500 - recommendedPermits = 2 (lets the common case - detail fetch + one chapter-list page - through without an artificial stall) Wires the :core rateLimit() self-throttle into the client directly, so this keeps working even if the host app's rate limiting is missing or disabled. Verified the relevant keiyoushi.network.RateLimit* classes are present in NovelFire's own compiled dex (not just the app's), confirming the self-throttle is genuinely bundled into the extension's own APK.
extensions-lib's index.proto now carries isNovel on Extension (reserved 8000+ fork-field block, not an upstream field). Without this, create-repo.py's isNovel key made merge-repo.py's strict json_format.Parse reject every extension outright.
…tructions git apply --index --3way exits non-zero even when it successfully applies with conflict markers, so the old classification lumped genuine failures together with resolvable conflicts, dumping a redundant raw patch for the latter while also leaving it dirty in the tree. Split into three buckets (clean / conflicted / failed) so conflicted files are left with their markers for direct resolution. Also: the printed "undo" instructions said `git reset --hard kei-last`, but kei-last points into kei's disjoint commit history, not ours -- doing that would replace the whole tree with kei's raw upstream. Since all work happens on a throwaway branch, fixed the instructions to just abandon it. .kei-sync is now committed automatically once every file at least applied (clean or with conflict markers), rather than left staged for manual commit -- a bad sync is cheap to revert by discarding the workbranch. Also: workbranch naming keyed off target SHA instead of date (so re-runs on the same day resume instead of colliding), added binary-file handling (take upstream wholesale if undiverged locally, else flag for manual pick), and a remote-existence check.
Answers two questions that came up while planning the big pending kei refactor (89b3b42a4..7d508b834): "what commit did I actually run this against" and "what's left to deal with, without re-running the merge and re-touching things I've already dealt with." - Every invocation (apply or status) appends a timestamped block to .git/kei-sync.log with the full sync range, workbranch, and per-file buckets -- a durable local audit trail independent of branch names or the reject dir (which gets wiped and regenerated each run). - New `--status` mode: read-only, makes no changes. For each file in the pending range, checks whether the working tree already matches upstream (resolved), still matches the old sync point exactly (untouched), or differs from both (diverged). Diverged entries print a diff-size hint against upstream, since a small diff there is often a fork customization layered on an already-incorporated change (e.g. the isNovel field we hand-added to index_pb2.py/.pyi will never byte-match kei's version) rather than something still unresolved. Safe to re-run any time; exits non-zero while anything's pending so it doubles as a readiness check. - New .kei-sync-ignore file (repo-relative paths, one per line): files permanently excluded from both modes, for cases where we've decided a path should never be auto-synced from upstream again. This is distinct from a file merely being resolved for the current range -- ignored paths drop out of consideration entirely, forever, until removed from the list. Verified all three flows (clean auto-commit through conflicts, hard failures correctly blocking the .kei-sync bump, and ignore-list suppression working in both modes) against a synthetic repo, and ran --status and a dry apply against the real pending kei range in a scratch clone to confirm the bucketing matches reality.
…cision annotations) Toward feeling like a native git subcommand rather than a one-off script: - -h/--help prints usage. - Reject unexpected extra arguments (only $1 was ever checked before). - Preserve git apply's stderr for genuinely-unappliable files into $REJECT_DIR/$f.patch.err instead of discarding it via 2>/dev/null -- the raw patch alone didn't say *why* it failed. Also wires --status up to .git/kei-sync-decisions.jsonl (written by the kei-sync-vscode helper, added next commit, at the moment a conflict is actually resolved in the Merge Editor): when present and jq is on PATH, Diverged entries get annotated with the real recorded decision instead of only the diff-size guess. Falls back silently to the existing heuristic if jq is missing or no decision was recorded for a path -- no new hard dependency on the core script. Verified against the real pending kei range (89b3b42a4..7d508b834) in a scratch clone: stderr capture produces real "patch does not apply" diagnostics for the 11 hard-failures, and a synthetic decisions.jsonl correctly annotates matching Diverged entries while leaving unmarked ones on the plain diff-size hint.
sync-kei.sh --status's Diverged bucket can't tell a genuinely unresolved conflict apart from a file where the fork permanently carries its own content on top of an already-correctly-merged upstream change (e.g. index_pb2.py's hand-added isNovel field) -- its diff-size hint is only a guess. This records the actual decision made at the moment a conflict is resolved in VSCode (Merge Editor or manual edits -- indistinguishable via the Git extension API, and it doesn't matter which), via .git/kei-sync-decisions.jsonl, which the previous commit already wired --status up to read. Not published to the marketplace -- personal, local-only tooling, installed via `npx @vscode/vsce package` + `code --install-extension`. Checked in (with a README) so it isn't lost and isn't unusable if we or anyone else wants it later, per the plan's addendum. Verified with a mock-vscode Node harness driving the real extension.js through: initial-snapshot (no false prompt), resolve-with-decision (correct jsonl shape, status bar count), a dismissed prompt showing up as untagged, finalize (summary content, clipboard/doc, backup-then-clear), and the gate closing when the branch no longer matches kei-sync-*. Also packaged and installed the real .vsix locally via `code --install-extension` to confirm it loads without error.
Root cause of the isNovel/branding/libVersion regressions found in this sync's review: when a file lands in the FAILED bucket because it's a rename (old path deleted upstream, new path added elsewhere) AND our copy had local fork customizations, the only artifact sync-kei.sh gave you was a bare "delete N lines" patch for the old path -- nothing connecting what the fork actually changed to the new upstream file. Reconciliation was 100% manual and memory-dependent, which is exactly the kind of thing that silently drops content at scale (11 failed files) or under fatigue. Fixes, verified against the real pending range and the actual current kei-sync-7d508b834809 working tree: - Both modes now print detected renames up front, using a 10% similarity threshold -- git's own default (50%) misses heavily-rewritten renames entirely, which are exactly the ones most likely to carry fork customizations. Confirmed empirically: PluginExtension.kt-> ExtensionPlugin.kt (49% similar) and merge-repo.py->publish-repo.py (38%) were BOTH invisible at the default threshold. - For a detected rename where the old path has local divergence, the script now saves just the fork's own edit to that file (old kei baseline vs. our current content) to .git/kei-sync-pending/<oldpath>.fork-customizations.diff -- a small, surgical checklist of what needs porting into the new file, generated automatically instead of requiring anyone to reconstruct it from memory. Also lists pure deletions (no matching replacement) separately. - --status now additionally compares the current working tree (not just the pending range) against kei's target tree, catching files we're still carrying that don't exist upstream at all -- almost always the old half of a rename that never got deleted. Split into "orphaned" (existed in kei as of our last sync, so very likely a leftover) vs. "fork-original" (never existed upstream, probably intentional but flagged so that's a conscious call). - New .kei-sync-ignore populated with the fork's actual permanent-only content (lib/, scripts/, sync bookkeeping files, a handful of novel-specific core utilities and repo policy files), discovered by running the new fork-original report and confirming each entry is legitimate, permanent fork content rather than something needing review. Running --status against the real in-progress merge immediately surfaced all 10 genuinely orphaned files (the exact ones this session's manual review found by hand) with zero false positives, and the fork-customizations diff for PluginExtension.kt was a clean ~15-line artifact showing exactly the libVersion/common16 wiring that needs to be re-applied to ExtensionPlugin.kt.
report_renames_and_deletions() used "git diff --quiet LAST_SYNC -- old" to decide whether a rename's old side had local customizations worth saving. That check can't tell "modified" from "gone" -- for old files that had already been cleanly auto-applied (deleted) earlier in the same run, it produced a full "delete the whole file" diff mislabeled as "fork customizations," which is actively misleading (nothing to port, but reads like there is). Added an explicit existence check first: gone means either auto-applied or already handled by hand, nothing to diff, just confirm the replacement file exists. Caught by re-running --status against the real in-progress merge and noticing 8 of the 15 rename entries had suspiciously large "fork-customizations" diffs (up to 108 lines) for files known to have applied cleanly -- turned out to be exactly this.
…ation
Two real bugs found while using the script to actually resolve the pending
kei refactor tonight:
- sync-kei.sh always resolved TARGET from kei/main, the remote-tracking
ref -- which moves every time anyone fetches, including this script's
own `git fetch kei` step. Mid-session, kei/main advanced 5 commits
under us with no way to have prevented it, silently changing what a
--status run was even comparing against. Fixed: pull up to a local
branch (default: kei, overridable with --branch <ref>) that's advanced
explicitly and only on request (git branch -f kei kei/main), never as a
side effect of fetching. Also fixes a genuine ref ambiguity: naming the
pin branch the same as the remote ("kei") makes bare `git rev-parse kei`
ambiguous between refs/heads/kei and the remote's own ref namespace;
now resolved via refs/heads/<ref> explicitly.
- --status's RESOLVED/UNTOUCHED/DIVERGED classification used `git diff
--quiet <rev> -- <path>`, which only compares a commit against the
INDEX, not the raw working tree -- an untracked file (never `git add`ed,
exactly what a hand-recreated rename target looks like right after a
failed apply) reads as "doesn't exist" even when it's sitting on disk
with the right content, silently misclassifying it as untouched instead
of resolved. Replaced with a helper that compares the commit's blob
directly against the working-tree file, correct regardless of tracking
state. This alone moved ~18 files from the wrong bucket into "Resolved"
once the real build-logic fixes landed.
Also: fixed a `set -e` footgun where the Diverged-bucket diff-size hint
used `grep -c`, which exits 1 (not an error) on a zero count -- inside a
variable-assignment command substitution that silently aborted the whole
script the moment a file had zero pure additions or zero pure removals,
with no error message. Fixed a regex-escaping bug generating harmless but
noisy "stray \ before /" warnings (escaping `/`, which isn't a regex
metacharacter). Added gradle/kei.versions.toml to .kei-sync-ignore (a file
kei has that this fork deliberately never carries) and __pycache__/ to
.gitignore.
kei restructured build-logic wholesale (package rename, PluginExtension.kt
-> ExtensionPlugin.kt split across io/github/keiyoushi/gradle/{api,internal,
tasks}/, new signing/jar pipeline). sync-kei.sh correctly flagged every
touched file as a rename with local customizations that needed manual
porting (see its "Renames detected" report) -- this is that porting,
verified file-by-file against each old file's actual fork-diff rather than
guessed from memory.
Restored:
- Dual libVersion support (Phase F): `keiyoushi.libVersion.convention("1.6")`
and the compileOnly(libs.bundles.common vs common16) switch, now on
ExtensionPlugin.kt and ThemePlugin.kt (the old PluginExtension.kt and
PluginMultiSrc.kt both had it -- kei's own new files had neither).
Constant.kt's VALID_LIB_VERSIONS extended to ["1.4", "1.6"] to match.
- The kei->ns catalog aliasing this fork relies on for Android SDK/Java
version pins, since we don't carry a real kei.versions.toml (settings.gradle.kts
registers "kei" against gradle/ns.versions.toml instead). Restored the
`Project.ns` accessor and `Project.kei = ns` alias, and the explicit
kei.versions.* -> ns.versions.* call-site swaps in AndroidBasePlugin.kt
and configurations/Kotlin.kt matching the old files' own precedent.
- baseVersionCode/libVersion extension properties on Project, now backed
by kei's renamed KeiyoushiThemeExtension (was KeiyoushiMultisrcExtension).
- isNovel: true on ExtensionMetadata, the JSON payload GenerateSourceInfoTask
writes for publish-repo.py to read -- every extension in this repo is a
novel by construction, and this is the field the whole isNovel pipeline
(extensions-lib index.proto, this repo's publish script, the app) depends
on further downstream.
- PluginExtensionLegacy.kt's imports, which still pointed at the old
namespace entirely (it's fork-original, never touched by kei syncs, so
nothing here would have caught this until the build broke): repointed to
the new namespace, KeiyoushiThemeExtension, and a new
GenerateLegacyKeepRulesTask. That last one is NOT the same as kei's own
GenerateKeepRulesTask -- kei's version hardcodes the keep rule to the
KSP-generated "ExtensionGenerated" class name, which is correct for the
modern DSL but wrong for legacy extensions with hand-written, arbitrarily-
named entry classes (the old GenerateKeepRulesTask took an explicit
className for exactly this reason). Restored as a separate, legacy-only
task rather than modifying kei's shared one.
Deleted the 10 old-namespace files these changes make dead: PluginAndroidBase.kt,
PluginExtension.kt, PluginMultiSrc.kt, and the old keiyoushi.gradle.{configurations,
extensions,tasks,utils} tree, once nothing referenced them anymore (verified via
`grep -rn '^import keiyoushi\.gradle\.'` returning empty outside these files).
Verified: no remaining old-namespace imports anywhere in build-logic, the proto/JSON
pipeline round-trips isNovel correctly end to end, and `scripts/sync-kei.sh --status`
reports every one of these files as either Resolved or a small, expected diff against
upstream -- no unexplained divergence remains.
kei folded create-repo.py + merge-repo.py into a single publish-repo.py and
switched its metadata source from parsing `aapt dump badging` output to
reading the JSON GenerateSourceInfoTask now emits at build time. The new
script was a clean copy of kei's own upstream version -- Keiyoushi branding,
their signing key, their CDN URLs, generic `tachiyomi-` naming, and (this is
the one that actually mattered) never sets isNovel on the extensions it
publishes, since the manifest-meta-data-tag mechanism that used to derive it
doesn't exist in the new pipeline at all.
- index.proto: added the missing `isNovel = 8000` field declaration (the fork's
reserved 8000+ field block) -- the compiled index_pb2.py/.pyi already had it
from an earlier session, hand-patched without updating the .proto source they're
supposed to be generated from, so the two had drifted apart. Regenerated both
via `protoc --python_out=. --pyi_out=.` per the file's own header instructions,
confirmed byte-identical in the only intentional aspect (offsets) and verified
with a live strict-JSON parse round-trip.
- publish-repo.py: name/badgeLabel/signingKey/contact -> NovelSourcery's (from
the old merge-repo.py), LANGUAGE_REGEX and the apk/jar/icon cleanup globs
tachiyomi- -> tsundoku- and eu.kanade.tachiyomi.extension. ->
eu.kanade.tachiyomi.novelextension. (matching the fork's actual APK naming
convention), and `isNovel=info.get("isNovel", True)` threaded through to
index_pb2.Extension -- paired with the ExtensionPlugin.kt commit's
`isNovel: true` on the JSON payload this reads.
APK/JAR/ICON_BASE_URL kept on the fork's existing raw.githubusercontent.com
pattern rather than adopting kei's new jsdelivr CDN switch -- a deliberate,
conservative call (only found evidence jsdelivr is intentionally used for
index.min.json specifically, via commit-repo.sh's purge call, not for
APK/icon downloads), flagging in case that's wrong and jsdelivr should
apply here too.
Deleted create-repo.py, merge-repo.py, commit-repo.sh, and always_build.json
now that publish-repo.py fully replaces them and nothing else references them.
The mechanically clean/already-resolved part of the kei build-logic refactor sync -- fork branding on the CI workflows and issue templates (novelsourcery/extensions-source, NS org references), CONTRIBUTING.md brought up to date with kei's rewritten version, the KSP SourceProcessor mirrors/concrete-source-class support and MirrorPreferences/strings.json updates (pure upstream, no fork logic needed), the new ext-bootstrap.py, and gradle/libs.versions.toml's apksig catalog entry (needed by the new build-logic signing pipeline) plus the fork's existing tachiyomi-lib-16/common16 entries. No fork-specific customization needed in any of these -- verified each against kei's target commit directly where it mattered (SourceProcessor.kt and MirrorPreferences.kt are byte-identical to upstream).
Matches kei's own pattern: apk/jar/icon URLs (versioned filenames, so no cache-purge needed) move to jsdelivr's free GitHub CDN; the top-level index.min.json URL users paste into the app stays on raw.githubusercontent.com, same as kei. The purge curls already present in build_push.yml were still pointed at keiyoushi/extensions from the merge -- fixed to novelsourcery/extensions.
…conflicts Two independent sources of needless manual conflict resolution, found while re-running the script on an already-fully-resolved sync: - Permanent local overrides (repo name, product branding, signing key, CDN URLs) sit on lines upstream routinely touches for its own reasons (e.g. its own version-bump commits), so every such commit re-triggered a conflict whose resolution is always "keep ours." .kei-sync-rewrite normalizes known permanent-override regions to a stable placeholder on both the base and target blobs before diffing, so a routine upstream touch-up near one of these never generates a hunk in the first place. - git apply --3way's diff3 algorithm can lose alignment around repeated boilerplate (e.g. CONTRIBUTING.md's near-duplicate examples) and flag a block as conflicting even when both sides are byte-identical. collapse_identical_conflicts() resolves those silently before they ever reach a human. Building the rewrite table required writing the rewritten blobs as real git objects (git hash-object -w) so --3way has something to look up by SHA -- a bare unified diff with no `index` line falls back to strict apply, which either succeeds outright or hard-fails with no usable markers at all. Also fixed two bugs hit along the way: a pipefail trap where `diff -u | tail` killed the whole script the moment two rewritten blobs actually differed, and files that should resolve as a no-op (rewrite fully accounted for the only difference, or the file was already deleted locally same as upstream) landing in hard-failure instead of being recognized as already resolved. Verified in a disposable scratch clone against the real pending range: 9 conflicted + 37 hard-failed dropped to 0 hard-failed + 13 genuine conflicts (all matching the already-known substantive fork customizations).
Two branding gaps missed in the earlier publish-repo.py pass: the Discord invite still pointed at kei's server (3FbCpdKbdY instead of our own JG2K2jTjd6), and one repository-JSON link used lowercase "novelsourcery" while every other github.com/... link in the file uses the real org casing "NovelSourcery". Found while testing sync-kei.sh's new rewrite table -- both showed up as conflicts that looked spurious (case-only, or Discord IDs that are easy to eyeball as "probably the same") but were actually flagging real, if minor, content gaps.
…r context The org name needs different casing in different contexts -- "NovelSourcery" (the real casing) in plain github.com/... links, but lowercase "novelsourcery" in raw.githubusercontent.com/jsdelivr.net/github.io URLs and the github.repository == '...' YAML comparison (all confirmed intentional, not casing bugs). An unscoped rule can only pick one output casing, so it was guaranteed to be wrong in whichever context it didn't target -- which is exactly what happened: the previous all-lowercase version was wrong for plain links (and produced a spurious-looking case-only conflict against CONTRIBUTING.md's correct capitalized content), and the fix-in-place attempt at capitalizing it broke the CDN/YAML contexts instead. Rules are now scoped by domain prefix or exact surrounding text, same technique already used for the CDN-specific rules. Matching is now case-insensitive (sed's I flag) so kei's own text doesn't need to match one specific casing to be caught by these rules in the first place. Verified via a full case-insensitive-identical sweep across every conflicted file in a scratch clone of the real pending range: zero ghost conflicts remain anywhere, not just in the specific file this was found in.
… failure) CI failed on :lib-multisrc:readwn (and every other lib-multisrc module -- all 75 build.gradle.kts files use the identical two-line import) with "Unresolved reference 'keiyoushi'": they still imported baseVersionCode/ libVersion from keiyoushi.gradle.extensions, a package deleted when the orphaned old-namespace build-logic files were removed earlier this branch. lib-multisrc/ is deliberately outside sync-kei.sh's scope (it's extension code, not build-logic), so it was never touched by any of that cleanup -- but it still references symbols that moved. baseVersionCode/libVersion are fork-only properties (Phase F work, never existed in kei's upstream), now declared in io.github.keiyoushi.gradle.internal.extensions.Project.kt; updated all 75 imports to match, same fix already applied to PluginExtensionLegacy.kt for the same reason.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Perhaps a bit of a mess, needs review, but appears to work maybe.
Checklist:
extVersionCodevalue inbuild.gradlefor individual extensionsoverrideVersionCodeorbaseVersionCodeas needed for all multisrc extensionsisNsfw = trueflag inbuild.gradlewhen appropriateidif a source's name or language were changedweb_hi_res_512.pngwhen adding a new extension