Compare commits

...

54 Commits

Author SHA1 Message Date
jmiller b545e7414c chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-24 11:50:59 +00:00
jmiller 024b00a38e chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-24 11:50:50 +00:00
jmiller 8f9a43f32c chore: sync notify.yml from Template-Generic [skip ci] 2026-06-24 11:50:35 +00:00
jmiller 225ea65881 chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-06-24 11:50:22 +00:00
jmiller 4c018fac62 chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-24 11:50:18 +00:00
jmiller 41e48945fd chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-23 23:12:05 +00:00
jmiller 7d28fe522d Merge pull request 'fix: correct dev health URL to git.dev.mokoconsulting.tech' (#695) from fix/dev-deploy-hostname into main
Deploy MokoGitea / Verify dev environment is healthy (push) Successful in 2s
Deploy MokoGitea / deploy (push) Has been cancelled
fix: correct dev health URL to git.dev.mokoconsulting.tech (#695)
2026-06-23 23:08:13 +00:00
Jonathan Miller 8780ec7c5f fix: correct dev health URL to git.dev.mokoconsulting.tech
Universal: PR Check / Branch Policy (pull_request) Failing after 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 9s
PR RC Release / Build RC Release (pull_request) Failing after 59s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m22s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m20s
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
Was dev.git.mokoconsulting.tech which doesn't exist. The dev instance
runs at git.dev.mokoconsulting.tech (port 3090 behind nginx).
2026-06-23 18:06:49 -05:00
jmiller 9106cfe254 Merge pull request 'feat: wiki search, metadata deploy fields, workflow cleanup' (#694) from dev into main
Deploy MokoGitea / Verify dev environment is healthy (push) Failing after 2s
Deploy MokoGitea / deploy (push) Has been cancelled
Merge PR #694: wiki search, metadata deploy fields, workflow cleanup
2026-06-23 22:34:39 +00:00
Jonathan Miller f7d70ae95a docs: update changelog with wiki search, metadata deploy fields, fixes
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Universal: Auto Version Bump / Version Bump (push) Successful in 17s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 1m30s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m35s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 2m30s
PR RC Release / Build RC Release (pull_request) Failing after 2m28s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 1m16s
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
2026-06-23 17:33:35 -05:00
Jonathan Miller 5c43cf1f02 fix(metadata): support partial updates — only sent fields are changed
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 10s
Universal: Auto Version Bump / Version Bump (push) Successful in 16s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m30s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m32s
PR RC Release / Build RC Release (pull_request) Failing after 2m28s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 2m32s
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
Previously PUT /metadata replaced all fields, wiping any not included
in the request. Now loads existing metadata first and merges only the
fields present in the JSON body.
2026-06-23 17:28:40 -05:00
Jonathan Miller c8c74c7afe fix: handle DB errors in licensing API, fix wiki API URL-decode fallback
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 16s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m12s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 1m46s
PR RC Release / Build RC Release (pull_request) Failing after 1m38s
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
- licensing/manage.go: capture Update/Delete errors instead of silently
  discarding them (UpdateLicense, UpdateTier, DeleteTier)
- wiki.go API: fix findEntryForFile to allow URL-decode fallback for
  non-ASCII page names (was returning on ErrNotExist instead of falling through)
2026-06-23 17:25:47 -05:00
Jonathan Miller 7b68963b67 feat(metadata): add deploy fields to repo metadata API (#692)
Universal: Auto Version Bump / Version Bump (push) Successful in 18s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 1m2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m11s
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 2s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Build & Release / Promote to RC (pull_request) Failing after 18s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 1m16s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m23s
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
- Migration v360: adds deploy_host, deploy_port, deploy_user, deploy_path,
  docker_image, docker_registry, container_name, health_url to repo_manifest
- API: GET/PUT /metadata now includes deploy fields
- Settings: preserve deploy fields on web UI save
- Remove 4 unneeded workflows (gitleaks, npm-publish, notify, workflow-sync)
  - gitleaks will become built-in (#692)
  - npm-publish/notify not applicable to Go repo
  - workflow-sync moving to MokoCLI
2026-06-23 14:32:31 -05:00
jmiller 22c2a99f8b chore: remove security-audit.yml -- handled by MokoGitea
Deploy MokoGitea / Verify dev environment is healthy (push) Failing after 2s
Deploy MokoGitea / deploy (push) Has been cancelled
2026-06-23 18:26:46 +00:00
jmiller c974970118 chore: remove security-audit.yml -- handled by MokoGitea
Universal: Auto Version Bump / Version Bump (push) Successful in 19s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m36s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 1m6s
2026-06-23 18:04:41 +00:00
jmiller c9a8deee0e chore: remove deploy-manual.yml -- no longer needed
Universal: Auto Version Bump / Version Bump (push) Successful in 25s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 3m2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m33s
2026-06-23 17:59:24 +00:00
jmiller ea05851d0a chore: remove composer-publish.yml -- no longer needed
Universal: Auto Version Bump / Version Bump (push) Successful in 23s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m44s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 3m12s
2026-06-23 17:59:09 +00:00
Jonathan Miller 1178975be3 feat(wiki): full-text search across wiki pages (#550)
Universal: Auto Version Bump / Version Bump (push) Successful in 20s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 1m25s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m16s
- Web: WikiSearch handler with case-insensitive search of titles and content
- Web: search.tmpl with search form and results display
- Web: "Search wiki" link added to wiki dropdown menu
- API: GET /wiki/search?q=term endpoint with pagination
- Recursive traversal handles nested folder wikis
2026-06-23 12:05:48 -05:00
Jonathan Miller b283fad8bd docs: update README and changelog with wiki features documentation
Universal: Auto Version Bump / Version Bump (push) Successful in 20s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 1m1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m3s
- README: rewrite with current feature set, fix stale code.mokoconsulting.tech URLs
- CHANGELOG: add missing wiki feature entries (#667, #668, #671, #673, #674, #675)
- Wiki Features page published to org wiki (standards/Wiki-Features)
2026-06-23 09:08:58 -05:00
Jonathan Miller a792772397 feat: dev environment deploy gate for production safety
Universal: Auto Version Bump / Version Bump (push) Successful in 20s
Deploy MokoGitea / Verify dev environment is healthy (push) Failing after 2s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 1m30s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m23s
Deploy MokoGitea / deploy (push) Has been cancelled
- New deploy-dev.yml: builds and deploys to dev.git.mokoconsulting.tech
  on push to dev branch
- Production deploy (deploy-mokogitea.yml): now checks dev health before
  building. If dev.git.mokoconsulting.tech/api/healthz fails, production
  deploy is blocked.
- Flow: push to dev → dev builds/deploys → verify healthy → merge to
  main → production checks dev health → builds/deploys production
2026-06-23 08:12:22 -05:00
Jonathan Miller 4d1be56bad fix: move CategoryPage type to package level to fix compile error
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 48s
Deploy MokoGitea / deploy (push) Successful in 4m51s
CategoryPage was defined inside WikiCategory() but referenced by
scanCategoryEntries() which is a top-level function. Renamed to
wikiCategoryPage and moved to package scope.
2026-06-23 07:40:19 -05:00
Jonathan Miller 2dc745c5fa feat(wiki): print view, ZIP export, and folder access control (#674, #675)
Universal: Auto Version Bump / Version Bump (push) Successful in 18s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 34s
Deploy MokoGitea / deploy (push) Failing after 4m10s
Print view: clean rendering without navigation chrome for printing.
ZIP export: download entire wiki as ZIP archive of markdown files.
Folder ACL: _access.yml per-folder write protection with role checks.
Resolve merge conflicts between #674 and #675 implementations.
2026-06-22 08:51:58 -05:00
Jonathan Miller 9dda78da7c feat(wiki): print view, ZIP export, and folder access control (#674, #675)
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 27s
Print view: clean page rendering without navigation chrome for printing.
ZIP export: download entire wiki as ZIP archive of markdown files.
Folder ACL: _access.yml files control per-folder write permissions.
Both accessible from wiki pages dropdown menu.
2026-06-22 08:47:36 -05:00
jmiller 6ceef765eb feat(wiki): per-folder access control via _access.yml (#674)
Deploy MokoGitea / deploy (push) Failing after 4m8s
2026-06-22 01:12:47 +00:00
jmiller 23d3528676 feat(wiki): per-folder access control via _access.yml (#674)
Deploy MokoGitea / deploy (push) Failing after 5m28s
2026-06-22 01:08:35 +00:00
jmiller 249b639c70 feat(wiki): per-folder access control via _access.yml (#674)
Universal: Auto Version Bump / Version Bump (push) Successful in 15s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 49s
2026-06-22 01:05:43 +00:00
jmiller 5c9db551dc feat(wiki): per-folder access control via _access.yml (#674) — template
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 41s
2026-06-22 00:54:07 +00:00
jmiller 408f2329b3 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-22 00:35:06 +00:00
Jonathan Miller 827025bd17 feat(wiki): enhanced ToC — collapsible, inline, sticky, frontmatter control (#673)
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 56s
Deploy MokoGitea / deploy (push) Failing after 4m4s
ToC can be controlled via frontmatter: toc=false disables, toc=inline
shows at top of content instead of sidebar. Sidebar ToC is now
collapsible via <details> and sticky on scroll. Inline ToC also
uses collapsible <details> with "Contents" header.
2026-06-21 19:33:57 -05:00
Jonathan Miller 98da1644be feat(wiki): template transclusion — reusable content blocks (#671)
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 53s
Deploy MokoGitea / deploy (push) Failing after 3m57s
Add {{template:Name|key=val}} syntax for embedding reusable content.
Templates stored as _Template/Name.md with {{{key}}} parameter
substitution. Recursive with depth limit of 5. _Template folder
hidden from sidebar tree.
2026-06-21 19:05:12 -05:00
Jonathan Miller db596575a0 feat(wiki): page categories via YAML frontmatter (#668)
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 54s
Deploy MokoGitea / deploy (push) Failing after 5m44s
Add categories support using frontmatter: categories: [arch, api, ref]
Categories render as clickable tags at the bottom of wiki pages.
Category index page lists all pages tagged with a given category.
Frontmatter is stripped before markdown rendering.
2026-06-21 18:32:07 -05:00
Jonathan Miller 3c56dc8814 feat(wiki): revision diff — view changes for any wiki commit (#667)
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m9s
Deploy MokoGitea / deploy (push) Failing after 5m31s
Compare a wiki commit against its parent showing added/removed lines
with color-coded diff view. Accessible via diff icon button in wiki
page header and from revision history. Shows commit metadata
(author, message, timestamp) alongside the diff.
2026-06-21 18:23:09 -05:00
Jonathan Miller dce712fabd Merge remote-tracking branch 'origin/main' into dev
Deploy MokoGitea / deploy (push) Failing after 6m21s
2026-06-21 18:17:44 -05:00
Jonathan Miller 78b0ce9650 fix: org metadata API respects visibility for unauthenticated requests (#690)
Universal: Auto Version Bump / Version Bump (push) Successful in 18s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m28s
Add checkOrgVisibility() guard to issue-statuses, issue-priorities,
and issue-types endpoints. Public orgs remain accessible to everyone.
Private/limited orgs return 404 for unauthenticated callers.
2026-06-21 18:17:00 -05:00
jmiller 500a5be6d7 Merge pull request 'Release: Wiki redirects, code review fixes, bug fixes' (#689) from dev into main
Deploy MokoGitea / deploy (push) Failing after 7m34s
2026-06-21 23:14:07 +00:00
Jonathan Miller 95a747b1d5 docs: update changelog with post-#682 fixes and #672
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m1s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 1m27s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 2m43s
PR RC Release / Build RC Release (pull_request) Failing after 2m5s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 7s
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
2026-06-21 18:13:28 -05:00
Jonathan Miller bb7e99ad40 fix: backlinks template pluralization
Universal: Auto Version Bump / Version Bump (push) Successful in 16s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m6s
Deploy MokoGitea / deploy (push) Failing after 6m34s
2026-06-21 18:10:16 -05:00
Jonathan Miller 6c6b7c888e feat(wiki): page rename with automatic redirects (#672)
Universal: Auto Version Bump / Version Bump (push) Successful in 16s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m25s
Deploy MokoGitea / deploy (push) Successful in 5m57s
When a wiki page is renamed, automatically create a redirect file at
the old path with YAML frontmatter (redirect: new/path). The redirect
is detected in renderViewPage() before markdown rendering, issuing an
HTTP redirect with a flash message "Redirected from OldPage".
2026-06-21 18:00:56 -05:00
Jonathan Miller 2a1692d599 fix: resolve code review issues in wiki and status features
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 36s
Deploy MokoGitea / deploy (push) Successful in 7m31s
- collectWikiPages: store hyphen-normalized names for flexible lookup
- Backlinks: use wiki_service.GitPathToWebPath for subdirectory URLs
- issue_statuses.tmpl: fix garbled em-dash bytes (0x97 → hyphen)
- backlinks.tmpl: fix pluralization text
2026-06-21 17:57:46 -05:00
Jonathan Miller 6984ac108f Merge remote-tracking branch 'origin/main' into dev
Deploy MokoGitea / deploy (push) Successful in 4m47s
2026-06-21 17:39:36 -05:00
Jonathan Miller 3fdbe94830 fix: use commit.MessageTitle() instead of commit.Message() in wiki recent changes
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 35s
The Commit type embeds CommitMessage which provides MessageTitle(),
MessageUTF8(), and MessageBody() — not Message().
2026-06-21 17:38:43 -05:00
jmiller e937dd8d8b Merge pull request 'Release: Wiki system, licensing, issue statuses, metadata API' (#682) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m39s
2026-06-21 22:26:33 +00:00
Jonathan Miller e7b70f54ed docs: add #670 to changelog
Universal: PR Check / Branch Policy (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 21s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 1m50s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Failing after 1m52s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 1m11s
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 32s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-06-21 17:24:17 -05:00
Jonathan Miller b161561571 Merge remote-tracking branch 'origin/main' into dev
Deploy MokoGitea / deploy (push) Failing after 5m8s
2026-06-21 17:21:01 -05:00
Jonathan Miller b981cf72e3 feat(wiki): recent changes page — cross-page edit activity (#670)
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m4s
Shows all recent wiki edits with page name, author, edit summary,
and timestamp. Paginated. Accessible via "Recent changes" in the
wiki pages dropdown menu.
2026-06-21 17:20:12 -05:00
jmiller 9964c7e16c chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-21 22:02:50 +00:00
Jonathan Miller ff27e77c37 docs: update changelog with session 2 changes
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m6s
2026-06-21 16:54:20 -05:00
Jonathan Miller 04ce7dc896 feat(wiki): internal wikilinks with [[Page Name]] syntax (#666)
Deploy MokoGitea / deploy (push) Successful in 4m51s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 40s
Add Wikipedia-style [[Page Name]] and [[Page|Display Text]] syntax.
Existing pages render as normal links; non-existent pages render as
red "new page" links. Supports [[folder/Page]], [[Page#Section]],
and [[#Anchor]] patterns.
2026-06-21 15:42:50 -05:00
Jonathan Miller f87f904a21 fix: remove Version field from metadata settings template
Version is no longer stored in RepoMetadata struct — it comes from
the metadata API. The template reference caused a 500 error.
2026-06-21 15:42:46 -05:00
Jonathan Miller fc72d8e90a feat(wiki): add backlinks — "What links here" for wiki pages (#669)
Deploy MokoGitea / deploy (push) Successful in 3m27s
Scan all wiki pages for references to the current page and display
them on a dedicated backlinks page. Uses content search across
markdown files (no database needed). Adds cross-reference button
to wiki page header.
2026-06-21 11:45:20 -05:00
Jonathan Miller 71d52e432e feat: enforce required baseline issue statuses (#681)
Deploy MokoGitea / deploy (push) Successful in 5m8s
Add IsRequired field to IssueStatusDef. Open and Closed statuses are
seeded as required and cannot be deleted. Delete attempts return an
error flash in the web UI and ErrStatusRequired in the model layer.
API response now includes is_required field.
2026-06-21 11:29:49 -05:00
jmiller 172303b61f chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-21 16:05:39 +00:00
Jonathan Miller bfb4b53da3 feat: add folder-based tree sidebar to org wiki (#680)
Deploy MokoGitea / deploy (push) Successful in 3m31s
Replace flat page list with hierarchical folder tree in org wiki sidebar.
_Sidebar.md takes precedence when present; otherwise auto-generates
collapsible folder menus up to 2 levels deep.
2026-06-21 11:00:18 -05:00
Jonathan Miller 9149fa100c feat: make metadata/manifest GET endpoint publicly accessible (#676)
Deploy MokoGitea / deploy (push) Successful in 3m30s
Remove reqRepoReader auth requirement from GET /repos/{owner}/{repo}/metadata
and /manifest endpoints. PUT (update) still requires token + admin.
2026-06-21 10:20:56 -05:00
35 changed files with 2707 additions and 802 deletions
+52 -6
View File
@@ -10,9 +10,9 @@
# VERSION: 05.00.00 # VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml # BRIEF: Universal build & release detects platform from manifest.xml
# #
# +========================================================================+ # +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE | # | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+ # +=======================================================================+
# | | # | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | | # | |
@@ -21,15 +21,24 @@
# | dolibarr: mod*.class.php, update.txt, dev version reset | # | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream | # | generic: README-only, no update stream |
# | | # | |
# +========================================================================+ # +=======================================================================+
name: "Universal: Build & Release" name: "Universal: Build & Release"
on: on:
pull_request: pull_request:
types: [opened, closed] types: [opened, synchronize, closed]
branches: branches:
- main - main
paths-ignore:
- '.mokogitea/workflows/**'
- '*.md'
- 'wiki/**'
- '.editorconfig'
- '.gitignore'
- '.gitattributes'
- '.gitmessage'
- 'LICENSE'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
action: action:
@@ -51,12 +60,13 @@ permissions:
contents: write contents: write
jobs: jobs:
# ── PR Opened → Rename branch to RC and build RC release ───────────────────── # ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
promote-rc: promote-rc:
name: Promote to RC name: Promote to RC
runs-on: release runs-on: release
if: >- if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) || (github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps: steps:
@@ -149,7 +159,7 @@ jobs:
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ──────────────────── # ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
release: release:
name: Build & Release Pipeline name: Build & Release Pipeline
runs-on: release runs-on: release
@@ -241,11 +251,47 @@ jobs:
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT" [ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]]; then
echo "tag=stable" >> "$GITHUB_OUTPUT" echo "tag=stable" >> "$GITHUB_OUTPUT"
echo "release_tag=stable" >> "$GITHUB_OUTPUT" echo "release_tag=stable" >> "$GITHUB_OUTPUT"
else
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
fi
echo "branch=main" >> "$GITHUB_OUTPUT" echo "branch=main" >> "$GITHUB_OUTPUT"
echo "Published version: ${VERSION}" echo "Published version: ${VERSION}"
- name: "Create semver tag for non-Joomla repos"
id: semver
if: |
steps.version.outputs.skip != 'true' &&
!startsWith(steps.platform.outputs.platform, 'joomla')
run: |
VERSION="${{ steps.version.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
SEMVER_TAG="v${VERSION}"
echo "Creating semver tag: ${SEMVER_TAG}"
# Create the git tag via API
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/tags" \
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "Created semver tag: ${SEMVER_TAG}"
elif [ "$HTTP_CODE" = "409" ]; then
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
else
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
fi
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
- name: Update release notes and promote changelog - name: Update release notes and promote changelog
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
-76
View File
@@ -1,76 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
name: "Publish to Composer"
on:
push:
tags:
- 'v*'
- '[0-9]*.[0-9]*.[0-9]*'
release:
types: [published]
workflow_dispatch:
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
publish:
name: Publish Package
runs-on: ubuntu-latest
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip publish]')
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
- name: Install dependencies
run: composer install --no-dev --no-interaction --prefer-dist --quiet
- name: Determine version
id: version
run: |
VERSION=$(php -r "echo json_decode(file_get_contents('composer.json'))->version;")
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Package version: ${VERSION}"
# Gitea Composer Registry — auto-publishes from tags
# The tag push itself registers the package at:
# https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer
- name: Verify Gitea registry
run: |
echo "Gitea Composer registry auto-publishes from tags."
echo "Package available at: ${GITEA_URL}/api/packages/MokoConsulting/composer"
echo "Install: composer require mokoconsulting/mokocli"
# Packagist — notify of new version
- name: Notify Packagist
if: secrets.PACKAGIST_TOKEN != ''
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "Notifying Packagist of version ${VERSION}..."
curl -sf -X POST \
-H "Content-Type: application/json" \
-d '{"repository":{"url":"https://git.mokoconsulting.tech/MokoConsulting/mokocli"}}' \
"https://packagist.org/api/update-package?username=mokoconsulting&apiToken=${{ secrets.PACKAGIST_TOKEN }}" \
&& echo "Packagist notified" \
|| echo "::warning::Packagist notification failed (package may not be registered yet)"
- name: Summary
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "## Composer Package Published" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Registry | Status |" >> $GITHUB_STEP_SUMMARY
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Gitea | \`composer require mokoconsulting/mokocli:${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Packagist | \`composer require mokoconsulting/mokocli\` |" >> $GITHUB_STEP_SUMMARY
+109
View File
@@ -0,0 +1,109 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# BRIEF: Build and deploy MokoGitea to dev environment on push to dev branch.
# Production deploy (deploy-mokogitea.yml) only succeeds if dev is healthy.
name: Deploy MokoGitea (Dev)
on:
push:
branches:
- dev
concurrency:
group: deploy-mokogitea-dev
cancel-in-progress: true
env:
REGISTRY: git.mokoconsulting.tech
IMAGE: mokoconsulting/mokogitea
DEPLOY_HOST: git.mokoconsulting.tech
DEPLOY_PORT: 2918
DEPLOY_USER: mokoconsulting
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
deploy-dev:
name: "Build & Deploy to Dev"
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine version
id: config
run: |
VERSION=$(git describe --tags --always 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)")
echo "tag=${VERSION}-dev" >> $GITHUB_OUTPUT
echo "Version: ${VERSION}-dev"
- name: Write deploy key
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
mkdir -p ~/.ssh
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
- name: Build and deploy to dev via SSH
env:
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
run: |
HEALTH_FMT='${{ '{{' }}.State.Health.Status${{ '}}' }}'
ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} \
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} bash -s <<DEPLOY_EOF
set -e
echo 'SSH connected to dev environment'
echo 'Cleaning Docker build cache...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
echo 'Pulling source...'
SOURCE_DIR=/opt/gitea-dev/source
if [ ! -d \$SOURCE_DIR/.git ]; then
git clone -b dev https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git \$SOURCE_DIR
fi
cd \$SOURCE_DIR
git remote set-url origin https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git 2>/dev/null || true
git fetch origin dev
git reset --hard origin/dev
echo 'Building Docker image...'
docker build --no-cache --build-arg GOFLAGS='-p 1' \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:\$TAG \
-f Dockerfile .
echo 'Pushing to registry...'
echo '\$REGISTRY_TOKEN' | docker login ${{ env.REGISTRY }} -u ${{ env.DEPLOY_USER }} --password-stdin
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:\$TAG
echo 'Restarting dev container...'
cd /opt/gitea-dev
sed -i "s|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:\$TAG|" docker-compose.yml
docker compose up -d mokogitea-dev
echo 'Health check...'
for i in 1 2 3 4 5 6 7 8; do
sleep 15
if docker inspect --format='\$HEALTH_FMT' mokogitea-dev 2>/dev/null | grep -q healthy; then
echo 'Dev container healthy!'
exit 0
fi
echo "Waiting... (attempt \$i/8)"
done
echo 'Health check failed'
docker logs mokogitea-dev --tail 20
exit 1
DEPLOY_EOF
- name: Verify dev instance
run: |
sleep 5
curl -sf https://git.dev.mokoconsulting.tech/api/healthz && echo " Dev API healthy"
+16
View File
@@ -36,7 +36,23 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs: jobs:
check-dev:
name: "Verify dev environment is healthy"
runs-on: ubuntu-latest
steps:
- name: Check dev health
run: |
echo "Checking git.dev.mokoconsulting.tech health..."
if curl -sf --max-time 10 https://git.dev.mokoconsulting.tech/api/healthz; then
echo " Dev environment is healthy — proceeding with production deploy"
else
echo "::error::Dev environment is NOT healthy — blocking production deploy"
echo "Deploy to dev first (push to dev branch) and verify it passes before merging to main."
exit 1
fi
deploy: deploy:
needs: check-dev
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout source (for version detection) - name: Checkout source (for version detection)
-51
View File
@@ -1,51 +0,0 @@
name: Publish MCP to npm
on:
push:
branches: [main]
paths:
- '.mokogitea/mcp/**'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install and build
working-directory: .mokogitea/mcp
run: |
npm ci
npx tsc
- name: Check version change
id: version
working-directory: .mokogitea/mcp
run: |
LOCAL_VERSION=$(node -p "require('./package.json').version")
NPM_VERSION=$(npm view @mokoconsulting/mokogitea-mcp version 2>/dev/null || echo "0.0.0")
if [ "$LOCAL_VERSION" != "$NPM_VERSION" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo "Version changed: $NPM_VERSION -> $LOCAL_VERSION"
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "Version unchanged: $LOCAL_VERSION"
fi
- name: Publish to npm
if: steps.version.outputs.changed == 'true'
working-directory: .mokogitea/mcp
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish to Gitea registry
if: steps.version.outputs.changed == 'true'
working-directory: .mokogitea/mcp
run: |
npm publish --registry ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/npm/ \
--//$(echo "${{ github.server_url }}" | sed 's|https://||')/api/packages/${{ github.repository_owner }}/npm/:_authToken=${{ secrets.MOKOGITEA_TOKEN }}
+16
View File
@@ -88,8 +88,20 @@ jobs:
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Check platform eligibility (Joomla only)
id: eligibility
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"
else
echo "proceed=false" >> "$GITHUB_OUTPUT"
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
fi
- name: Resolve metadata and bump version - name: Resolve metadata and bump version
id: meta id: meta
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
# Auto-detect stability from branch name on push, or use input on dispatch # Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then if [ "${{ github.event_name }}" = "push" ]; then
@@ -166,6 +178,7 @@ jobs:
- name: Create release - name: Create release
id: release id: release
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
@@ -176,6 +189,7 @@ jobs:
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md - name: Update release notes from CHANGELOG.md
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
@@ -212,6 +226,7 @@ jobs:
- name: Build package and upload - name: Build package and upload
id: package id: package
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
@@ -225,6 +240,7 @@ jobs:
# No need to build, commit, or sync updates.xml from workflows # No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)" - name: "Delete lesser pre-release channels (cascade)"
if: steps.eligibility.outputs.proceed == 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
-82
View File
@@ -1,82 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/security-audit.yml
# VERSION: 01.00.00
# BRIEF: Dependency vulnerability scanning for composer and npm packages
name: "Universal: Security Audit"
on:
schedule:
- cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC
pull_request:
branches:
- main
paths:
- 'composer.json'
- 'composer.lock'
- 'package.json'
- 'package-lock.json'
workflow_dispatch:
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
jobs:
audit:
name: Dependency Audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Composer audit
if: hashFiles('composer.lock') != ''
run: |
echo "=== Composer Security Audit ==="
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1
fi
composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt
RESULT=$?
if [ $RESULT -ne 0 ]; then
echo "::warning::Composer vulnerabilities found"
echo "composer_vulnerable=true" >> "$GITHUB_ENV"
else
echo "No known vulnerabilities in composer dependencies"
fi
- name: NPM audit
if: hashFiles('package-lock.json') != ''
run: |
echo "=== NPM Security Audit ==="
npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true
if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then
echo "No known vulnerabilities in npm dependencies"
else
echo "::warning::NPM vulnerabilities found"
echo "npm_vulnerable=true" >> "$GITHUB_ENV"
fi
- name: Notify on vulnerabilities
if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true'
run: |
REPO="${{ github.event.repository.name }}"
curl -sS \
-H "Title: ${REPO} has vulnerable dependencies" \
-H "Tags: lock,warning" \
-H "Priority: high" \
-d "Security audit found vulnerabilities. Review dependency updates." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
+130
View File
@@ -0,0 +1,130 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml
# VERSION: 01.00.00
# BRIEF: Set or reset the extension version across all version-bearing files
name: "Joomla: Set Version"
on:
workflow_dispatch:
inputs:
version:
description: "Version number (e.g. 01.00.00)"
required: true
type: string
branch:
description: "Branch to update (default: current)"
required: false
type: string
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
set-version:
name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
VERSION="${{ inputs.version }}"
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
exit 1
fi
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.MOKOGITEA_TOKEN || secrets.GA_TOKEN || github.token }}
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1
- name: Update manifest version
run: |
MANIFEST=""
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla extension manifest found — skipping manifest update"
else
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
fi
- name: Update README.md version
run: |
if [ -f "README.md" ]; then
if grep -qP '^\s*VERSION:\s*\d' README.md; then
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
echo "README.md version updated to ${VERSION}"
else
echo "::warning::No VERSION line found in README.md — skipping"
fi
fi
- name: Update CHANGELOG.md
run: |
if [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
# Check if this version already has an entry
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
else
# Insert new version entry after [Unreleased] or at the top after header
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
else
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
fi
echo "CHANGELOG.md: added entry for ${VERSION}"
fi
else
echo "::warning::No CHANGELOG.md found — skipping"
fi
- name: Update FILE INFORMATION blocks
run: |
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
while IFS= read -r -d '' FILE; do
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
echo "Updated FILE INFORMATION VERSION in ${FILE}"
fi
done
- name: Commit and push
run: |
git config user.name "Moko Consulting [bot]"
git config user.email "hello@mokoconsulting.tech"
git add -A
if git diff --cached --quiet; then
echo "No version changes detected — nothing to commit"
else
git commit -m "chore: set version to ${VERSION} [skip bump]
Authored-by: Moko Consulting"
git push
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
fi
+35
View File
@@ -3,6 +3,17 @@
## [Unreleased] ## [Unreleased]
### Added ### Added
- Wiki full-text search: case-insensitive search across all wiki page titles and content (#550)
- Wiki search API: GET /wiki/search?q=term with paginated JSON results (#550)
- Metadata deploy fields: deploy_host, deploy_port, deploy_user, deploy_path, docker_image, docker_registry, container_name, health_url (#692)
- Metadata API partial updates: PUT /metadata now merges only sent fields instead of replacing all
- Wiki revision diff: line-by-line diff view per commit in wiki page history (#667)
- Wiki categories: YAML frontmatter `categories:` with category index page (#668)
- Wiki template transclusion: `{{template:Name|key=val}}` with `_Template/` folder (#671)
- Wiki enhanced ToC: collapsible, inline via frontmatter, sticky sidebar (#673)
- Wiki folder ACL: `_access.yml` per-folder write protection (#674)
- Wiki print view and ZIP export of all wiki pages (#675)
- Wiki features documentation page in org wiki (standards/Wiki-Features)
- DLID licensing system: license, entitlement, activation, product_tier, audit_log tables (v359 migration) - DLID licensing system: license, entitlement, activation, product_tier, audit_log tables (v359 migration)
- License CRUD with CRC32-checksummed DLID generation and format validation - License CRUD with CRC32-checksummed DLID generation and format validation
- Entitlement model with tier-based rebuild and custom entitlement preservation - Entitlement model with tier-based rebuild and custom entitlement preservation
@@ -10,6 +21,30 @@
- 13 seeded product tiers from base to enterprise - 13 seeded product tiers from base to enterprise
- DLID-gated update XML endpoint: GET /api/v1/licensing/updates/{product}.xml - DLID-gated update XML endpoint: GET /api/v1/licensing/updates/{product}.xml
- Profile repo fallback chain: .mokogitea > .profile > .github - Profile repo fallback chain: .mokogitea > .profile > .github
- Metadata/manifest GET endpoint publicly accessible without auth (#676)
- Org wiki: folder-based collapsible tree sidebar, _Sidebar.md overrides (#680)
- Wiki backlinks: "What links here" page showing all pages referencing current page (#669)
- Wiki wikilinks: [[Page Name]] and [[Page|Display Text]] syntax with red links for missing pages (#666)
- Required baseline issue statuses: Open and Closed are indestructible (is_required flag) (#681)
- Issue status API response includes is_required field
- Wiki recent changes page: cross-page edit activity with pagination (#670)
- Wiki page rename with automatic redirects via YAML frontmatter (#672)
### Fixed
- Licensing API: handle DB write errors in UpdateLicense, UpdateTier, DeleteTier instead of silently discarding
- Wiki API: fix findEntryForFile URL-decode fallback for non-ASCII page names
- Metadata settings template 500 error: removed reference to deleted Version field
- Wiki recent changes: use commit.MessageTitle() instead of commit.Message()
- Wiki backlinks: proper URL encoding for subdirectory pages
- Wiki wikilinks: page existence lookup normalizes spaces and hyphens
- Issue statuses template: garbled em-dash character replaced
### Changed
- Issue status seed defaults: Open, In Progress, Waiting, In Review, Closed, Won't Fix
- Pre-release workflow: auto-bump skipped for non-Joomla repos (platform check)
### Removed
- Workflows: gitleaks.yml, npm-publish.yml, notify.yml, workflow-sync-trigger.yml, composer-publish.yml, deploy-manual.yml, security-audit.yml (not applicable to Go repo)
## [06.19.00] --- 2026-06-20 ## [06.19.00] --- 2026-06-20
+14 -23
View File
@@ -1,38 +1,29 @@
# MokoGitea # MokoGitea
Moko fork of Gitea — adding project board REST API endpoints and custom enhancements Custom Gitea fork with enhanced wiki system, DLID licensing, issue statuses, org metadata, and project board API.
![Language](https://img.shields.io/badge/Go-00ADD8?style=flat-square&logo=go&logoColor=white) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green?style=flat-square) ![Wiki](https://img.shields.io/badge/wiki-MokoGitea-blue?style=flat-square) ![Language](https://img.shields.io/badge/Go-00ADD8?style=flat-square&logo=go&logoColor=white) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green?style=flat-square)
Custom Gitea fork with Project Board API
--- ---
## Pages ## Key Features
- [Branding](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/Branding) - **Wiki System** -- wikilinks, categories, backlinks, template transclusion, revision diffs, rename redirects, folder ACL, enhanced ToC, print view, ZIP export ([details](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/standards/Wiki-Features))
- [Deployment](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/Deployment) - **DLID Licensing** -- license management, entitlements, domain activations, ed25519-signed downloads
- [Project API](Project API) - **Issue Statuses** -- custom workflow statuses per org with required baseline protection
- [roadmap](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/roadmap) - **Org Metadata** -- per-repo metadata API (public GET, admin PUT), platform detection for versioning
- **Project Board API** -- REST endpoints for project columns and cards
--- - **Dev Deploy Gate** -- builds deploy to dev environment first, production checks dev health
**Category:** Infrastructure | **Platform:** [MokoPlatform wiki](https://code.mokoconsulting.tech/MokoConsulting/MokoPlatform/wiki)
---
---
## Documentation ## Documentation
Full documentation is available on the [Wiki](https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki). - [Org Wiki](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/) -- standards, CLI reference, API docs
- [Wiki Features](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/standards/Wiki-Features) -- all 10 wiki enhancements
- [Licensing API](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/api/Licensing-API)
## Contributing ## Contributing
See the wiki for development guidelines and contribution instructions. See the [org wiki](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki/) for development guidelines, coding standards, and contribution instructions.
## License ## License
@@ -40,4 +31,4 @@ This project is licensed under the GNU General Public License v3.0 or later -- s
--- ---
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://code.mokoconsulting.tech/MokoConsulting/MokoPlatform/wiki/Home)* *[Moko Consulting](https://mokoconsulting.tech)*
+32 -6
View File
@@ -22,6 +22,7 @@ type IssueStatusDef struct {
Color string `xorm:"VARCHAR(7)"` // hex color, e.g. "#e11d48" Color string `xorm:"VARCHAR(7)"` // hex color, e.g. "#e11d48"
Description string `xorm:"TEXT"` Description string `xorm:"TEXT"`
ClosesIssue bool `xorm:"NOT NULL DEFAULT false 'closes_issue'"` ClosesIssue bool `xorm:"NOT NULL DEFAULT false 'closes_issue'"`
IsRequired bool `xorm:"NOT NULL DEFAULT false 'is_required'"` // cannot be deleted
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"` SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"` IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
@@ -56,14 +57,15 @@ func GetIssueStatusDefsByOrg(ctx context.Context, orgID int64) ([]*IssueStatusDe
} }
// seedDefaultIssueStatuses creates the standard status presets for an org. // seedDefaultIssueStatuses creates the standard status presets for an org.
// Open and Closed are required (is_required=true) and cannot be deleted.
func seedDefaultIssueStatuses(ctx context.Context, orgID int64) error { func seedDefaultIssueStatuses(ctx context.Context, orgID int64) error {
defaults := []*IssueStatusDef{ defaults := []*IssueStatusDef{
{OrgID: orgID, Name: "In Progress", Color: "#2563eb", Description: "Work is actively being done", SortOrder: 1, IsActive: true}, {OrgID: orgID, Name: "Open", Color: "#2563eb", Description: "New or active issue", ClosesIssue: false, IsRequired: true, SortOrder: 0, IsActive: true},
{OrgID: orgID, Name: "Needs Info", Color: "#f59e0b", Description: "Waiting for more information", SortOrder: 2, IsActive: true}, {OrgID: orgID, Name: "In Progress", Color: "#7c3aed", Description: "Work is actively being done", SortOrder: 1, IsActive: true},
{OrgID: orgID, Name: "Blocked", Color: "#dc2626", Description: "Cannot proceed due to dependency", SortOrder: 3, IsActive: true}, {OrgID: orgID, Name: "Waiting", Color: "#f59e0b", Description: "Blocked or waiting for input", SortOrder: 2, IsActive: true},
{OrgID: orgID, Name: "Resolved", Color: "#16a34a", Description: "Fix implemented and verified", ClosesIssue: true, SortOrder: 4, IsActive: true}, {OrgID: orgID, Name: "In Review", Color: "#0891b2", Description: "PR submitted, awaiting review", SortOrder: 3, IsActive: true},
{OrgID: orgID, Name: "Closed", Color: "#16a34a", Description: "Completed or resolved", ClosesIssue: true, IsRequired: true, SortOrder: 4, IsActive: true},
{OrgID: orgID, Name: "Won't Fix", Color: "#6b7280", Description: "Decided not to address", ClosesIssue: true, SortOrder: 5, IsActive: true}, {OrgID: orgID, Name: "Won't Fix", Color: "#6b7280", Description: "Decided not to address", ClosesIssue: true, SortOrder: 5, IsActive: true},
{OrgID: orgID, Name: "Duplicate", Color: "#8b5cf6", Description: "Already tracked elsewhere", ClosesIssue: true, SortOrder: 6, IsActive: true},
} }
for _, d := range defaults { for _, d := range defaults {
if _, err := db.GetEngine(ctx).Insert(d); err != nil { if _, err := db.GetEngine(ctx).Insert(d); err != nil {
@@ -111,13 +113,37 @@ func UpdateIssueStatusDef(ctx context.Context, def *IssueStatusDef) error {
return err return err
} }
// ErrStatusRequired is returned when trying to delete a required status.
type ErrStatusRequired struct {
ID int64
Name string
}
func (e ErrStatusRequired) Error() string {
return "status is required and cannot be deleted"
}
// IsErrStatusRequired checks if an error is ErrStatusRequired.
func IsErrStatusRequired(err error) bool {
_, ok := err.(ErrStatusRequired)
return ok
}
// DeleteIssueStatusDef deletes a status definition and clears references on issues. // DeleteIssueStatusDef deletes a status definition and clears references on issues.
// Returns ErrStatusRequired if the status is marked as required.
func DeleteIssueStatusDef(ctx context.Context, id int64) error { func DeleteIssueStatusDef(ctx context.Context, id int64) error {
def, err := GetIssueStatusDefByID(ctx, id)
if err != nil {
return err
}
if def.IsRequired {
return ErrStatusRequired{ID: def.ID, Name: def.Name}
}
// Clear status_id on all issues that reference this definition // Clear status_id on all issues that reference this definition
if _, err := db.GetEngine(ctx).Exec("UPDATE issue SET status_id = 0 WHERE status_id = ?", id); err != nil { if _, err := db.GetEngine(ctx).Exec("UPDATE issue SET status_id = 0 WHERE status_id = ?", id); err != nil {
return err return err
} }
_, err := db.GetEngine(ctx).ID(id).Delete(new(IssueStatusDef)) _, err = db.GetEngine(ctx).ID(id).Delete(new(IssueStatusDef))
return err return err
} }
+1
View File
@@ -436,6 +436,7 @@ func prepareMigrationTasks() []*migration {
newMigration(356, "Rename package_type to extension_type in repo manifest", v1_27.RenamePackageTypeToExtensionType), newMigration(356, "Rename package_type to extension_type in repo manifest", v1_27.RenamePackageTypeToExtensionType),
newMigration(357, "Drop display_name from repo manifest and update stream config", v1_27.DropDisplayNameColumns), newMigration(357, "Drop display_name from repo manifest and update stream config", v1_27.DropDisplayNameColumns),
newMigration(358, "Add licensing tables (license, entitlement, activation, product_tier)", v1_27.AddLicensingTables), newMigration(358, "Add licensing tables (license, entitlement, activation, product_tier)", v1_27.AddLicensingTables),
newMigration(359, "Add deploy fields to repo manifest", v1_27.AddDeployFieldsToRepoManifest),
} }
return preparedMigrations return preparedMigrations
} }
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
// AddDeployFieldsToRepoManifest adds deploy configuration columns to repo_manifest.
func AddDeployFieldsToRepoManifest(x *xorm.Engine) error {
type RepoManifest struct {
DeployHost string `xorm:"VARCHAR(255) 'deploy_host'"`
DeployPort string `xorm:"VARCHAR(10) 'deploy_port'"`
DeployUser string `xorm:"VARCHAR(100) 'deploy_user'"`
DeployPath string `xorm:"TEXT 'deploy_path'"`
DockerImage string `xorm:"VARCHAR(255) 'docker_image'"`
DockerRegistry string `xorm:"VARCHAR(255) 'docker_registry'"`
ContainerName string `xorm:"VARCHAR(100) 'container_name'"`
HealthURL string `xorm:"TEXT 'health_url'"`
}
return x.Sync(new(RepoManifest))
}
+10
View File
@@ -50,6 +50,16 @@ type RepoMetadata struct {
ExtensionType string `xorm:"VARCHAR(50) 'extension_type'"` // component, module, plugin, package, template, library, file ExtensionType string `xorm:"VARCHAR(50) 'extension_type'"` // component, module, plugin, package, template, library, file
EntryPoint string `xorm:"TEXT 'entry_point'"` // build entry point path EntryPoint string `xorm:"TEXT 'entry_point'"` // build entry point path
// deploy section
DeployHost string `xorm:"VARCHAR(255) 'deploy_host'"` // SSH host for deploy
DeployPort string `xorm:"VARCHAR(10) 'deploy_port'"` // SSH port (default 2918)
DeployUser string `xorm:"VARCHAR(100) 'deploy_user'"` // SSH user
DeployPath string `xorm:"TEXT 'deploy_path'"` // remote path for source/compose
DockerImage string `xorm:"VARCHAR(255) 'docker_image'"` // e.g. mokoconsulting/mokogitea
DockerRegistry string `xorm:"VARCHAR(255) 'docker_registry'"` // e.g. git.mokoconsulting.tech
ContainerName string `xorm:"VARCHAR(100) 'container_name'"` // Docker container name
HealthURL string `xorm:"TEXT 'health_url'"` // health check URL after deploy
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"` UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
} }
+1
View File
@@ -165,6 +165,7 @@ type IssueStatusDef struct {
Color string `json:"color"` Color string `json:"color"`
Description string `json:"description"` Description string `json:"description"`
ClosesIssue bool `json:"closes_issue"` ClosesIssue bool `json:"closes_issue"`
IsRequired bool `json:"is_required"`
SortOrder int `json:"sort_order"` SortOrder int `json:"sort_order"`
} }
+5 -6
View File
@@ -1314,6 +1314,7 @@ func Routes() *web.Router {
m.Get("/revisions/*", repo.ListPageRevisions) m.Get("/revisions/*", repo.ListPageRevisions)
m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage) m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
m.Get("/pages", repo.ListWikiPages) m.Get("/pages", repo.ListWikiPages)
m.Get("/search", repo.SearchWikiPages)
}, mustEnableWiki) }, mustEnableWiki)
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup) m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown) m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
@@ -1480,12 +1481,10 @@ func Routes() *web.Router {
Delete(reqToken(), repo.DeleteTopic) Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin()) }, reqAdmin())
}, reqAnyRepoReader()) }, reqAnyRepoReader())
m.Combo("/metadata", reqRepoReader(unit.TypeCode)). m.Get("/metadata", repo.GetRepoMetadata)
Get(repo.GetRepoMetadata). m.Put("/metadata", reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
Put(reqToken(), reqAdmin(), repo.UpdateRepoMetadata) m.Get("/manifest", repo.GetRepoMetadata) // backward compat
m.Combo("/manifest", reqRepoReader(unit.TypeCode)). // backward compat m.Put("/manifest", reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
Get(repo.GetRepoMetadata).
Put(reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
// MokoGitea badge engine // MokoGitea badge engine
m.Get("/badge/{type}.svg", repo.GetRepoBadge) m.Get("/badge/{type}.svg", repo.GetRepoBadge)
m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates) m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates)
+12 -3
View File
@@ -207,7 +207,10 @@ func UpdateLicense(ctx *context.APIContext) {
} }
if len(cols) > 0 { if len(cols) > 0 {
cols = append(cols, "updated_at") cols = append(cols, "updated_at")
db.GetEngine(ctx).ID(id).Cols(cols...).Update(license) if _, err := db.GetEngine(ctx).ID(id).Cols(cols...).Update(license); err != nil {
ctx.APIErrorInternal(err)
return
}
} }
ctx.JSON(http.StatusOK, licenseToJSON(ctx, license)) ctx.JSON(http.StatusOK, licenseToJSON(ctx, license))
@@ -399,7 +402,10 @@ func UpdateTier(ctx *context.APIContext) {
} }
if len(cols) > 0 { if len(cols) > 0 {
db.GetEngine(ctx).ID(id).Cols(cols...).Update(tier) if _, err := db.GetEngine(ctx).ID(id).Cols(cols...).Update(tier); err != nil {
ctx.APIErrorInternal(err)
return
}
} }
ctx.JSON(http.StatusOK, tierToJSON(tier)) ctx.JSON(http.StatusOK, tierToJSON(tier))
@@ -427,7 +433,10 @@ func DeleteTier(ctx *context.APIContext) {
return return
} }
db.GetEngine(ctx).ID(id).Delete(new(licensing_model.ProductTier)) if _, err := db.GetEngine(ctx).ID(id).Delete(new(licensing_model.ProductTier)); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
} }
+26
View File
@@ -11,6 +11,19 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
) )
// checkOrgVisibility returns true if the current user can view org metadata.
// Public orgs are visible to everyone. Private/limited orgs require authentication.
func checkOrgVisibility(ctx *context.APIContext) bool {
if ctx.Org.Organization.Visibility == api.VisibleTypePublic {
return true
}
if ctx.Doer == nil {
ctx.APIErrorNotFound()
return false
}
return true
}
// ListIssueStatuses returns active issue status definitions for an org. // ListIssueStatuses returns active issue status definitions for an org.
func ListIssueStatuses(ctx *context.APIContext) { func ListIssueStatuses(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/issue-statuses organization orgListIssueStatuses // swagger:operation GET /orgs/{org}/issue-statuses organization orgListIssueStatuses
@@ -34,6 +47,10 @@ func ListIssueStatuses(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
if !checkOrgVisibility(ctx) {
return
}
defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Org.Organization.ID) defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
@@ -47,6 +64,7 @@ func ListIssueStatuses(ctx *context.APIContext) {
Color: d.Color, Color: d.Color,
Description: d.Description, Description: d.Description,
ClosesIssue: d.ClosesIssue, ClosesIssue: d.ClosesIssue,
IsRequired: d.IsRequired,
SortOrder: d.SortOrder, SortOrder: d.SortOrder,
}) })
} }
@@ -76,6 +94,10 @@ func ListIssuePriorities(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
if !checkOrgVisibility(ctx) {
return
}
defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Org.Organization.ID) defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
@@ -118,6 +140,10 @@ func ListIssueTypes(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
if !checkOrgVisibility(ctx) {
return
}
defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Org.Organization.ID) defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
+72 -22
View File
@@ -32,6 +32,16 @@ type apiMetadata struct {
Language string `json:"language"` Language string `json:"language"`
ExtensionType string `json:"extension_type"` ExtensionType string `json:"extension_type"`
EntryPoint string `json:"entry_point"` EntryPoint string `json:"entry_point"`
// deploy
DeployHost string `json:"deploy_host,omitempty"`
DeployPort string `json:"deploy_port,omitempty"`
DeployUser string `json:"deploy_user,omitempty"`
DeployPath string `json:"deploy_path,omitempty"`
DockerImage string `json:"docker_image,omitempty"`
DockerRegistry string `json:"docker_registry,omitempty"`
ContainerName string `json:"container_name,omitempty"`
HealthURL string `json:"health_url,omitempty"`
} }
// GetRepoMetadata returns the manifest settings for a repository. // GetRepoMetadata returns the manifest settings for a repository.
@@ -81,6 +91,14 @@ func GetRepoMetadata(ctx *context.APIContext) {
Language: m.Language, Language: m.Language,
ExtensionType: m.ExtensionType, ExtensionType: m.ExtensionType,
EntryPoint: m.EntryPoint, EntryPoint: m.EntryPoint,
DeployHost: m.DeployHost,
DeployPort: m.DeployPort,
DeployUser: m.DeployUser,
DeployPath: m.DeployPath,
DockerImage: m.DockerImage,
DockerRegistry: m.DockerRegistry,
ContainerName: m.ContainerName,
HealthURL: m.HealthURL,
}) })
} }
@@ -96,34 +114,58 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Manifest" // "$ref": "#/responses/Manifest"
var req apiMetadata // Decode into a map to detect which fields were actually sent.
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil { var raw map[string]any
if err := json.NewDecoder(ctx.Req.Body).Decode(&raw); err != nil {
ctx.APIError(http.StatusBadRequest, err) ctx.APIError(http.StatusBadRequest, err)
return return
} }
m := &repo_model.RepoMetadata{ // Load existing metadata (or create defaults).
m, _ := repo_model.GetRepoMetadata(ctx, ctx.Repo.Repository.ID)
if m == nil {
m = &repo_model.RepoMetadata{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
Name: req.Name, Name: ctx.Repo.Repository.Name,
Org: req.Org, Org: ctx.Repo.Repository.OwnerName,
Description: req.Description, Description: ctx.Repo.Repository.Description,
LicenseSPDX: req.LicenseSPDX,
LicenseName: req.LicenseName,
VersionPrefix: req.VersionPrefix,
ElementName: req.ElementName,
Platform: req.Platform,
StandardsVersion: req.StandardsVersion,
StandardsSource: req.StandardsSource,
Maintainer: req.Maintainer,
MaintainerURL: req.MaintainerURL,
InfoURL: req.InfoURL,
TargetVersion: req.TargetVersion,
PHPMinimum: req.PHPMinimum,
Language: req.Language,
ExtensionType: req.ExtensionType,
EntryPoint: req.EntryPoint,
} }
}
// Apply only the fields present in the request.
setStr := func(key string, target *string) {
if v, ok := raw[key]; ok {
if s, ok := v.(string); ok {
*target = s
}
}
}
setStr("name", &m.Name)
setStr("org", &m.Org)
setStr("description", &m.Description)
setStr("license_spdx", &m.LicenseSPDX)
setStr("license_name", &m.LicenseName)
setStr("version_prefix", &m.VersionPrefix)
setStr("element_name", &m.ElementName)
setStr("platform", &m.Platform)
setStr("standards_version", &m.StandardsVersion)
setStr("standards_source", &m.StandardsSource)
setStr("maintainer", &m.Maintainer)
setStr("maintainer_url", &m.MaintainerURL)
setStr("info_url", &m.InfoURL)
setStr("target_version", &m.TargetVersion)
setStr("php_minimum", &m.PHPMinimum)
setStr("language", &m.Language)
setStr("extension_type", &m.ExtensionType)
setStr("entry_point", &m.EntryPoint)
setStr("deploy_host", &m.DeployHost)
setStr("deploy_port", &m.DeployPort)
setStr("deploy_user", &m.DeployUser)
setStr("deploy_path", &m.DeployPath)
setStr("docker_image", &m.DockerImage)
setStr("docker_registry", &m.DockerRegistry)
setStr("container_name", &m.ContainerName)
setStr("health_url", &m.HealthURL)
if err := repo_model.CreateOrUpdateRepoMetadata(ctx, m); err != nil { if err := repo_model.CreateOrUpdateRepoMetadata(ctx, m); err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
@@ -151,5 +193,13 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
Language: m.Language, Language: m.Language,
ExtensionType: m.ExtensionType, ExtensionType: m.ExtensionType,
EntryPoint: m.EntryPoint, EntryPoint: m.EntryPoint,
DeployHost: m.DeployHost,
DeployPort: m.DeployPort,
DeployUser: m.DeployUser,
DeployPath: m.DeployPath,
DockerImage: m.DockerImage,
DockerRegistry: m.DockerRegistry,
ContainerName: m.ContainerName,
HealthURL: m.HealthURL,
}) })
} }
+140 -1
View File
@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"strings"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo" repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
@@ -461,10 +462,148 @@ func ListPageRevisions(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount)) ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount))
} }
// SearchWikiPages searches wiki page titles and content.
func SearchWikiPages(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/wiki/search repository repoSearchWikiPages
// ---
// summary: Search wiki pages
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: q
// in: query
// description: search query
// type: string
// required: true
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// description: "SearchResults"
// "404":
// "$ref": "#/responses/notFound"
query := strings.TrimSpace(ctx.FormString("q"))
if query == "" {
ctx.JSON(http.StatusOK, []interface{}{})
return
}
wikiRepo, commit := findWikiRepoCommit(ctx)
if wikiRepo != nil {
defer wikiRepo.Close()
}
if ctx.Written() {
return
}
queryLower := strings.ToLower(query)
type WikiSearchResult struct {
PageName string `json:"page_name"`
PageURL string `json:"page_url"`
Context string `json:"context,omitempty"`
}
entries, err := commit.ListEntriesRecursiveFast()
if err != nil {
ctx.APIErrorInternal(err)
return
}
var results []WikiSearchResult
for _, entry := range entries {
if !entry.IsRegular() || !strings.HasSuffix(entry.Name(), ".md") {
continue
}
baseName := strings.TrimSuffix(entry.Name(), ".md")
// Extract just the filename without path for special file check
parts := strings.Split(baseName, "/")
shortName := parts[len(parts)-1]
if shortName == "_Sidebar" || shortName == "_Footer" {
continue
}
blob := entry.Blob()
content, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
if err != nil {
continue
}
titleMatch := strings.Contains(strings.ToLower(baseName), queryLower)
contentMatch := strings.Contains(strings.ToLower(content), queryLower)
if !titleMatch && !contentMatch {
continue
}
contextLine := ""
if contentMatch {
for _, line := range strings.Split(content, "\n") {
if strings.Contains(strings.ToLower(line), queryLower) {
contextLine = strings.TrimSpace(line)
if len(contextLine) > 200 {
contextLine = contextLine[:200] + "..."
}
break
}
}
}
wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
if err != nil {
continue
}
_, displayName := wiki_service.WebPathToUserTitle(wikiName)
results = append(results, WikiSearchResult{
PageName: displayName,
PageURL: string(wikiName),
Context: contextLine,
})
}
// Pagination
page := max(ctx.FormInt("page"), 1)
limit := ctx.FormInt("limit")
if limit <= 0 {
limit = setting.API.DefaultPagingNum
}
total := len(results)
start := (page - 1) * limit
end := start + limit
if start > total {
start = total
}
if end > total {
end = total
}
ctx.SetLinkHeader(int64(total), limit)
ctx.SetTotalCountHeader(int64(total))
ctx.JSON(http.StatusOK, results[start:end])
}
// findEntryForFile finds the tree entry for a target filepath. // findEntryForFile finds the tree entry for a target filepath.
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) { func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
entry, err := commit.GetTreeEntryByPath(target) entry, err := commit.GetTreeEntryByPath(target)
if err != nil { if err != nil && !git.IsErrNotExist(err) {
return nil, err return nil, err
} }
if entry != nil { if entry != nil {
+5
View File
@@ -103,6 +103,11 @@ func SettingsIssueStatusesDeletePost(ctx *context.Context) {
} }
if err := issues_model.DeleteIssueStatusDef(ctx, id); err != nil { if err := issues_model.DeleteIssueStatusDef(ctx, id); err != nil {
if issues_model.IsErrStatusRequired(err) {
ctx.Flash.Error("Cannot delete required status: " + def.Name)
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
return
}
ctx.ServerError("DeleteIssueStatusDef", err) ctx.ServerError("DeleteIssueStatusDef", err)
return return
} }
+73 -25
View File
@@ -29,6 +29,14 @@ type OrgWikiPage struct {
SubURL string SubURL string
} }
// OrgWikiTreeNode represents a node in the org wiki folder tree for sidebar navigation.
type OrgWikiTreeNode struct {
Name string
SubURL string
IsDir bool
Children []*OrgWikiTreeNode
}
// Wiki renders the org wiki tab. // Wiki renders the org wiki tab.
func Wiki(ctx *context.Context) { func Wiki(ctx *context.Context) {
org := ctx.Org.Organization org := ctx.Org.Organization
@@ -71,31 +79,9 @@ func Wiki(ctx *context.Context) {
} }
ctx.Data["WikiRepoLink"] = wikiRepo.Link() ctx.Data["WikiRepoLink"] = wikiRepo.Link()
// Build page list from repo root. // Build folder tree for sidebar navigation.
entries, err := commit.ListEntries() wikiTree := buildOrgWikiTree(commit)
if err != nil { ctx.Data["WikiTree"] = wikiTree
ctx.ServerError("ListEntries", err)
return
}
pages := make([]OrgWikiPage, 0, len(entries))
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
name := entry.Name()
if !isMarkdownFile(name) {
continue
}
displayName := strings.TrimSuffix(name, path.Ext(name))
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
continue
}
pages = append(pages, OrgWikiPage{
Name: displayName,
SubURL: displayName,
})
}
ctx.Data["Pages"] = pages
// Determine which page to render. // Determine which page to render.
pageName := ctx.PathParamRaw("*") pageName := ctx.PathParamRaw("*")
@@ -157,6 +143,68 @@ func Wiki(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplOrgWiki) ctx.HTML(http.StatusOK, tplOrgWiki)
} }
// buildOrgWikiTree builds a hierarchical folder tree from the org wiki git repo.
// Shows up to 2 levels deep (folders and their immediate children).
func buildOrgWikiTree(commit *git.Commit) []*OrgWikiTreeNode {
if commit == nil {
return nil
}
entries, err := commit.ListEntries()
if err != nil {
return nil
}
var topLevel []*OrgWikiTreeNode
for _, entry := range entries {
name := entry.Name()
if entry.IsDir() {
node := &OrgWikiTreeNode{
Name: name,
SubURL: name,
IsDir: true,
}
// List children of this directory (1 level deep).
subTree := entry.Tree()
if subTree != nil {
children, _ := subTree.ListEntries()
for _, child := range children {
childName := child.Name()
if child.IsDir() {
node.Children = append(node.Children, &OrgWikiTreeNode{
Name: childName,
SubURL: name + "/" + childName,
IsDir: true,
})
} else if isMarkdownFile(childName) {
displayName := strings.TrimSuffix(childName, path.Ext(childName))
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
continue
}
node.Children = append(node.Children, &OrgWikiTreeNode{
Name: displayName,
SubURL: name + "/" + displayName,
IsDir: false,
})
}
}
}
topLevel = append(topLevel, node)
} else if isMarkdownFile(name) {
displayName := strings.TrimSuffix(name, path.Ext(name))
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
continue
}
topLevel = append(topLevel, &OrgWikiTreeNode{
Name: displayName,
SubURL: displayName,
IsDir: false,
})
}
}
return topLevel
}
// findOrgWikiCommit locates the profile repo's wiki and returns its HEAD commit. // findOrgWikiCommit locates the profile repo's wiki and returns its HEAD commit.
// The org wiki lives in the .wiki.git sidecar of the profile repo (e.g. .mokogitea.wiki.git). // The org wiki lives in the .wiki.git sidecar of the profile repo (e.g. .mokogitea.wiki.git).
// Tries fallback repo names (.profile, .github) if the primary doesn't exist. // Tries fallback repo names (.profile, .github) if the primary doesn't exist.
+8
View File
@@ -123,6 +123,14 @@ func saveMetadata(ctx *context.Context) {
manifest.Maintainer = existing.Maintainer manifest.Maintainer = existing.Maintainer
manifest.MaintainerURL = existing.MaintainerURL manifest.MaintainerURL = existing.MaintainerURL
manifest.Language = existing.Language manifest.Language = existing.Language
manifest.DeployHost = existing.DeployHost
manifest.DeployPort = existing.DeployPort
manifest.DeployUser = existing.DeployUser
manifest.DeployPath = existing.DeployPath
manifest.DockerImage = existing.DockerImage
manifest.DockerRegistry = existing.DockerRegistry
manifest.ContainerName = existing.ContainerName
manifest.HealthURL = existing.HealthURL
} }
if err := repo_model.CreateOrUpdateRepoMetadata(ctx, manifest); err != nil { if err := repo_model.CreateOrUpdateRepoMetadata(ctx, manifest); err != nil {
+1096 -51
View File
File diff suppressed because it is too large Load Diff
@@ -28,6 +28,7 @@
</td> </td>
<td> <td>
<strong>{{.Name}}</strong> <strong>{{.Name}}</strong>
{{if .IsRequired}}<span class="ui mini blue label" title="Required status - cannot be deleted">{{svg "octicon-lock" 10}} required</span>{{end}}
{{if not .IsActive}}<span class="ui mini grey label">{{ctx.Locale.Tr "org.settings.issue_status_inactive"}}</span>{{end}} {{if not .IsActive}}<span class="ui mini grey label">{{ctx.Locale.Tr "org.settings.issue_status_inactive"}}</span>{{end}}
{{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}} {{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}}
</td> </td>
@@ -40,10 +41,14 @@
</td> </td>
<td>{{.SortOrder}}</td> <td>{{.SortOrder}}</td>
<td class="tw-text-right"> <td class="tw-text-right">
{{if .IsRequired}}
<span class="ui tiny icon button disabled" title="Required - cannot be deleted">{{svg "octicon-lock" 14}}</span>
{{else}}
<form method="post" action="{{$.OrgLink}}/settings/issue-statuses/{{.ID}}/delete" class="tw-inline"> <form method="post" action="{{$.OrgLink}}/settings/issue-statuses/{{.ID}}/delete" class="tw-inline">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<button class="ui tiny red icon button" type="submit" title="{{ctx.Locale.Tr "remove"}}">{{svg "octicon-trash" 14}}</button> <button class="ui tiny red icon button" type="submit" title="{{ctx.Locale.Tr "remove"}}">{{svg "octicon-trash" 14}}</button>
</form> </form>
{{end}}
</td> </td>
</tr> </tr>
{{end}} {{end}}
+34 -9
View File
@@ -11,7 +11,7 @@
This organization doesn't have a wiki yet. This organization doesn't have a wiki yet.
</div> </div>
<p class="tw-text-center"> <p class="tw-text-center">
Enable the wiki on the <code>.profile</code> (public) or <code>.profile-private</code> (members-only) Enable the wiki on the <code>.mokogitea</code> (public) or <code>.mokogitea-private</code> (members-only)
repository to get started. repository to get started.
</p> </p>
</div> </div>
@@ -47,34 +47,59 @@
<p>The page "{{.CurrentPage}}" does not exist in this wiki.</p> <p>The page "{{.CurrentPage}}" does not exist in this wiki.</p>
</div> </div>
</div> </div>
{{if .Pages}} {{if .WikiTree}}
<h4>Available pages:</h4> <h4>Available pages:</h4>
<ul> <ul>
{{range .Pages}} {{range .WikiTree}}
{{if .IsDir}}
{{range .Children}}
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li> <li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
{{end}} {{end}}
{{else}}
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
{{end}}
{{end}}
</ul> </ul>
{{end}} {{end}}
</div> </div>
{{else}} {{else}}
<div class="wiki-content-parts"> <div class="wiki-content-parts">
<div class="render-content markup wiki-content-main {{if or .WikiSidebarHTML .Pages}}with-sidebar{{end}}"> <div class="render-content markup wiki-content-main {{if or .WikiSidebarHTML .WikiTree}}with-sidebar{{end}}">
{{.WikiContent}} {{.WikiContent}}
</div> </div>
{{if or .WikiSidebarHTML .Pages}} {{if or .WikiSidebarHTML .WikiTree}}
<div class="render-content markup wiki-content-sidebar"> <div class="render-content markup wiki-content-sidebar">
{{if .WikiSidebarHTML}} {{if .WikiSidebarHTML}}
{{.WikiSidebarHTML}} {{.WikiSidebarHTML}}
<div class="ui divider"></div> {{else if .WikiTree}}
{{end}}
{{if .Pages}}
<strong>{{svg "octicon-list-unordered" 14}} Pages</strong> <strong>{{svg "octicon-list-unordered" 14}} Pages</strong>
<ul class="wiki-tree-list"> <ul class="wiki-tree-list">
{{range .Pages}} {{range .WikiTree}}
<li> <li>
{{if .IsDir}}
<details open>
<summary>{{svg "octicon-file-directory" 14}} <strong>{{.Name}}</strong></summary>
{{if .Children}}
<ul>
{{range .Children}}
<li>
{{if .IsDir}}
{{svg "octicon-file-directory" 14}}
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
{{else}}
{{svg "octicon-file" 14}} {{svg "octicon-file" 14}}
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a> <a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
{{end}}
</li>
{{end}}
</ul>
{{end}}
</details>
{{else}}
{{svg "octicon-file" 14}}
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
{{end}}
</li> </li>
{{end}} {{end}}
</ul> </ul>
+1 -5
View File
@@ -21,11 +21,7 @@
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization"> <input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
</div> </div>
</div> </div>
<div class="four fields"> <div class="three fields">
<div class="field">
<label>Version</label>
<input name="version" value="{{.Manifest.Version}}" placeholder="e.g. 06.00.00">
</div>
<div class="field"> <div class="field">
<label>Version Prefix</label> <label>Version Prefix</label>
<input name="version_prefix" value="{{.Manifest.VersionPrefix}}" placeholder="e.g. v1.26.1-moko."> <input name="version_prefix" value="{{.Manifest.VersionPrefix}}" placeholder="e.g. v1.26.1-moko.">
+42
View File
@@ -0,0 +1,42 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
{{template "repo/header" .}}
<div class="ui container">
<div class="repo-button-row">
<div class="tw-flex tw-items-center tw-gap-2">
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}">
{{svg "octicon-arrow-left" 14}} Back to {{.title}}
</a>
</div>
</div>
<h2>{{svg "octicon-cross-reference" 20}} What links here: {{.title}}</h2>
{{if .Backlinks}}
<div class="ui relaxed divided list">
{{range .Backlinks}}
<div class="item">
<div class="content">
<a class="header" href="{{$.RepoLink}}/wiki/{{.PageURL}}">{{.PageName}}</a>
{{if .Context}}
<div class="description">
<code class="tw-text-sm">{{.Context}}</code>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
<p class="tw-mt-4 text grey">{{.BacklinkCount}} {{if eq .BacklinkCount 1}}page{{else}}pages{{end}} linking here.</p>
{{else}}
<div class="ui placeholder segment">
<div class="ui icon header">
{{svg "octicon-unlink" 48}}
<br>
No pages link to "{{.title}}"
</div>
</div>
{{end}}
</div>
</div>
{{template "base/footer" .}}
+36
View File
@@ -0,0 +1,36 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
{{template "repo/header" .}}
<div class="ui container">
<div class="repo-button-row tw-flex tw-items-center tw-gap-2 tw-mb-4">
<div class="tw-flex-1">
<a class="ui small button" href="{{.RepoLink}}/wiki/">
{{svg "octicon-arrow-left" 14}} Back to wiki
</a>
</div>
</div>
<h2>{{svg "octicon-tag" 20}} Category: {{.CategoryName}}</h2>
{{if .CategoryPages}}
<div class="ui relaxed divided list">
{{range .CategoryPages}}
<div class="item">
{{svg "octicon-file" 14}}
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</a>
</div>
{{end}}
</div>
<p class="tw-mt-4 text grey">{{.CategoryCount}} {{if eq .CategoryCount 1}}page{{else}}pages{{end}} in this category.</p>
{{else}}
<div class="ui placeholder segment">
<div class="ui icon header">
{{svg "octicon-tag" 48}}
<br>
No pages in category "{{.CategoryName}}"
</div>
</div>
{{end}}
</div>
</div>
{{template "base/footer" .}}
+51
View File
@@ -0,0 +1,51 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
{{template "repo/header" .}}
<div class="ui container">
<div class="repo-button-row tw-flex tw-items-center tw-gap-2 tw-mb-4">
<div class="tw-flex-1">
<a href="{{.RepoLink}}/wiki/{{.PageURL}}">{{svg "octicon-arrow-left" 14}} {{.title}}</a>
&middot;
<a href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision">Revision history</a>
</div>
</div>
<div class="ui segment">
<h3>{{svg "octicon-diff" 20}} Changes in <code>{{.CommitID}}</code></h3>
<p>
<strong>{{.CommitAuthor}}</strong> &mdash; {{.CommitMessage}}
<br>
<small class="text grey">{{DateUtils.TimeSince .CommitWhen}}</small>
</p>
{{if .IsNewPage}}
<div class="ui info message">New page created</div>
{{else if .IsDeletedPage}}
<div class="ui warning message">Page deleted</div>
{{else if not .HasDiff}}
<div class="ui info message">No content changes in this revision</div>
{{end}}
{{if .HasDiff}}
<div class="diff-file-box" style="overflow-x: auto;">
<table class="chroma" style="width: 100%; border-collapse: collapse; font-family: monospace; font-size: 13px;">
{{range .DiffLines}}
<tr class="{{if eq .Type "add"}}diff-line-add{{else if eq .Type "del"}}diff-line-del{{else}}diff-line-context{{end}}">
<td style="width: 40px; text-align: right; padding: 0 8px; color: #999; user-select: none; {{if eq .Type "add"}}background: #e6ffec;{{else if eq .Type "del"}}background: #ffebe9;{{else}}background: #f6f8fa;{{end}}">
{{if .OldNum}}{{.OldNum}}{{end}}
</td>
<td style="width: 40px; text-align: right; padding: 0 8px; color: #999; user-select: none; {{if eq .Type "add"}}background: #e6ffec;{{else if eq .Type "del"}}background: #ffebe9;{{else}}background: #f6f8fa;{{end}}">
{{if .NewNum}}{{.NewNum}}{{end}}
</td>
<td style="padding: 0 8px; white-space: pre-wrap; word-break: break-all; {{if eq .Type "add"}}background: #e6ffec;{{else if eq .Type "del"}}background: #ffebe9;{{else}}background: #fff;{{end}}">
{{if eq .Type "add"}}+{{else if eq .Type "del"}}-{{else}} {{end}} {{.Content}}
</td>
</tr>
{{end}}
</table>
</div>
{{end}}
</div>
</div>
</div>
{{template "base/footer" .}}
+40
View File
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{.Title}}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
color: #333;
line-height: 1.6;
}
h1 { font-size: 2em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
code { background: #f6f8fa; padding: 2px 6px; border-radius: 3px; font-size: 85%; }
pre { background: #f6f8fa; padding: 16px; border-radius: 6px; overflow-x: auto; }
pre code { background: none; padding: 0; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #f6f8fa; }
img { max-width: 100%; }
blockquote { border-left: 4px solid #ddd; margin: 0; padding: 0 16px; color: #666; }
a { color: #0366d6; }
@media print {
body { padding: 0; }
a { color: inherit; text-decoration: none; }
}
</style>
</head>
<body>
<h1>{{.Title}}</h1>
{{.WikiContentHTML}}
<hr>
<p style="font-size: 12px; color: #999;">
Printed from wiki &middot; {{.Title}}
</p>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
{{template "repo/header" .}}
<div class="ui container">
<div class="repo-button-row tw-flex tw-items-center tw-gap-2 tw-mb-4">
<div class="tw-flex-1">
<h2>{{svg "octicon-history" 20}} Recent changes</h2>
</div>
<a class="ui small button" href="{{.RepoLink}}/wiki/">
{{svg "octicon-arrow-left" 14}} Back to wiki
</a>
</div>
{{if .RecentChanges}}
<table class="ui compact table">
<thead>
<tr>
<th>Page</th>
<th>Author</th>
<th>Edit summary</th>
<th>When</th>
<th>Commit</th>
</tr>
</thead>
<tbody>
{{range .RecentChanges}}
<tr>
<td>
{{if .PageURL}}
{{svg "octicon-file" 14}}
<a href="{{$.RepoLink}}/wiki/{{.PageURL}}">{{.PageName}}</a>
{{else if .PageName}}
{{svg "octicon-file" 14}} {{.PageName}}
{{else}}
<span class="text grey">—</span>
{{end}}
</td>
<td>{{.Author}}</td>
<td class="gt-ellipsis" style="max-width: 400px;">{{.Message}}</td>
<td>{{DateUtils.TimeSince .When}}</td>
<td><code class="tw-text-xs">{{.SHA}}</code></td>
</tr>
{{end}}
</tbody>
</table>
<div class="tw-flex tw-justify-between tw-mt-4">
{{if .HasPrevPage}}
<a class="ui small button" href="{{.RepoLink}}/wiki/?action=_recent&page={{Eval .CurrentPage "-" 1}}">
{{svg "octicon-chevron-left" 14}} Newer
</a>
{{else}}
<span></span>
{{end}}
{{if .HasNextPage}}
<a class="ui small button" href="{{.RepoLink}}/wiki/?action=_recent&page={{Eval .CurrentPage "+" 1}}">
Older {{svg "octicon-chevron-right" 14}}
</a>
{{end}}
</div>
{{else}}
<div class="ui placeholder segment">
<div class="ui icon header">
{{svg "octicon-history" 48}}
<br>
No recent changes
</div>
</div>
{{end}}
</div>
</div>
{{template "base/footer" .}}
+52
View File
@@ -0,0 +1,52 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki">
{{template "repo/header" .}}
<div class="ui container">
<div class="repo-button-row">
<div class="tw-flex tw-items-center tw-gap-2">
<a class="ui small button" href="{{.RepoLink}}/wiki/">
{{svg "octicon-arrow-left" 14}} Back to wiki
</a>
</div>
</div>
<h2>{{svg "octicon-search" 20}} Wiki search</h2>
<form class="ui form tw-mb-4" action="{{.RepoLink}}/wiki/" method="get">
<input type="hidden" name="action" value="_search">
<div class="ui action input tw-w-full">
<input type="text" name="q" value="{{.Query}}" placeholder="Search wiki pages..." autofocus>
<button class="ui primary button" type="submit">{{svg "octicon-search" 14}} Search</button>
</div>
</form>
{{if .Query}}
{{if .Results}}
<div class="ui relaxed divided list">
{{range .Results}}
<div class="item">
<div class="content">
<a class="header" href="{{$.RepoLink}}/wiki/{{.PageURL}}">{{.PageName}}</a>
{{if .Context}}
<div class="description">
<code class="tw-text-sm">{{.Context}}</code>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
<p class="tw-mt-4 text grey">{{.ResultCount}} {{if eq .ResultCount 1}}result{{else}}results{{end}} for "{{.Query}}"</p>
{{else}}
<div class="ui placeholder segment">
<div class="ui icon header">
{{svg "octicon-search" 48}}
<br>
No results for "{{.Query}}"
</div>
</div>
{{end}}
{{end}}
</div>
</div>
{{template "base/footer" .}}
+32 -1
View File
@@ -20,6 +20,10 @@
</div> </div>
<div class="scrolling menu"> <div class="scrolling menu">
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_pages">{{ctx.Locale.Tr "repo.wiki.pages"}}</a> <a class="item muted" href="{{.RepoLink}}/wiki/?action=_pages">{{ctx.Locale.Tr "repo.wiki.pages"}}</a>
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_search">{{svg "octicon-search" 14}} Search wiki</a>
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_recent">{{svg "octicon-history" 14}} Recent changes</a>
t <a class="item muted" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_print" target="_blank">{{svg "octicon-browser" 14}} Print view</a>
<a class="item muted" href="{{.RepoLink}}/wiki/?action=_export&format=zip">{{svg "octicon-download" 14}} Export wiki (ZIP)</a>
<div class="divider"></div> <div class="divider"></div>
{{range .Pages}} {{range .Pages}}
<a class="item {{if eq $.Title .Name}}selected{{end}}" href="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</a> <a class="item {{if eq $.Title .Name}}selected{{end}}" href="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</a>
@@ -34,6 +38,8 @@
<div class="flex-text-block tw-flex-wrap tw-justify-end"> <div class="flex-text-block tw-flex-wrap tw-justify-end">
<div class="flex-text-block tw-flex-1 tw-min-w-[300px]"> <div class="flex-text-block tw-flex-1 tw-min-w-[300px]">
<a class="ui basic button tw-px-3 tw-gap-3" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" >{{if .CommitCount}}<span>{{.CommitCount}}</span> {{end}}{{svg "octicon-history"}}</a> <a class="ui basic button tw-px-3 tw-gap-3" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" >{{if .CommitCount}}<span>{{.CommitCount}}</span> {{end}}{{svg "octicon-history"}}</a>
<a class="ui basic button tw-px-3" title="What links here" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_backlinks">{{svg "octicon-cross-reference"}}</a>
{{if .LastCommitID}}<a class="ui basic button tw-px-3" title="View last change" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_diff&commit={{.LastCommitID}}">{{svg "octicon-diff"}}</a>{{end}}
<div class="tw-flex-1 gt-ellipsis"> <div class="tw-flex-1 gt-ellipsis">
{{$title}} {{$title}}
<div class="ui sub header gt-ellipsis"> <div class="ui sub header gt-ellipsis">
@@ -47,7 +53,7 @@
<a class="ui small button unescape-button tw-hidden" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a> <a class="ui small button unescape-button tw-hidden" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
<a class="ui small button escape-button" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a> <a class="ui small button escape-button" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
{{end}} {{end}}
{{if and .CanWriteWiki (not .Repository.IsMirror)}} {{if and .CanWriteWiki (not .Repository.IsMirror) (not .WikiFolderProtected)}}
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_edit">{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}</a> <a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_edit">{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}</a>
<a class="ui small primary button" href="{{.RepoLink}}/wiki?action=_new">{{ctx.Locale.Tr "repo.wiki.new_page_button"}}</a> <a class="ui small primary button" href="{{.RepoLink}}/wiki?action=_new">{{ctx.Locale.Tr "repo.wiki.new_page_button"}}</a>
<a class="ui small red button link-action" href data-modal-confirm="#repo-wiki-delete-page-modal" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a> <a class="ui small red button link-action" href data-modal-confirm="#repo-wiki-delete-page-modal" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
@@ -69,6 +75,12 @@
{{end}} {{end}}
{{end}} {{end}}
{{if .WikiFolderProtected}}
<div class="ui warning message">
<p>{{svg "octicon-lock" 14}} This page is in a protected folder. Only users with the required role can edit it.</p>
</div>
{{end}}
{{if .FormatWarning}} {{if .FormatWarning}}
<div class="ui negative message"> <div class="ui negative message">
<p>{{.FormatWarning}}</p> <p>{{.FormatWarning}}</p>
@@ -103,13 +115,30 @@
<div class="wiki-content-parts"> <div class="wiki-content-parts">
{{if .WikiSidebarTocHTML}} {{if .WikiSidebarTocHTML}}
<div class="render-content markup wiki-content-sidebar wiki-content-toc"> <div class="render-content markup wiki-content-sidebar wiki-content-toc">
<details open>
<summary><strong>{{svg "octicon-list-unordered" 14}} Contents</strong></summary>
{{.WikiSidebarTocHTML}} {{.WikiSidebarTocHTML}}
</details>
</div> </div>
{{end}} {{end}}
<div class="render-content markup wiki-content-main {{if or .WikiSidebarTocHTML .WikiSidebarHTML .WikiTree}}with-sidebar{{end}}"> <div class="render-content markup wiki-content-main {{if or .WikiSidebarTocHTML .WikiSidebarHTML .WikiTree}}with-sidebar{{end}}">
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus}} {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus}}
{{if .WikiInlineTocHTML}}
<details open class="wiki-toc-inline tw-mb-4">
<summary><strong>{{svg "octicon-list-unordered" 14}} Contents</strong></summary>
{{.WikiInlineTocHTML}}
</details>
{{end}}
{{.WikiContentHTML}} {{.WikiContentHTML}}
{{if .WikiCategories}}
<div class="tw-mt-4 tw-pt-2" style="border-top: 1px solid var(--color-secondary);">
{{svg "octicon-tag" 14}} Categories:
{{range .WikiCategories}}
<a class="ui small label" href="{{$.RepoLink}}/wiki/?action=_category&name={{.}}">{{.}}</a>
{{end}}
</div>
{{end}}
</div> </div>
{{if .WikiTree}} {{if .WikiTree}}
@@ -121,6 +150,7 @@
{{if .IsDir}} {{if .IsDir}}
{{svg "octicon-file-directory" 14}} {{svg "octicon-file-directory" 14}}
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a> <a href="{{$.RepoLink}}/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
{{if .Protected}}{{svg "octicon-lock" 12}}{{end}}
{{if .Children}} {{if .Children}}
<ul> <ul>
{{range .Children}} {{range .Children}}
@@ -128,6 +158,7 @@
{{if .IsDir}} {{if .IsDir}}
{{svg "octicon-file-directory" 14}} {{svg "octicon-file-directory" 14}}
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a> <a href="{{$.RepoLink}}/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
{{if .Protected}}{{svg "octicon-lock" 12}}{{end}}
{{else}} {{else}}
{{svg "octicon-file" 14}} {{svg "octicon-file" 14}}
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}" {{if eq $.PageURL .SubURL}}class="active"{{end}}>{{.Name}}</a> <a href="{{$.RepoLink}}/wiki/{{.SubURL}}" {{if eq $.PageURL .SubURL}}class="active"{{end}}>{{.Name}}</a>
+31
View File
@@ -86,3 +86,34 @@
max-width: unset; max-width: unset;
} }
} }
/* Wikilinks: red links for non-existent pages */
.wiki .wiki-link-new {
color: var(--color-red);
}
.wiki .wiki-link-new:hover {
color: var(--color-red);
text-decoration: underline;
}
/* Wiki inline ToC */
.wiki .wiki-toc-inline {
border: 1px solid var(--color-secondary);
border-radius: 4px;
padding: 8px 16px;
background: var(--color-box-body);
}
.wiki .wiki-toc-inline summary {
cursor: pointer;
user-select: none;
}
/* Sticky sidebar ToC */
.wiki .wiki-content-toc {
position: sticky;
top: 16px;
max-height: calc(100vh - 100px);
overflow-y: auto;
}