33 Commits

Author SHA1 Message Date
Jonathan Miller 8e0388c9d8 fix(custom-fields): log errors instead of silently discarding them
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- saveCustomFieldsFromForm: log GetCustomFieldsByOwner errors
- resolveExtensionMetadata: log DB errors on custom field lookup
- NewIssue/ViewIssue: log errors from GetCustomFieldsByOwner and
  GetCustomFieldValuesMap instead of blank-assigning
- Composer: fix misleading comment about override source

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 22:14:10 -05:00
Jonathan Miller cd4c701cb6 fix(custom-fields): address code review findings
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- API: return 500 on GetCustomFieldsByOwner failure instead of silently
  swallowing the error
- resolveExtensionMetadata: add DownloadGating/KeyPrefix to metadata
  struct instead of mutating the caller's cfg pointer (side effect)
- resolveExtensionMetadata: add Description custom field mapping
- Composer: use meta.PHPMinimum instead of bypassing the cascade
- Web form: flash error on custom field save failure instead of silent log

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 21:59:50 -05:00
Jonathan Miller 1935889f6b feat(updateserver): resolve extension metadata from custom fields with config fallback
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Add resolveExtensionMetadata() with cascading priority: org-level
repo-scoped custom fields → update_stream_config table → repo-derived
defaults. All six feed generators (Joomla, WordPress, Composer, Drupal,
PrestaShop, WHMCS) now use this unified resolver. Repos can be migrated
to custom fields gradually since the config table remains as fallback.

Ref #492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 21:48:14 -05:00
Jonathan Miller 5665bc545e fix(updateserver): use client=0 for packages to fix Joomla extension matching (#482)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 25s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m5s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Joomla matches updates to installed extensions via element+type+client_id.
Packages in #__extensions have client_id=0. Omitting <client> caused
Joomla to default to client_id=1, resulting in extension_id=0 in
#__updates and updates not appearing.

Fix: output <client>0</client> for package types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 18:44:46 -05:00
Jonathan Miller d553c87a9d fix(updateserver): derive maintainer from org profile, infourl from support_url
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 24s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m2s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- Maintainer name: org FullName (from org profile)
- Maintainer URL: org Website (from org profile)
- Info URL: support_url (product page), falls back to releases page
- Removes dependency on separate maintainer/maintainer_url/info_url
  fields in update_stream_config

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 18:17:51 -05:00
Jonathan Miller 5a80b8da33 docs(updateserver): correct joomlaTagName comment with Joomla source reference
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Failing after 21s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 58s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Joomla's Update.php maps tags via STABILITY_ + strtoupper(tag).
Valid values: dev, alpha, beta, rc, stable. Full names like
"development" silently fall back to STABILITY_STABLE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 14:29:26 -05:00
Jonathan Miller 0de02fdce5 fix(updateserver): prevent stream name tag from overriding asset-derived version
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
When the tag is a stream name (e.g. "release-candidate"), the version
extracted from the asset filename was being overwritten by the release
title version. Remove the isStreamName check since the priority chain
(filename -> tag -> title) already handles this correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:22:40 -05:00
Jonathan Miller f0aa2c3034 fix(updateserver): extract version from asset filename, omit client for packages
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 0s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- Version now extracted from the zip asset filename first (most
  accurate), falling back to tag name then release title. Fixes
  mismatch where title version was updated but asset was stale.
- Omit <client> element for package extension types (packages manage
  their own sub-extension clients per Joomla spec).
- Make Client field omitempty so empty string doesn't render empty tag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:14:29 -05:00
Jonathan Miller e5aa0c343d fix(updates): default Joomla target version to 5/6
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 25s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m31s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 21:57:03 -05:00
Jonathan Miller ba0d180e39 fix(updates): correct infourl/maintainerurl mapping
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Failing after 19s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 6m52s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
<infourl> = InfoURL field (product/release info page), fallback /releases
<maintainerurl> = SupportURL field (support site), fallback MaintainerURL, fallback org profile

Previously SupportURL was mapped to <infourl> which was wrong.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 21:54:01 -05:00
Jonathan Miller bd81616432 fix(build): remove unused time import in drupal.go
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 20s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 09:10:11 -05:00
Jonathan Miller 02f3ed88f1 feat(updates): PrestaShop (#352), Drupal (#353), WHMCS (#355) update feeds
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 18s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
PrestaShop: GET /updates/prestashop.xml — module update XML with
name, version, download URL, author, SHA256. Serves stable only.

Drupal: GET /updates/drupal.xml — update status XML per Drupal API
spec. Includes project metadata, all releases with status, download
links, SHA256. Uses TargetVersion config for api_version field.

WHMCS: GET /updates/whmcs.json — simple JSON with latest stable
version, download URL (with dlid), changelog, author. License key
embedded in download URL when provided.

All three use ResolveReleaseStream for manual/auto stream mapping,
readSHA256FromSidecar for integrity hashes, and extractVersion with
stream-name tag fallback.

Routes registered under the update server group alongside Joomla,
Dolibarr, WordPress, and Composer feeds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 09:08:03 -05:00
Jonathan Miller 0fb0aea719 feat(updates): Composer packages.json feed (#354), hide menu items for guests
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 20s
Composer feed: new endpoint GET /updates/packages.json serving
Composer/Packagist-compatible packages.json. Includes version,
dist URL with SHA256, authors, PHP requirement. License key
embedded in download URL when provided.

Menu visibility: Actions and Licenses tabs in repo header now
require .IsSigned — anonymous users no longer see tabs they
can't access. Previously the tabs were visible but clicking
redirected to login (confusing UX).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 09:02:00 -05:00
Jonathan Miller 1b9b82d59a fix(build): pass ctx to buildWordPressChangelog for ResolveReleaseStream
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 24s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 07:47:42 -05:00
Jonathan Miller 37322e4212 feat(updates): manual release-to-stream mapping
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 22s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Add release_stream_map table for explicitly assigning releases to
update streams. When a mapping exists, it overrides automatic tag
detection. When absent, falls back to tag name/suffix matching.

New model: ReleaseStreamMap with SetReleaseStream, GetReleaseStream,
ResolveReleaseStream (manual first, auto fallback).

UI: stream selector dropdown on release create/edit page, shown when
licensing is enabled. Options: auto-detect (default) or any
configured stream (stable, release-candidate, beta, etc.).

All three feed generators (Joomla, Dolibarr, WordPress) now use
ResolveReleaseStream instead of MatchStreamFromTag.

Migration v340 updated with release_stream_map table creation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 07:37:02 -05:00
Jonathan Miller 2f9097a254 fix(updates): check tag name not extracted version for stream name detection
isStreamName was checking the extracted version (empty for stream
tags) instead of the original tag name. Now checks rel.TagName
directly, and also falls through when extractVersion returns empty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 07:29:43 -05:00
Jonathan Miller ce3af35c40 fix(updates): extract version numbers from release titles via regex
When tags are stream names, extractVersion falls back to finding a
version pattern (digits.digits.digits) anywhere in the release title.
Handles titles like "Package - MokoWaaS (VERSION: 02.31.00)".

Previously the full title was used as the version, producing invalid
entries like "Package - MokoWaaS (VERSION: 02.31.00)" in the XML.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 07:27:06 -05:00
Jonathan Miller 0a3cd3115f feat(updates): support stream-name tags alongside version tags
MatchStreamFromTag now checks if the tag name directly matches a
stream name (e.g. "stable", "release-candidate", "development")
before falling back to suffix matching. Supports both conventions:

1. Stream-name tags: tag IS the stream (MokoWaaS style)
2. Version tags: tag has version + suffix (v1.0.0-rc1 style)

When a stream-name tag is detected, the version number is extracted
from the release title instead of the tag. Falls back to tag name
if no version found in title.

Applied across all feed generators: Joomla XML, Dolibarr JSON,
WordPress JSON, and Changelog XML.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 07:21:02 -05:00
Jonathan Miller 0e7d3c4a34 fix(security): ownership guards, RepoScope parsing, CSRF tokens, XSS escaping
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
PR RC Release / Build RC Release (pull_request) Failing after 17s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
SECURITY: Add verifyPackageOwnership/verifyKeyOwnership checks to
all API handlers that accept ID parameters. Prevents cross-org
access where an admin of org A could modify org B's license data.

FIX: RepoScope validation now properly parses JSON arrays using
json.Unmarshal instead of strings.Contains. The old approach matched
substrings (repo ID "2" matched inside "12"). Now uses typed int64
comparison.

FIX: Add {{$.CsrfTokenHtml}} to both delete confirmation modal
forms (package and key) in repo and org templates. Without CSRF
tokens, the form-fetch-action POST requests would be rejected.

FIX: HTML-escape release notes in WordPress changelog to prevent
XSS via malicious release note content reaching WP admin dashboards.

FIX: Parse AllowedChannels JSON format before comma-split fallback
to avoid garbage values from splitting JSON arrays by comma.

FIX: Add missing third return value (false) on error path in
validateUpdateKey to prevent compile error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 06:59:29 -05:00
Jonathan Miller a149edccd3 feat(licenses): feed visibility modes and login-required releases
Add FeedVisibility field to UpdateStreamConfig with three modes:
- public: full feed with download URLs (default)
- no-download: version info visible but download URLs stripped
- hidden: empty feed returned without a valid license key

The "no-download" mode is the key commercial pattern — customers
see updates exist (motivating purchase/renewal) but cannot download
without a valid key. Joomla shows "update available" in admin.

Applied consistently across all update feed endpoints (Joomla XML,
Dolibarr JSON, WordPress JSON) via the shared validateUpdateKey()
which now returns a stripDownloads flag.

Also: when licensing is enabled, the release listing page requires
login. Anonymous users are redirected to the login page. This
prevents browsing release notes and download links without auth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 06:38:09 -05:00
Jonathan Miller 1fabdb94ec feat(updates): WordPress PUC-compatible update feed (#351)
New endpoint: GET /{owner}/{repo}/updates/wordpress.json

Generates JSON compatible with the YahnisElsts plugin-update-checker
library — the standard for commercial WordPress plugin self-hosted
updates. Returns name, slug, version, download_url, homepage,
requires_php, author, sections (changelog HTML), icons, and banners.

License key validation: reads from ?license_key=, ?dlid=, or ?key=
query params (PUC sends these via addQueryArgFilter). When RequireKey
is enabled, returns minimal empty response without download_url.

Changelog section built from release notes (last 10 stable releases),
converting markdown list items to HTML <ul>/<li> elements.

Icon/banner URLs point to conventional paths in the repo:
  assets/icon-128x128.png, assets/icon-256x256.png
  assets/banner-772x250.png, assets/banner-1544x500.png

Route registered at /updates/wordpress.json alongside existing
/updates.xml (Joomla) and /updates/dolibarr.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 00:01:16 -05:00
Jonathan Miller 7da0e025da feat(updates): include SHA256 from sidecar files in Joomla updates.xml
Read the .sha256 sidecar attachment (generated by
GenerateReleaseChecksums) and populate the <sha256> element in the
update XML. This matches the pattern used by Akeeba (sha512) and
JCE (sha256 + sha384 + sha512) for integrity verification.

Also fix zip attachment filter to skip .sha256 sidecar files when
selecting the download URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 05:04:24 -05:00
Jonathan Miller 0e09723d2a fix(updates): map stream names to Joomla-standard tag values
Joomla only recognizes: dev, alpha, beta, rc, stable. Our internal
stream names use longer forms (development, release-candidate).
Add joomlaTagName() to map between conventions in the <tags><tag>
XML element.

Without this, Joomla's update channel filter ignores entries with
non-standard tag values like "release-candidate" or "development".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 05:01:59 -05:00
Jonathan Miller 53a5d0b97b feat(licenses): domain lock timer, infourl fix, Akeeba-compatible XML format
Domain lock timer: add DomainLockHours to LicensePackage and
FirstUsedUnix to LicenseKey. During the grace period after first
use, any domain is accepted and auto-added to the restriction list.
After the grace period, only listed domains are allowed. Set 0 for
immediate lock-on-first-use (default).

Fix infourl: default to /releases listing page instead of specific
tag page. Falls back to SupportURL or InfoURL if configured.

Match Akeeba Backup Pro XML format: downloadkey prefix is "dlid="
(not "&dlid="), matching how Joomla stores extra_query. Verified
against production Akeeba/JCE/AdminTools manifests via SSH.

Update migration v340 with FirstUsedUnix and DomainLockHours columns.

Add DomainLockHours field to create/edit package forms for both
repo and org levels with help text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 05:00:50 -05:00
Jonathan Miller 448b7d3ab0 feat(licenses): archive, search, download gating, changelog XML, and expanded permissions
Migration v340: sync all missing columns (key_raw, payment_ref,
last_heartbeat_unix, is_archived, licensing_enabled, download_gating,
support_url, and all extension metadata fields).

Package archiving (#384): add IsArchived field with archive/unarchive
handlers and collapsible "Archived Packages" section in templates.
Existing keys from archived packages continue to work.

Expanded delete permissions (#385): org owners and site admins can
permanently delete packages and keys (previously site admin only).

Search (#392): server-side search across key_prefix, key_raw,
licensee_name, licensee_email, domain_restriction, and payment_ref
via ?q= query parameter on both repo and org licenses pages.

Sortable tables (#390): Fomantic UI sortable class on keys table
with new Domain column showing DomainRestriction per key.

Download gating (#347): three modes — none, prerelease-only, and
all downloads. CheckDownloadGating() intercepts both release
attachment and git archive download handlers.

Support URL (#393): configurable SupportURL field on
UpdateStreamConfig for wiki or external site links.

Changelog XML (#343): ServeChangelogXML endpoint at /changelog.xml
generates Joomla-compatible changelog from release notes. Parses
Keep-a-Changelog markdown sections into <security>, <fix>,
<addition>, <change>, <remove>, <note> XML elements.

API renew (#387): POST /license-keys/{id}/renew endpoint extends
key expiration by package duration.

Closes #384, #385, #386, #387, #389, #390, #392, #393
Refs #343, #346, #347

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 04:45:20 -05:00
Jonathan Miller d30e7d7a5a feat(updates): extension metadata settings for update feed generation
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 7s
PR RC Release / Build RC Release (pull_request) Failing after 27s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
- Add configurable fields: element name, display name, description,
  extension type, maintainer, maintainer URL, info URL, target version,
  PHP minimum
- Add platform dropdown: joomla, dolibarr, wordpress, prestashop,
  drupal, composer, both
- Update Joomla XML generator to use metadata from config (falls back
  to repo-derived values when not set)
- Add GetEffectiveConfig() for resolving repo → org → nil config chain
- Add locale keys for all new settings fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 10:58:45 -05:00
Jonathan Miller 9a5720e8ad chore: rename Go module from git. to code.mokoconsulting.tech (#336)
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Full namespace migration: update the Go module path and all import
statements from git.mokoconsulting.tech to code.mokoconsulting.tech.
Also updates all URL references in templates, workflows, configs,
tests, and documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 10:28:25 -05:00
Jonathan Miller 5e4ac1617e fix(updates): correct dlid prefix and align XML with Joomla standard
- Fix downloadkey prefix: "&dlid=" → "dlid=" (Joomla handles the separator)
- Reorder XML fields to match Akeeba/Joomla convention
- Add sha512 and php_minimum optional fields to update XML struct

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 10:23:20 -05:00
Jonathan Miller b77da17f38 feat(licenses): implement full commercial license management system
Add key editing, domain enforcement, purchase webhooks, public
validation API, channels multiselect, Joomla downloadkey element,
licensing feature toggle, unified update system, release tag
enforcement, heartbeat tracking, and improved settings UX.

Phase 1: Full key display with AbsoluteShort dates, master package
protection (hide edit/delete in UI, reject in handlers).

Phase 2: Key edit page with template, handlers, and routes for both
repo and org levels. Master keys redirect away.

Phase 3: Domain restriction checking against CSV allowlist,
MaxSites enforcement via CountUniqueDomainsByKey and
IsDomainKnownForKey, dlid query param support for Joomla.

Phase 4: Purchase webhook (POST /license-keys/purchase) with
PaymentRef idempotency. Public validation endpoint
(POST /license-keys/validate) outside auth middleware.
PATCH /license-keys/{id} for API key editing.

Phase 5: Channels multiselect using org UpdateStreamConfig streams
rendered as checkboxes, stored as JSON arrays.

Additional: downloadkey XML element, LicensingEnabled toggle on
UpdateStreamConfig, Dolibarr endpoint unified with key validation,
release tag suffix enforcement, LastHeartbeatUnix field with
TouchHeartbeat, and cleaned-up settings pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 01:31:51 -05:00
Jonathan Miller a88e3f8787 feat(updates): org-level default streams with per-repo override
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m29s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Add configurable update streams at org and repo level:

- UpdateStreamConfig model: stores stream mode (joomla/custom) and
  custom stream definitions (name, suffix, description)
- Resolution chain: repo override → org default → Joomla defaults
- MatchStreamFromTag: matches release tags to streams using configured
  suffixes (longest match wins)
- Both Joomla XML and Dolibarr JSON generators use effective streams
- DB migration v336 creates update_stream_config table
- Default Joomla streams: stable, release-candidate, beta, alpha,
  development
- Custom streams support any tag suffix (e.g. -lts, -nightly, -security)

Ref #265

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 20:49:46 -05:00
Jonathan Miller 50454db3fb feat(updates): use full Joomla channel names in update feeds
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m0s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Use the full Joomla convention for update stream tag names:
- dev → development
- rc → release-candidate
- alpha, beta, stable unchanged

Add NormalizeChannel() helper that maps shorthand names (dev, rc)
to full names so license key allowed_channels work with either
format. Applied in XML generation, JSON generation, and key
validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 20:31:43 -05:00
Jonathan Miller 627a22ee53 feat(updates): license key system and Dolibarr endpoint (Phase 2-3)
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 54s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Add license key data model and Dolibarr update feed endpoint:

License key system:
- license_package table: subscription tiers with duration, max sites,
  repo scope (org-wide or specific repos), and allowed update channels
- license_key table: individual keys with SHA-256 hashed storage,
  domain restriction, custom start/end dates, internal/master key flag
- license_key_usage table: tracks update check activity per key
- DB migration v335 creates all three tables

Update server enhancements:
- Dolibarr JSON endpoint at /{owner}/{repo}/updates/dolibarr.json
- License key validation on update endpoints via ?key=MOKO-XXXX param
- Channel filtering: packages restrict which update streams keys access
- Invalid keys get empty XML response (Joomla-compatible "no updates")
- Usage tracking records domain, IP, user agent, version on each check

Key design decisions:
- Org-level master keys: IsInternal=true, package RepoScope="all"
- Keys stored as SHA-256 hashes, raw key only shown at creation
- Packages define allowed channels (e.g. ["stable","rc"] for Pro tier)
- MOKO-XXXX-XXXX-XXXX-XXXX format for license keys

Ref #239

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 13:09:47 -05:00
Jonathan Miller 6c06384966 feat(updates): built-in Joomla update server endpoint
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m4s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Add GET /{owner}/{repo}/updates.xml that dynamically generates a
Joomla-compatible updates.xml from the repository's releases.

Features:
- Automatically maps release tags to channels (stable/rc/beta/alpha/dev)
- Finds .zip attachments for download URLs, falls back to archive URL
- Emits one entry per channel (latest release wins)
- Extracts version from tag names, strips common prefixes
- Publicly accessible (no auth required) for Joomla update clients

This is Phase 1 of #239 — the core dynamic update feed generation.
Future phases will add license key gating, Dolibarr support, and
repo settings UI.

Ref #239

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 12:54:31 -05:00