@mokoenterprises/mokossh-mcp 3.8.1 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,16 +2,16 @@
2
2
  blank_issues_enabled: true
3
3
  contact_links:
4
4
  - name: 💼 Enterprise Support
5
- url: https://mokoconsulting.tech/enterprise
5
+ url: https://mokoenterprises.us/enterprise
6
6
  about: Enterprise-level support and consultation services
7
7
  - name: 💬 Ask a Question
8
- url: https://mokoconsulting.tech/
8
+ url: https://mokoenterprises.us/
9
9
  about: Get help or ask questions through our website
10
10
  - name: 📚 MokoCLI Documentation
11
- url: https://git.mokoconsulting.tech/MokoConsulting/mokocli
11
+ url: https://git.mokoenterprises.us/MokoEnterprises/mokocli
12
12
  about: View our coding standards and best practices
13
13
  - name: 🔒 Report a Security Vulnerability
14
- url: https://git.mokoconsulting.tech/MokoConsulting/.github-private/security/advisories/new
14
+ url: https://git.mokoenterprises.us/MokoEnterprises/.github-private/security/advisories/new
15
15
  about: Report security vulnerabilities privately (for critical issues)
16
16
  - name: 💡 Community Discussions
17
17
  url: https://github.com/orgs/mokoconsulting-tech/discussions
@@ -37,7 +37,7 @@ If you have ideas about how this could be implemented, share them here:
37
37
  Add any other context, mockups, or screenshots about the feature request here.
38
38
 
39
39
  ## Relevant Standards
40
- Does this relate to any standards in [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli)?
40
+ Does this relate to any standards in [MokoCLI](https://git.mokoenterprises.us/MokoEnterprises/mokocli)?
41
41
  - [ ] Accessibility (WCAG 2.1 AA)
42
42
  - [ ] Localization (en_US/en_GB)
43
43
  - [ ] Security best practices
@@ -35,7 +35,7 @@ Use this template only for:
35
35
  <!-- Describe how this could be addressed -->
36
36
 
37
37
  ## Standards Reference
38
- Does this relate to security standards in [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli)?
38
+ Does this relate to security standards in [MokoCLI](https://git.mokoenterprises.us/MokoEnterprises/mokocli)?
39
39
  - [ ] SPDX license identifiers
40
40
  - [ ] Secret management
41
41
  - [ ] Dependency security
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
1
+ # Copyright (C) 2026 Moko Enterprises <hello@mokoenterprises.us>
2
2
  # SPDX-License-Identifier: GPL-3.0-or-later
3
3
 
4
4
  name: "Resolve source dir"
@@ -1,10 +1,10 @@
1
- # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
1
+ # Copyright (C) 2026 Moko Enterprises <hello@mokoenterprises.us>
2
2
  # SPDX-License-Identifier: GPL-3.0-or-later
3
3
  #
4
4
  # FILE INFORMATION
5
5
  # DEFGROUP: MokoGIT.Workflow
6
6
  # INGROUP: MokoCLI.Universal
7
- # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
7
+ # REPO: https://git.mokoenterprises.us/MokoEnterprises/Template-Generic
8
8
  # PATH: /.mokogit/workflows/apidocs.yml
9
9
  #
10
10
  # apidocs.yml — UNIVERSAL API-docs → wiki (synced to every repo via the universal
@@ -62,9 +62,9 @@ jobs:
62
62
  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 || true
63
63
  fi
64
64
  rm -rf /tmp/mokocli
65
- REG='{"type":"composer","url":"https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"}'
65
+ REG='{"type":"composer","url":"https://git.mokoenterprises.us/api/packages/MokoEnterprises/composer"}'
66
66
  if composer create-project --no-install --no-interaction --no-progress --quiet \
67
- --repository="${REG}" mokoconsulting/mokocli /tmp/mokocli >/dev/null 2>&1 \
67
+ --repository="${REG}" mokoenterprises/mokocli /tmp/mokocli >/dev/null 2>&1 \
68
68
  && [ -f /tmp/mokocli/cli/docs_php_reference.php ]; then
69
69
  echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
70
70
  echo "Installed mokocli fresh from the Composer registry."
@@ -128,12 +128,12 @@ jobs:
128
128
  if [ -z "$TOKEN" ]; then echo "::warning::no MOKOGIT_TOKEN — skipping wiki publish"; exit 0; fi
129
129
  REPO="${{ github.event.repository.name }}"; OWNER="${{ github.repository_owner }}"
130
130
  rm -rf /tmp/wiki
131
- if ! git clone -q "https://x-access-token:${TOKEN}@git.mokoconsulting.tech/${OWNER}/${REPO}.wiki.git" /tmp/wiki 2>/dev/null; then
131
+ if ! git clone -q "https://x-access-token:${TOKEN}@git.mokoenterprises.us/${OWNER}/${REPO}.wiki.git" /tmp/wiki 2>/dev/null; then
132
132
  echo "::warning::wiki clone failed (wiki may not be initialised) — skipping"; exit 0
133
133
  fi
134
134
  cp "$FILE" "/tmp/wiki/${PAGE}.md"
135
135
  cd /tmp/wiki
136
- git config user.name "Moko Consulting"; git config user.email "hello@mokoconsulting.tech"
136
+ git config user.name "Moko Enterprises"; git config user.email "hello@mokoenterprises.us"
137
137
  git add -A
138
138
  if git diff --cached --quiet; then echo "wiki already up to date"; exit 0; fi
139
139
  git commit -q -m "docs(wiki): update ${PAGE} (moko ${{ steps.gen.outputs.kind }} docs) [skip ci]"
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
1
+ # Copyright (C) 2026 Moko Enterprises <hello@mokoenterprises.us>
2
2
  # SPDX-License-Identifier: GPL-3.0-or-later
3
3
  #
4
4
  # FILE INFORMATION
@@ -1,6 +1,6 @@
1
- # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
1
+ # Copyright (C) 2026 Moko Enterprises <hello@mokoenterprises.us>
2
2
  #
3
- # This file is part of a Moko Consulting project.
3
+ # This file is part of a Moko Enterprises project.
4
4
  #
5
5
  # SPDX-License-Identifier: GPL-3.0-or-later
6
6
  #
@@ -1,14 +1,14 @@
1
- # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
1
+ # Copyright (C) 2026 Moko Enterprises <hello@mokoenterprises.us>
2
2
  #
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
  #
5
5
  # FILE INFORMATION
6
6
  # DEFGROUP: MokoGIT.Workflow
7
7
  # INGROUP: MokoCLI.Release
8
- # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
8
+ # REPO: https://git.mokoenterprises.us/MokoEnterprises/Template-Generic
9
9
  # PATH: /.mokogit/workflows/auto-release.yml
10
- # VERSION: 07.24.00
11
- # BRIEF: NEW (07.24.00): npmjs.org publish now FAILS LOUD (::error:: + exit $rc) on an AUTHORIZATION error (404 "not in this registry" / 401 / 403 / ENEEDAUTH / EOTP) instead of swallowing EVERY failure as a non-failing ::warning:: — a missing-scope / unauthorized / revoked / expired NPM_TOKEN previously hid behind a green release fleet-wide (the "secondary registry, not failing the run" catch masked a 404 PUT, which is exactly how npm reports an unauthorized scoped write; CI had in fact NEVER successfully published any MCP package to npmjs while every run reported success). Transient/5xx/network failures still stay a non-failing ::warning:: (npmjs is the SECONDARY mirror; the MokoGIT registry is primary and already hard-fails), and an unset NPM_TOKEN still skips cleanly — so repos that do not publish to npmjs are 100% unaffected. PREVIOUS (07.23.00): "Vendor PHP dependencies (build-time)" added to BOTH publish legs (promote-rc + stable) — this workflow had NO build-time composer vendoring at all, so any component declaring runtime PHP deps via a sub-package composer.json (vendor-dir admin/vendor, e.g. mPDF in MokonyxCRM/MokonyxCRMDCM) shipped its rc/stable zip WITHOUT admin/vendor while the component manifest lists <folder>vendor</folder> — JInstaller then aborts the ENTIRE package install on-site ("File does not exist [ROOT][TMP]/.../admin/vendor" → "Failed to copy administrator files"). The step mirrors pre-release.yml 07.15.00: no-op without a sub-package composer.json; provisions ext-gd when missing (the release runners' php-cli lacks it, which failed mpdf dependency RESOLUTION outright); verifies the CONFIGURED vendor-dir (composer config vendor-dir) materialized autoload.php; FAILS the build loud when vendoring fails for a package whose manifest ships <folder>vendor</folder> (a green build with an install-bricking artifact is strictly worse than a red build); fail-soft warning for packages whose manifests do not reference vendor/. Runs before `moko publish` (release_publish.php → release_package.php zips the plain working tree; vendor/ was never excluded — the tree simply never had it in CI). PREVIOUS (07.22.00): #498 — the #469 SRC_DIR gate no longer self-traps, and the fast-merge guard is MINOR-identity aware (fixes stable version never advancing; MokoCLI stuck re-publishing v10.20.00). (1) The gate read SRC_DIR with grep|sed, capturing the LITERAL unexpanded `$(shell …)` from the fleet-standard `SRC_DIR := $(shell [ -d src ] && echo src || echo source)` — an unmatchable directory that forced should_bump=false on EVERY push even when source changed. It now resolves SRC_DIR via `make --eval` expansion, falls back to the grep|sed read only if make is unavailable, nulls out a still-unexpanded `$(...)`/backtick value, and treats a declared-but-NONEXISTENT SRC_DIR (repos with code in cli/lib/bin, not src/source) as the non-source denylist fallback instead of an unmatchable INCLUSION prefix. (2) The "Guarantee monotonic release version (fast-merge bump guard)" compared the FULL version, so dev's per-merge PATCH bumps (10.20.00→10.20.04) fooled it into "normal path, no heal" and it re-published the already-released vMAJOR.MINOR.00. It now compares the MINOR identity (MAJOR.MINOR), so a fast dev→stable merge with no promote-rc cuts a new MINOR. Fleet-wide release-semantics change — validate on one repo before syncing. PREVIOUS (07.21.00): #469 — gate the version BUMP on whether the push touched the Makefile SRC_DIR. The former non-code paths-ignore denylist on the push trigger is REMOVED: a docs/workflow/sample push no longer skips the pipeline, it now RUNS in no-bump "update the existing stable release in place" mode (re-stamps the UNCHANGED current version via the existing `moko publish --bump none --no-commit`, refreshes notes/assets, no version advance, no new vNN tag). A new early step "Resolve SRC_DIR bump gate (#469)" (id: srcgate) decides should_bump: (1) ELECTRON (file-detected) or opt-out vars.RELEASE_SRCDIR_GATE=off -> BYPASS, always bump as today (electron has NO source dir, so a naive SRC_DIR test would never bump — a release-breaking trap); (2) else SRC_DIR is REQUIRED — a non-electron repo with no resolvable SRC_DIR FAILS LOUD (::error:: + exit 1), never silently never-bump; (3) else diff the push range (github.event.before..GITHUB_SHA, with a HEAD^1 / merge-base fallback) intersected with ${SRC_DIR}/ — source hit -> should_bump=true, none -> should_bump=false. workflow_dispatch always bumps. The self-heal monotonic bump + the new vNN semver tag are now gated on should_bump!='false' so no-bump mode keeps the version PUT; the publish + "Update release notes and reconcile changelog" (channel-pointer resolution) run on BOTH paths to refresh the current release. Loop-safety is unchanged: the job if: still re-checks [skip ci]/[skip sync], so the workflow's own commits never re-trigger. Fleet-wide release-semantics change — validate on one repo before syncing. PREVIOUS (07.20.00): the dev/promote bump + release steps now name the per-run token env var GITHUB_TOKEN (was the misleadingly-named MOKOGIT_TOKEN; the VALUE was always ${{ github.token }}). All token use in this workflow is SAME-REPO (release API, the release_bump_dev dev base bump, pushes to its own repo) — the org MOKOGIT_TOKEN secret is not referenced here. Naming-only; no behavior change. Mirrors the pre-release.yml fix (MokoGIT#1721). PREVIOUS: NEW (07.19.00): #1621 Phase 6 / .mokogit-private#76 — resolve the CURRENT stable release by its CHANNEL POINTER, not by a fixed tag_name=stable. Under version-primary releases (a release is born with the immutable tag_name=vNN and `stable`/`rc` attached as movable is_channel tags), GET /releases/tags/stable no longer resolves the current stable release, which would break two steps that read "the last released version". Both now call GET /repos/{owner}/{repo}/releases/channel/stable (#1702 — resolves whichever release the `stable` channel points at via ReleaseStreamMap) with a GRACEFUL FALLBACK to the legacy GET /releases/tags/stable for forges predating #1702 (the channel route 404s pre-rollout and the rolling `stable` tag still exists). (1) The monotonic-version guard ("Guarantee monotonic release version") resolves ROLLING_LAST by parsing the same `VERSION: NN.NN.NN` out of the channel-resolved release NAME. (2) "Update release notes and reconcile changelog" resolves the stable RELEASE_ID + VERSION the same channel-first way. The #386 integrity gate is UNCHANGED (it already keys off the immutable vNN version tag). SEQUENCING unchanged: do NOT enable in prod until #1702 server support is on stable/prod; works pre-#1702 via the fallback. PREVIOUS (07.18.00): #1621 Phase 6 — version/* BRANCHES retired in favor of dual-tagged version RELEASES. REMOVED three now-redundant steps: "Step 12: Create version branch from main", "Step 13: Prune superseded-major version branches", and "Advance rolling channel tag via git". A release's permanent version identity is now the git tag v<version>, attached by the publish as a MOVABLE channel tag on the dual-tagged release (MokoCLI release_publish -> release_create channel_tags[]/update_stream; the forge attaches/moves the tag at the release commit via AttachChannelTag server-side) — so a new cut is born dual-tagged (rolling `stable`/`release-candidate` primary tag + permanent v<version> channel tag) and the channel tag MOVES onto the new release instead of a new version/* branch being created. The rolling `stable` tag is still pinned by release_create forceTagRef() (#808), so the "Advance rolling channel tag" force-push was redundant. KEPT UNCHANGED: "Create semver tag" (v<version>), the #386 integrity gate (still resolves tags/stable to the built commit), and the monotonic guard (still reads the stable release name VERSION: NN.NN.NN). Pruning EXISTING version/* branches is a SEPARATE one-time manual reconcile — not done here. SEQUENCING: do NOT enable in prod until the #1621 SERVER channel_tags support is on stable/prod. PREVIOUS (07.17.00): Setup MokoCLI tools no longer installs a STALE mokocli during the registry publish-lag window (#1671). After the fresh registry install, the step now compares the installed /tmp/mokocli PROJECT_VERSION against the /opt/mokocli failsafe and PREFERS whichever is NEWER (sort -V), and the opportunistic /opt refresh is gated to only move FORWARD (never downgrade /opt when it is the newer, just-deployed copy). Backward-compatible: runners with no /opt/mokocli use the registry copy exactly as before. PREVIOUS (07.16.00): release-integrity + dev-channel fixes. (1) A new pre-publish "no stale (pre-merge) tree" guard (#428/MokoCLI#901) hard-FAILS the release if the triggering GITHUB_SHA is an ANCESTOR of the current branch tip (a concurrent merge landed mid-run) -- otherwise a green, version-bumped release silently ships old code missing that merge (reproduced: built ef8c1a5d while tip was 2740342). Fail-open on any unresolved/replica-lagged tip so a good release is never falsely blocked. Complements the mokocli engine assertion (packaged-tree == GITHUB_SHA), which cannot see that GITHUB_SHA itself trails the tip. (2) The "Post-release: Reset dev version" safety-net (#309/MokoCLI#900) no longer swallows failure: a failed dev-advance now emits ::error:: + a job-summary block (dev-channel would trail stable), and a SUCCESSFUL advance dispatches pre-release.yml (development) to actually publish the leading dev build -- the advance commit is [skip ci]+paths-ignored and never auto-fires. Stays green (continue-on-error) since stable already published. NEW (07.15.00): the rolling stable channel tag is now advanced via a git force-push BEFORE the #386 integrity gate. MokoGIT's tags API 405s on the release-attached `stable` tag, so release_publish.php could not move it ("Could not pin tag stable ... HTTP 405") and every Joomla/electron stable cut left the tag lagging the freshly-built commit -- red-failing a correct release on the integrity gate. A new step git-force-pushes refs/tags/stable to the built HEAD (force-push allowlist -- the same escape hatch the rc BRANCH pin already uses), so the gate passes and no manual tag repoint is needed. PREVIOUS (07.14.00): integrity gate #386 no longer false-fails. It compared the published tag against GITHUB_SHA (the triggering commit), but the publish step commits the CHANGELOG entry ("build NN.NN.NN [skip ci]") and mokocli pins the channel tag + semver tag to that POST-PUBLISH HEAD -- so every cut whose CHANGELOG wasn't pre-seeded on dev (e.g. a version set straight on the release branch) diverged by one commit and red-failed a correct release. The gate now compares against the actual built/published HEAD (git rev-parse HEAD, falling back to GITHUB_SHA), which equals what the tag is pinned to in both the commit and no-commit cases while still catching a tag pinned to a commit we did NOT build; the semver tag is pinned to the same built HEAD for consistency. PREVIOUS (07.13.00): RC is ON-DEMAND ONLY. The `pull_request` trigger (RC-preview on a PR to main/stable) is REMOVED and the promote-rc job now runs solely on workflow_dispatch action=promote-rc. Only dev + stable release automatically (dev via pre-release.yml push->dev; stable via the push->main release job here); alpha/beta/rc never auto-cut. PREVIOUS (07.12.00): electron repos create NO release on ANY channel (RC enabled=false + stable publish/tag/notes/integrity guarded on is_electron; opt back in with vars.ELECTRON_CI_BUILD=true) and build NO installer in CI — always built locally + uploaded manually; the dev/promote version BUMP still runs. Template-Generic #390. NEW (07.11.00): the electron installer build/attach steps (electron-builder via Wine/Xvfb) are gated to MANUAL workflow_dispatch only (`&& github.event_name == 'workflow_dispatch'`). An automatic push no longer auto-builds electron installers (heavy ~90m, runner-fragile — OOM/Wine-zombie; migrating to mokocli ElectronPackager, MokoCLI #756/#758). The version bump, CHANGELOG, tag and npm publish STILL run on push — only the electron installer is skipped and pushed MANUALLY via dispatch. Non-electron platforms (joomla/go/npm) unaffected. Template-Generic #390. Universal build & release - BUMP-ON-DEV: base version + CHANGELOG promoted on dev pre-promotion (promote-rc), main publishes --bump none --no-commit so it never diverges from dev. MokoCLI build agent (moko publish). NEW (07.06.00): npm stable publishing ABSORBED here from Template-NPM's retired npm-publish.yml as npm-gated dual-registry steps (MokoGIT registry via per-run token + npmjs.org best-effort), replacing the former npm-publish.yml dispatch. No-Makefile repos SKIP the release job gracefully (green) instead of failing. Conflict-marker scan reads git-tracked files only (git ls-files | xargs grep) so vendored/generated trees can't false-match. NEW (07.07.00): npm stamp normalizes the zero-padded Makefile PROJECT_VERSION (NN.NN.NN) to strict semver via awk before `npm version`, and an unstampable version now HARD-FAILS instead of silently keeping the old package.json version (#382). NEW (07.00.00): `moko assets:optimize` artifact-time step added before publish (graceful-skip on non-asset repos); Setup MokoCLI tools makes the fresh `moko` executable + first on PATH to fix exit-126 (mokocli #650).
10
+ # VERSION: 07.25.01
11
+ # BRIEF: NEW (07.25.01): consistency follow-up to 07.25.00 — the monotonic self-heal's DEV CARRY-FORWARD now also bumps PATCH, not MINOR. After the self-heal patch-bumps the stable base, it carries that bump forward to dev via `release_bump_dev.php --path . --branch dev --bump patch` (was `--bump minor`), so a direct-push / fast-merge hotfix does NOT inflate the dev base by a full minor — dev and stable stay aligned on the PATCH heal. UNCHANGED: the promote-rc job's planned-release dev PRE-bump (`release_bump_dev.php --path . --branch dev --bump minor`) still stays MINOR; only the self-heal carry-forward inside the fast-merge bump guard changes minor->patch. PREVIOUS (07.25.00): the "Guarantee monotonic release version (fast-merge bump guard)" self-heal now bumps PATCH, not MINOR. On a push to stable whose version was NOT pre-bumped on dev — a direct admin push, or a fast dev->stable merge that landed before promote-rc ran its dev pre-bump — the monotonic guard previously self-healed with a MINOR bump (`version_bump.php --minor --min-version "$LAST"`), minting a new MINOR for an unplanned change. It now self-heals with a PATCH bump (`version_bump.php --patch --min-version "$LAST"`), matching hotfix / direct-admin-push semantics: `--min-version "$LAST"` still guarantees the healed version is strictly above the last release, so monotonicity is preserved. PLANNED ladder releases are UNCHANGED — the promote-rc dev pre-bump (`release_bump_dev.php --bump minor`) and the stable `moko publish --bump none --no-commit` are untouched; only the non-pre-bumped self-heal path changes minor->patch. The dev side already patch-bumps per push (pre-release.yml `release_bump_dev.php --branch dev --bump patch`), so the healed PATCH carries forward consistently. PREVIOUS (07.24.00): npmjs.org publish now FAILS LOUD (::error:: + exit $rc) on an AUTHORIZATION error (404 "not in this registry" / 401 / 403 / ENEEDAUTH / EOTP) instead of swallowing EVERY failure as a non-failing ::warning:: — a missing-scope / unauthorized / revoked / expired NPM_TOKEN previously hid behind a green release fleet-wide (the "secondary registry, not failing the run" catch masked a 404 PUT, which is exactly how npm reports an unauthorized scoped write; CI had in fact NEVER successfully published any MCP package to npmjs while every run reported success). Transient/5xx/network failures still stay a non-failing ::warning:: (npmjs is the SECONDARY mirror; the MokoGIT registry is primary and already hard-fails), and an unset NPM_TOKEN still skips cleanly — so repos that do not publish to npmjs are 100% unaffected. PREVIOUS (07.23.00): "Vendor PHP dependencies (build-time)" added to BOTH publish legs (promote-rc + stable) — this workflow had NO build-time composer vendoring at all, so any component declaring runtime PHP deps via a sub-package composer.json (vendor-dir admin/vendor, e.g. mPDF in MokonyxCRM/MokonyxCRMDCM) shipped its rc/stable zip WITHOUT admin/vendor while the component manifest lists <folder>vendor</folder> — JInstaller then aborts the ENTIRE package install on-site ("File does not exist [ROOT][TMP]/.../admin/vendor" → "Failed to copy administrator files"). The step mirrors pre-release.yml 07.15.00: no-op without a sub-package composer.json; provisions ext-gd when missing (the release runners' php-cli lacks it, which failed mpdf dependency RESOLUTION outright); verifies the CONFIGURED vendor-dir (composer config vendor-dir) materialized autoload.php; FAILS the build loud when vendoring fails for a package whose manifest ships <folder>vendor</folder> (a green build with an install-bricking artifact is strictly worse than a red build); fail-soft warning for packages whose manifests do not reference vendor/. Runs before `moko publish` (release_publish.php → release_package.php zips the plain working tree; vendor/ was never excluded — the tree simply never had it in CI). PREVIOUS (07.22.00): #498 — the #469 SRC_DIR gate no longer self-traps, and the fast-merge guard is MINOR-identity aware (fixes stable version never advancing; MokoCLI stuck re-publishing v10.20.00). (1) The gate read SRC_DIR with grep|sed, capturing the LITERAL unexpanded `$(shell …)` from the fleet-standard `SRC_DIR := $(shell [ -d src ] && echo src || echo source)` — an unmatchable directory that forced should_bump=false on EVERY push even when source changed. It now resolves SRC_DIR via `make --eval` expansion, falls back to the grep|sed read only if make is unavailable, nulls out a still-unexpanded `$(...)`/backtick value, and treats a declared-but-NONEXISTENT SRC_DIR (repos with code in cli/lib/bin, not src/source) as the non-source denylist fallback instead of an unmatchable INCLUSION prefix. (2) The "Guarantee monotonic release version (fast-merge bump guard)" compared the FULL version, so dev's per-merge PATCH bumps (10.20.00→10.20.04) fooled it into "normal path, no heal" and it re-published the already-released vMAJOR.MINOR.00. It now compares the MINOR identity (MAJOR.MINOR), so a fast dev→stable merge with no promote-rc cuts a new MINOR. Fleet-wide release-semantics change — validate on one repo before syncing. PREVIOUS (07.21.00): #469 — gate the version BUMP on whether the push touched the Makefile SRC_DIR. The former non-code paths-ignore denylist on the push trigger is REMOVED: a docs/workflow/sample push no longer skips the pipeline, it now RUNS in no-bump "update the existing stable release in place" mode (re-stamps the UNCHANGED current version via the existing `moko publish --bump none --no-commit`, refreshes notes/assets, no version advance, no new vNN tag). A new early step "Resolve SRC_DIR bump gate (#469)" (id: srcgate) decides should_bump: (1) ELECTRON (file-detected) or opt-out vars.RELEASE_SRCDIR_GATE=off -> BYPASS, always bump as today (electron has NO source dir, so a naive SRC_DIR test would never bump — a release-breaking trap); (2) else SRC_DIR is REQUIRED — a non-electron repo with no resolvable SRC_DIR FAILS LOUD (::error:: + exit 1), never silently never-bump; (3) else diff the push range (github.event.before..GITHUB_SHA, with a HEAD^1 / merge-base fallback) intersected with ${SRC_DIR}/ — source hit -> should_bump=true, none -> should_bump=false. workflow_dispatch always bumps. The self-heal monotonic bump + the new vNN semver tag are now gated on should_bump!='false' so no-bump mode keeps the version PUT; the publish + "Update release notes and reconcile changelog" (channel-pointer resolution) run on BOTH paths to refresh the current release. Loop-safety is unchanged: the job if: still re-checks [skip ci]/[skip sync], so the workflow's own commits never re-trigger. Fleet-wide release-semantics change — validate on one repo before syncing. PREVIOUS (07.20.00): the dev/promote bump + release steps now name the per-run token env var GITHUB_TOKEN (was the misleadingly-named MOKOGIT_TOKEN; the VALUE was always ${{ github.token }}). All token use in this workflow is SAME-REPO (release API, the release_bump_dev dev base bump, pushes to its own repo) — the org MOKOGIT_TOKEN secret is not referenced here. Naming-only; no behavior change. Mirrors the pre-release.yml fix (MokoGIT#1721). PREVIOUS: NEW (07.19.00): #1621 Phase 6 / .mokogit-private#76 — resolve the CURRENT stable release by its CHANNEL POINTER, not by a fixed tag_name=stable. Under version-primary releases (a release is born with the immutable tag_name=vNN and `stable`/`rc` attached as movable is_channel tags), GET /releases/tags/stable no longer resolves the current stable release, which would break two steps that read "the last released version". Both now call GET /repos/{owner}/{repo}/releases/channel/stable (#1702 — resolves whichever release the `stable` channel points at via ReleaseStreamMap) with a GRACEFUL FALLBACK to the legacy GET /releases/tags/stable for forges predating #1702 (the channel route 404s pre-rollout and the rolling `stable` tag still exists). (1) The monotonic-version guard ("Guarantee monotonic release version") resolves ROLLING_LAST by parsing the same `VERSION: NN.NN.NN` out of the channel-resolved release NAME. (2) "Update release notes and reconcile changelog" resolves the stable RELEASE_ID + VERSION the same channel-first way. The #386 integrity gate is UNCHANGED (it already keys off the immutable vNN version tag). SEQUENCING unchanged: do NOT enable in prod until #1702 server support is on stable/prod; works pre-#1702 via the fallback. PREVIOUS (07.18.00): #1621 Phase 6 — version/* BRANCHES retired in favor of dual-tagged version RELEASES. REMOVED three now-redundant steps: "Step 12: Create version branch from main", "Step 13: Prune superseded-major version branches", and "Advance rolling channel tag via git". A release's permanent version identity is now the git tag v<version>, attached by the publish as a MOVABLE channel tag on the dual-tagged release (MokoCLI release_publish -> release_create channel_tags[]/update_stream; the forge attaches/moves the tag at the release commit via AttachChannelTag server-side) — so a new cut is born dual-tagged (rolling `stable`/`release-candidate` primary tag + permanent v<version> channel tag) and the channel tag MOVES onto the new release instead of a new version/* branch being created. The rolling `stable` tag is still pinned by release_create forceTagRef() (#808), so the "Advance rolling channel tag" force-push was redundant. KEPT UNCHANGED: "Create semver tag" (v<version>), the #386 integrity gate (still resolves tags/stable to the built commit), and the monotonic guard (still reads the stable release name VERSION: NN.NN.NN). Pruning EXISTING version/* branches is a SEPARATE one-time manual reconcile — not done here. SEQUENCING: do NOT enable in prod until the #1621 SERVER channel_tags support is on stable/prod. PREVIOUS (07.17.00): Setup MokoCLI tools no longer installs a STALE mokocli during the registry publish-lag window (#1671). After the fresh registry install, the step now compares the installed /tmp/mokocli PROJECT_VERSION against the /opt/mokocli failsafe and PREFERS whichever is NEWER (sort -V), and the opportunistic /opt refresh is gated to only move FORWARD (never downgrade /opt when it is the newer, just-deployed copy). Backward-compatible: runners with no /opt/mokocli use the registry copy exactly as before. PREVIOUS (07.16.00): release-integrity + dev-channel fixes. (1) A new pre-publish "no stale (pre-merge) tree" guard (#428/MokoCLI#901) hard-FAILS the release if the triggering GITHUB_SHA is an ANCESTOR of the current branch tip (a concurrent merge landed mid-run) -- otherwise a green, version-bumped release silently ships old code missing that merge (reproduced: built ef8c1a5d while tip was 2740342). Fail-open on any unresolved/replica-lagged tip so a good release is never falsely blocked. Complements the mokocli engine assertion (packaged-tree == GITHUB_SHA), which cannot see that GITHUB_SHA itself trails the tip. (2) The "Post-release: Reset dev version" safety-net (#309/MokoCLI#900) no longer swallows failure: a failed dev-advance now emits ::error:: + a job-summary block (dev-channel would trail stable), and a SUCCESSFUL advance dispatches pre-release.yml (development) to actually publish the leading dev build -- the advance commit is [skip ci]+paths-ignored and never auto-fires. Stays green (continue-on-error) since stable already published. NEW (07.15.00): the rolling stable channel tag is now advanced via a git force-push BEFORE the #386 integrity gate. MokoGIT's tags API 405s on the release-attached `stable` tag, so release_publish.php could not move it ("Could not pin tag stable ... HTTP 405") and every Joomla/electron stable cut left the tag lagging the freshly-built commit -- red-failing a correct release on the integrity gate. A new step git-force-pushes refs/tags/stable to the built HEAD (force-push allowlist -- the same escape hatch the rc BRANCH pin already uses), so the gate passes and no manual tag repoint is needed. PREVIOUS (07.14.00): integrity gate #386 no longer false-fails. It compared the published tag against GITHUB_SHA (the triggering commit), but the publish step commits the CHANGELOG entry ("build NN.NN.NN [skip ci]") and mokocli pins the channel tag + semver tag to that POST-PUBLISH HEAD -- so every cut whose CHANGELOG wasn't pre-seeded on dev (e.g. a version set straight on the release branch) diverged by one commit and red-failed a correct release. The gate now compares against the actual built/published HEAD (git rev-parse HEAD, falling back to GITHUB_SHA), which equals what the tag is pinned to in both the commit and no-commit cases while still catching a tag pinned to a commit we did NOT build; the semver tag is pinned to the same built HEAD for consistency. PREVIOUS (07.13.00): RC is ON-DEMAND ONLY. The `pull_request` trigger (RC-preview on a PR to main/stable) is REMOVED and the promote-rc job now runs solely on workflow_dispatch action=promote-rc. Only dev + stable release automatically (dev via pre-release.yml push->dev; stable via the push->main release job here); alpha/beta/rc never auto-cut. PREVIOUS (07.12.00): electron repos create NO release on ANY channel (RC enabled=false + stable publish/tag/notes/integrity guarded on is_electron; opt back in with vars.ELECTRON_CI_BUILD=true) and build NO installer in CI — always built locally + uploaded manually; the dev/promote version BUMP still runs. Template-Generic #390. NEW (07.11.00): the electron installer build/attach steps (electron-builder via Wine/Xvfb) are gated to MANUAL workflow_dispatch only (`&& github.event_name == 'workflow_dispatch'`). An automatic push no longer auto-builds electron installers (heavy ~90m, runner-fragile — OOM/Wine-zombie; migrating to mokocli ElectronPackager, MokoCLI #756/#758). The version bump, CHANGELOG, tag and npm publish STILL run on push — only the electron installer is skipped and pushed MANUALLY via dispatch. Non-electron platforms (joomla/go/npm) unaffected. Template-Generic #390. Universal build & release - BUMP-ON-DEV: base version + CHANGELOG promoted on dev pre-promotion (promote-rc), main publishes --bump none --no-commit so it never diverges from dev. MokoCLI build agent (moko publish). NEW (07.06.00): npm stable publishing ABSORBED here from Template-NPM's retired npm-publish.yml as npm-gated dual-registry steps (MokoGIT registry via per-run token + npmjs.org best-effort), replacing the former npm-publish.yml dispatch. No-Makefile repos SKIP the release job gracefully (green) instead of failing. Conflict-marker scan reads git-tracked files only (git ls-files | xargs grep) so vendored/generated trees can't false-match. NEW (07.07.00): npm stamp normalizes the zero-padded Makefile PROJECT_VERSION (NN.NN.NN) to strict semver via awk before `npm version`, and an unstampable version now HARD-FAILS instead of silently keeping the old package.json version (#382). NEW (07.00.00): `moko assets:optimize` artifact-time step added before publish (graceful-skip on non-asset repos); Setup MokoCLI tools makes the fresh `moko` executable + first on PATH to fix exit-126 (mokocli #650).
12
12
  #
13
13
  # +=======================================================================+
14
14
  # | UNIVERSAL BUILD & RELEASE PIPELINE |
@@ -57,9 +57,20 @@ on:
57
57
 
58
58
  env:
59
59
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
60
- MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
60
+ MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoenterprises.us' }}
61
61
  GIT_ORG: ${{ vars.MOKOGIT_ORG || github.repository_owner }}
62
62
  GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
63
+ # #546: PIN the mokocli version installed by "Setup MokoCLI tools" below. The
64
+ # install was UNPINNED (composer create-project ... mokoenterprises/mokocli with
65
+ # no constraint => latest every run), so a bad mokocli release silently breaks
66
+ # EVERY repo's release at build time. This single pin is the version both the
67
+ # install step AND any future bump reference. BUMP THIS DELIBERATELY — do not
68
+ # auto-roll. In particular it MUST be bumped by hand once MokoCLI #1120's
69
+ # installer-inline capability ships (folding inlineInstallerScriptBase() into
70
+ # release_package.php), which is gated behind MOKO_JOOMLA_INLINE; pair the bump
71
+ # with that flag so an un-updated mokocli is a no-op, not an error, for one
72
+ # release. Allow a per-repo override via vars.MOKOCLI_VERSION for pilots.
73
+ MOKOCLI_VERSION: ${{ vars.MOKOCLI_VERSION || '10.34.00' }}
63
74
 
64
75
  permissions:
65
76
  contents: write
@@ -132,7 +143,7 @@ jobs:
132
143
  run: |
133
144
  set -u
134
145
  TOK="$(printf '%s' "$GITHUB_TOKEN" | tr -d '[:space:]')"; echo "::add-mask::$TOK"
135
- SERVER="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}"
146
+ SERVER="${GITHUB_SERVER_URL:-https://git.mokoenterprises.us}"
136
147
  CLONE_URL="https://x-access-token:${TOK}@${SERVER#https://}/${GITHUB_REPOSITORY}.git"
137
148
  REF="${GITHUB_SHA:-${GITHUB_REF_NAME:-}}"
138
149
  echo "Retrying checkout of ${GITHUB_REPOSITORY}@${REF:-<HEAD>} into ${GITHUB_WORKSPACE}"
@@ -177,11 +188,16 @@ jobs:
177
188
  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
178
189
  fi
179
190
  rm -rf /tmp/mokocli
180
- REG='{"type":"composer","url":"https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"}'
191
+ REG='{"type":"composer","url":"https://git.mokoenterprises.us/api/packages/MokoEnterprises/composer"}'
192
+ # #546: install the PINNED mokocli (env MOKOCLI_VERSION) rather than
193
+ # latest — an exact `name:version` constraint on composer create-project.
194
+ # Roll this forward deliberately (see the env-block note). /opt/mokocli
195
+ # stays the stale-tolerant FAILSAFE below if the pinned registry install
196
+ # fails.
181
197
  if composer create-project --no-install --no-interaction --no-progress --quiet \
182
- --repository="${REG}" mokoconsulting/mokocli /tmp/mokocli >/dev/null 2>&1 \
198
+ --repository="${REG}" "mokoenterprises/mokocli:${MOKOCLI_VERSION}" /tmp/mokocli >/dev/null 2>&1 \
183
199
  && [ -f /tmp/mokocli/cli/version_bump.php ]; then
184
- echo "Installed mokocli fresh from the Composer registry (primary)"
200
+ echo "Installed mokocli ${MOKOCLI_VERSION} (pinned, #546) fresh from the Composer registry (primary)"
185
201
  # #1671: registry-publish LAG race. The registry install "succeeds" but can
186
202
  # serve the PREVIOUS package for ~tens of seconds after a MokoCLI release
187
203
  # (publish finishes AFTER the release run reports success). Meanwhile the
@@ -320,7 +336,7 @@ jobs:
320
336
  # the push + force-push allowlists and creates rc if it is absent.
321
337
  PUSH_URL="https://x-access-token:${PUSH_TOK}@${MOKOGIT_URL#https://}/${GIT_ORG}/${GIT_REPO}.git"
322
338
  git config --global user.name "mokogit-actions[bot]"
323
- git config --global user.email "actions@mokoconsulting.tech"
339
+ git config --global user.email "actions@mokoenterprises.us"
324
340
  git fetch --no-tags "$PUSH_URL" "${FROM}"
325
341
  git push --force "$PUSH_URL" "FETCH_HEAD:refs/heads/rc" \
326
342
  || { echo "::error::Failed to point rc at ${FROM} (${SRC_SHA}) via git push"; exit 1; }
@@ -355,9 +371,9 @@ jobs:
355
371
  # `git fetch origin rc` / `git checkout rc` is ambiguous and could grab the tag.
356
372
  git fetch origin "+refs/heads/rc:refs/remotes/origin/rc"
357
373
  git checkout -B rc origin/rc
358
- git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
374
+ git config --local user.email "mokogit-actions[bot]@mokoenterprises.us"
359
375
  git config --local user.name "mokogit-actions[bot]"
360
- git remote set-url origin "https://x-access-token:${PUSH_TOK}@git.mokoconsulting.tech/${{ github.repository }}.git"
376
+ git remote set-url origin "https://x-access-token:${PUSH_TOK}@git.mokoenterprises.us/${{ github.repository }}.git"
361
377
 
362
378
  # Build-time PHP dependency vendoring (universal, guarded — mirrors
363
379
  # pre-release.yml 07.15.00). If a sub-package ships a composer.json, fetch its
@@ -589,7 +605,7 @@ jobs:
589
605
  run: |
590
606
  set -u
591
607
  TOK="$(printf '%s' "$GITHUB_TOKEN" | tr -d '[:space:]')"; echo "::add-mask::$TOK"
592
- SERVER="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}"
608
+ SERVER="${GITHUB_SERVER_URL:-https://git.mokoenterprises.us}"
593
609
  CLONE_URL="https://x-access-token:${TOK}@${SERVER#https://}/${GITHUB_REPOSITORY}.git"
594
610
  REF="${GITHUB_SHA:-${GITHUB_REF_NAME:-}}"
595
611
  echo "Retrying full-history checkout of ${GITHUB_REPOSITORY}@${REF:-<HEAD>} into ${GITHUB_WORKSPACE}"
@@ -647,15 +663,34 @@ jobs:
647
663
  echo "is_electron=false" >> "$GITHUB_OUTPUT"
648
664
  fi
649
665
 
666
+ # -- Download-stub opt-in detection (#544) ---------------------------------
667
+ # ADDITIVE + GATED. An electron repo opts into the branded download-stub
668
+ # installer build by shipping BOTH tools/build-release.mjs (the combined
669
+ # build+publish driver) AND moko-installer.config.mjs (the stub config) at
670
+ # the repo root. When present, the stub build below REPLACES the factory
671
+ # electron-builder leg (mutually exclusive via the is_stub gate). A plain
672
+ # electron repo keeps its existing vars.ELECTRON_CI_BUILD factory path; a
673
+ # non-electron repo sets is_stub=false and runs nothing new.
674
+ - name: "Detect download-stub installer (opt-in, #544)"
675
+ id: stub
676
+ if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true'
677
+ run: |
678
+ if [ -f tools/build-release.mjs ] && [ -f moko-installer.config.mjs ]; then
679
+ echo "is_stub=true" >> "$GITHUB_OUTPUT"
680
+ echo "download-stub opt-in detected (tools/build-release.mjs + moko-installer.config.mjs) -- stub build enabled, factory electron-builder leg disabled."
681
+ else
682
+ echo "is_stub=false" >> "$GITHUB_OUTPUT"
683
+ fi
684
+
650
685
  - name: Configure git for bot pushes
651
686
  if: steps.gate.outputs.has_makefile == 'true'
652
687
  env:
653
688
  GITHUB_TOKEN: ${{ github.token }}
654
689
  run: |
655
690
  PUSH_TOK="$(printf '%s' "$GITHUB_TOKEN" | tr -d '[:space:]')"; [ -z "$PUSH_TOK" ] && PUSH_TOK="${{ github.token }}"; echo "::add-mask::$PUSH_TOK"
656
- git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
691
+ git config --local user.email "mokogit-actions[bot]@mokoenterprises.us"
657
692
  git config --local user.name "mokogit-actions[bot]"
658
- git remote set-url origin "https://x-access-token:${PUSH_TOK}@git.mokoconsulting.tech/${{ github.repository }}.git"
693
+ git remote set-url origin "https://x-access-token:${PUSH_TOK}@git.mokoenterprises.us/${{ github.repository }}.git"
659
694
 
660
695
  - name: Check for merge conflict markers
661
696
  if: steps.gate.outputs.has_makefile == 'true'
@@ -689,11 +724,16 @@ jobs:
689
724
  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
690
725
  fi
691
726
  rm -rf /tmp/mokocli
692
- REG='{"type":"composer","url":"https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"}'
727
+ REG='{"type":"composer","url":"https://git.mokoenterprises.us/api/packages/MokoEnterprises/composer"}'
728
+ # #546: install the PINNED mokocli (env MOKOCLI_VERSION) rather than
729
+ # latest — an exact `name:version` constraint on composer create-project.
730
+ # Roll this forward deliberately (see the env-block note). /opt/mokocli
731
+ # stays the stale-tolerant FAILSAFE below if the pinned registry install
732
+ # fails.
693
733
  if composer create-project --no-install --no-interaction --no-progress --quiet \
694
- --repository="${REG}" mokoconsulting/mokocli /tmp/mokocli >/dev/null 2>&1 \
734
+ --repository="${REG}" "mokoenterprises/mokocli:${MOKOCLI_VERSION}" /tmp/mokocli >/dev/null 2>&1 \
695
735
  && [ -f /tmp/mokocli/cli/version_bump.php ]; then
696
- echo "Installed mokocli fresh from the Composer registry (primary)"
736
+ echo "Installed mokocli ${MOKOCLI_VERSION} (pinned, #546) fresh from the Composer registry (primary)"
697
737
  # #1671: registry-publish LAG race. The registry install "succeeds" but can
698
738
  # serve the PREVIOUS package for ~tens of seconds after a MokoCLI release
699
739
  # (publish finishes AFTER the release run reports success). Meanwhile the
@@ -1026,7 +1066,7 @@ jobs:
1026
1066
  - name: "Guarantee monotonic release version (fast-merge bump guard)"
1027
1067
  id: monotonic
1028
1068
  # #469: in no-bump mode (a non-source push updating the existing release in
1029
- # place) the version must stay PUT, so the self-healing minor bump must not
1069
+ # place) the version must stay PUT, so the self-healing patch bump must not
1030
1070
  # run — skip this guard when steps.srcgate decided should_bump=false. On the
1031
1071
  # bump path (should_bump=true, or electron/opt-out/dispatch where srcgate is
1032
1072
  # bypassed/true) it runs exactly as before.
@@ -1078,20 +1118,26 @@ jobs:
1078
1118
  # already-released vMAJOR.MINOR.00 forever (observed: MokoCLI stuck at
1079
1119
  # v10.20.00 while dev reached 10.20.04), violating this guard's own "never
1080
1120
  # re-publish an already-released base" intent. Compare the MINOR identity
1081
- # (MAJOR.MINOR): heal (minor-bump) whenever CUR's minor has NOT advanced past
1082
- # the last release's minor. The normal promote-rc path (dev already
1083
- # minor-bumped) still no-ops.
1121
+ # (MAJOR.MINOR): heal (PATCH-bump above --min-version LAST) whenever CUR's
1122
+ # minor has NOT advanced past the last release's minor. An unplanned /
1123
+ # non-pre-bumped stable change (a direct admin push, or a fast dev->stable
1124
+ # merge that skipped the promote-rc dev pre-bump) is a hotfix-shaped event,
1125
+ # so it bumps PATCH, not MINOR — --min-version "$LAST" still guarantees the
1126
+ # healed version is strictly above the last release, and the dev carry-forward
1127
+ # below is ALSO --bump patch (consistent with the stable patch heal, so a
1128
+ # direct-push hotfix does not inflate dev by a full minor). The normal
1129
+ # promote-rc path (dev already minor-bumped) still no-ops.
1084
1130
  CUR_MI=$(printf '%s' "$CUR" | awk -F. '{printf "%s.%s", $1, $2}')
1085
1131
  LAST_MI=$(printf '%s' "$LAST" | awk -F. '{printf "%s.%s", $1, $2}')
1086
1132
  GREATEST_MI=$( { echo "$CUR_MI"; echo "$LAST_MI"; } | sort -V | tail -n1 || true)
1087
1133
  if [ "$CUR_MI" = "$LAST_MI" ] || [ "$GREATEST_MI" = "$LAST_MI" ]; then
1088
- echo "::warning::inherited minor ${CUR_MI} <= last release minor ${LAST_MI} (CUR=${CUR} LAST=${LAST}); minor-bump skipped -> self-healing"
1089
- if ! php ${MOKO_CLI}/version_bump.php --minor --min-version "$LAST" --path .; then
1134
+ echo "::warning::inherited minor ${CUR_MI} <= last release minor ${LAST_MI} (CUR=${CUR} LAST=${LAST}); pre-bump skipped -> self-healing PATCH bump"
1135
+ if ! php ${MOKO_CLI}/version_bump.php --patch --min-version "$LAST" --path .; then
1090
1136
  echo "::error::monotonic bump guard failed to advance the base"; exit 1
1091
1137
  fi
1092
1138
  NEW=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
1093
- echo "healed ${CUR} -> ${NEW} (publishing fresh; carrying forward to dev)"
1094
- php ${MOKO_CLI}/release_bump_dev.php --path . --branch dev --bump minor \n --repo "${{ github.repository }}" --token "${GITHUB_TOKEN:-${{ github.token }}}" \n || echo "::warning::dev carry-forward failed (non-fatal; release still publishes the healed version)"
1139
+ echo "healed ${CUR} -> ${NEW} (publishing fresh; carrying the PATCH forward to dev)"
1140
+ php ${MOKO_CLI}/release_bump_dev.php --path . --branch dev --bump patch \n --repo "${{ github.repository }}" --token "${GITHUB_TOKEN:-${{ github.token }}}" \n || echo "::warning::dev carry-forward failed (non-fatal; release still publishes the healed version)"
1095
1141
  else
1096
1142
  echo "base ${CUR} > last release ${LAST}: normal path, no heal"
1097
1143
  fi
@@ -1239,6 +1285,37 @@ jobs:
1239
1285
  echo "branch=main" >> "$GITHUB_OUTPUT"
1240
1286
  echo "Published version: ${VERSION}"
1241
1287
 
1288
+ - name: Deploy over SFTP to stable/prod (generic, presence-gated)
1289
+ # SECURITY: this is an unattended PROD deploy. Gate it as tightly as the
1290
+ # sibling "Deploy to prod" step: (1) presence of SFTP_STABLE_HOST,
1291
+ # (2) a successful release (version.skip != true), (3) the protected
1292
+ # stable/main ref (never a feature branch), and (4) the makefile gate the
1293
+ # prod step uses — so a repo without a build never SFTP-ships its raw tree.
1294
+ # RECOMMENDED: additionally scope secrets.SFTP_STABLE_KEY to a GitHub/Gitea
1295
+ # "environment" with required reviewers so the deploy credential cannot be
1296
+ # used without a human approval on the deployment.
1297
+ if: ${{ steps.version.outputs.skip != 'true' && vars.SFTP_STABLE_HOST != '' && steps.gate.outputs.has_makefile == 'true' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stable') }}
1298
+ env:
1299
+ DEPLOY_KEY: ${{ secrets.SFTP_STABLE_KEY }}
1300
+ KNOWN_HOSTS: ${{ vars.SFTP_STABLE_KNOWN_HOSTS }}
1301
+ SSH_USER: ${{ vars.SFTP_STABLE_USER }}
1302
+ SSH_HOST: ${{ vars.SFTP_STABLE_HOST }}
1303
+ SSH_PORT: ${{ vars.SFTP_STABLE_PORT }}
1304
+ DEST: ${{ vars.SFTP_STABLE_PATH }}
1305
+ run: |
1306
+ set -euo pipefail
1307
+ # never leave the deploy key / known_hosts on a shared runner
1308
+ trap 'rm -f ~/.ssh/id_sftp_deploy ~/.ssh/known_hosts_sftp' EXIT
1309
+ [ -n "$DEST" ] && [ "$DEST" != "/" ] || { echo "SFTP_STABLE_PATH empty/root; refusing"; exit 1; }
1310
+ install -d -m 700 ~/.ssh
1311
+ install -m600 /dev/null ~/.ssh/id_sftp_deploy # 0600 before any bytes (no umask window)
1312
+ printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_sftp_deploy; chmod 600 ~/.ssh/id_sftp_deploy
1313
+ printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts_sftp; chmod 644 ~/.ssh/known_hosts_sftp
1314
+ SSH="ssh -i $HOME/.ssh/id_sftp_deploy -p ${SSH_PORT:-22} -o UserKnownHostsFile=$HOME/.ssh/known_hosts_sftp -o StrictHostKeyChecking=yes -o IdentitiesOnly=yes"
1315
+ $SSH "$SSH_USER@$SSH_HOST" "rm -rf \"$DEST\" && mkdir -p \"$DEST\""
1316
+ tar czf - --exclude=.git --exclude=.mokogit --exclude=.github --exclude='*.token' --exclude='*.token.json' . | $SSH "$SSH_USER@$SSH_HOST" "tar xzf - -C \"$DEST\""
1317
+ echo "Deployed to stable: $SSH_HOST:$DEST"
1318
+
1242
1319
  - name: "Deploy to prod (inline — replaces standalone deploy.yml, #1427)"
1243
1320
  if: steps.gate.outputs.has_makefile == 'true' && steps.version.outputs.skip != 'true' && vars.PROD_SSH_USERNAME != ''
1244
1321
  env:
@@ -1280,24 +1357,116 @@ jobs:
1280
1357
  --ssh-host "${{ vars.PROD_SSH_HOST }}" --ssh-port "${{ vars.PROD_SSH_PORT }}" \
1281
1358
  --ssh-user "${{ vars.PROD_SSH_USERNAME }}" --ssh-key ~/.ssh/deploy_key
1282
1359
 
1283
- # -- Electron: build installers (Windows via Wine + Linux) and attach ------
1360
+ # -- Electron download-stub release (opt-in, #544) -------------------------
1361
+ # When a repo opts into the branded download-stub (steps.stub.is_stub), build
1362
+ # + publish the stub installer + payload via tools/build-release.mjs on the
1363
+ # `release` runner (this job already runs there), MIRRORING MokoHub's proven
1364
+ # custom-mokohub-build-installer.yml. build-release.mjs does its own publish,
1365
+ # so this fully REPLACES the factory electron-builder leg below (mutually
1366
+ # exclusive via the is_stub gate). Channel = stable (this is auto-release).
1367
+ - name: "Setup Node (electron stub)"
1368
+ if: steps.gate.outputs.has_makefile == 'true' && steps.stub.outputs.is_stub == 'true' && steps.version.outputs.skip != 'true'
1369
+ uses: actions/setup-node@v4
1370
+ with:
1371
+ node-version: 24.18.1
1372
+
1373
+ - name: "Setup pnpm (electron stub)"
1374
+ if: steps.gate.outputs.has_makefile == 'true' && steps.stub.outputs.is_stub == 'true' && steps.version.outputs.skip != 'true'
1375
+ uses: pnpm/action-setup@v4
1376
+ with:
1377
+ version: 11.20.0
1378
+
1379
+ - name: "Provision Wine + Xvfb (stub build)"
1380
+ if: steps.gate.outputs.has_makefile == 'true' && steps.stub.outputs.is_stub == 'true' && steps.version.outputs.skip != 'true'
1381
+ run: |
1382
+ # electron-builder's portable/NSIS target runs the stub under Wine and
1383
+ # needs 32-bit libs + an X display; provision i386 Wine + Xvfb and init
1384
+ # the prefix once so the first Wine call does not race prefix creation.
1385
+ sudo dpkg --add-architecture i386 || true
1386
+ sudo apt-get update -qq
1387
+ sudo apt-get install -y -qq xvfb >/dev/null 2>&1 || echo "::warning::xvfb install failed"
1388
+ sudo apt-get install -y -qq wine64 wine32 >/dev/null 2>&1 || sudo apt-get install -y -qq wine >/dev/null 2>&1 || echo "::warning::wine install failed"
1389
+ export WINEDEBUG=-all
1390
+ xvfb-run -a wineboot --init >/dev/null 2>&1 || true
1391
+
1392
+ - name: "Fetch moko-installer (file: dep sibling) [stub]"
1393
+ if: steps.gate.outputs.has_makefile == 'true' && steps.stub.outputs.is_stub == 'true' && steps.version.outputs.skip != 'true'
1394
+ env:
1395
+ # github.token is scoped to THIS repo only, so it CANNOT clone the
1396
+ # cross-repo private moko-installer ("Repository not found"). Use the
1397
+ # org MOKOGIT_TOKEN secret (cross-repo read); fall back to github.token.
1398
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
1399
+ GITHUB_TOKEN: ${{ github.token }}
1400
+ run: |
1401
+ # @mokoenterprises/moko-installer is a `file:../moko-installer` dep
1402
+ # (local-dev link); a fresh runner checkout has no sibling, so clone it
1403
+ # here. This is the STABLE release path, so pull moko-installer's stable
1404
+ # branch (the matching toolchain for a stable build).
1405
+ TOK="$(printf '%s' "${MOKOGIT_TOKEN:-$GITHUB_TOKEN}" | tr -d '[:space:]')"; echo "::add-mask::$TOK"
1406
+ SRV="${GITHUB_SERVER_URL:-https://git.mokoenterprises.us}"
1407
+ rm -rf ../moko-installer
1408
+ # #546 (MEDIUM credential-exposure): do NOT embed the token in the clone
1409
+ # URL — that persists it in ../moko-installer/.git/config and exposes it in
1410
+ # the runner process list. Pass it as a one-shot http.extraHeader instead
1411
+ # (the URL carries NO token), then rewrite origin to the clean URL so no
1412
+ # credential lingers in .git/config. An always() cleanup step below removes
1413
+ # ../moko-installer entirely.
1414
+ REPO_URL="https://${SRV#https://}/MokoEnterprises/moko-installer.git"
1415
+ git -c http.extraHeader="Authorization: token ${TOK}" clone --depth 1 -b stable "$REPO_URL" ../moko-installer
1416
+ git -C ../moko-installer remote set-url origin "$REPO_URL"
1417
+
1418
+ - name: "Install dependencies (stub)"
1419
+ if: steps.gate.outputs.has_makefile == 'true' && steps.stub.outputs.is_stub == 'true' && steps.version.outputs.skip != 'true'
1420
+ run: pnpm install --no-frozen-lockfile
1421
+
1422
+ - name: "Build + publish stub installer + payload (#544)"
1423
+ if: steps.gate.outputs.has_makefile == 'true' && steps.stub.outputs.is_stub == 'true' && steps.version.outputs.skip != 'true'
1424
+ env:
1425
+ HUSKY: "0"
1426
+ WINEDEBUG: "-all"
1427
+ GITHUB_TOKEN: ${{ github.token }}
1428
+ # Scoped write:issue token for the installer bug-report feature (#86);
1429
+ # absent on repos without the secret -> build-release just leaves it unset.
1430
+ MOKO_REPORT_TOKEN: ${{ secrets.MOKO_REPORT_TOKEN }}
1431
+ run: |
1432
+ # tools/build-release.mjs: electron-builder --win dir (payload) ->
1433
+ # buildInstaller (download mode) -> stub exe + payload tar.gz -> upload
1434
+ # both to the stable release. Wrapped in xvfb-run so the nested
1435
+ # electron-builder portable/NSIS pass has a virtual display under Wine.
1436
+ # 30-min guard (SIGTERM, then SIGKILL +30s), short of the job timeout so
1437
+ # a regression fails loudly, not as a silent kill.
1438
+ timeout -k 30 1800 xvfb-run -a node tools/build-release.mjs --channel stable --publish \
1439
+ || { rc=$?; echo "::error::build-release.mjs exited $rc (124 = 30-min guard) — see electron-builder output above"; exit $rc; }
1440
+
1441
+ - name: "Cleanup moko-installer clone (#546)"
1442
+ # #546: always() — remove the cloned sibling so no checkout (and its
1443
+ # .git/config) lingers on the shared runner workspace, even if a stub step
1444
+ # above failed. LITERAL if: always() (never a continue-on-error expression,
1445
+ # which Gitea would discard the whole workflow over); independent step — no
1446
+ # needs:+always() fan-in. Safe no-op when the clone never happened.
1447
+ if: always()
1448
+ run: rm -rf ../moko-installer
1449
+
1450
+ # -- Electron: FACTORY installers (Windows via Wine + Linux) and attach ----
1284
1451
  # No Windows/macOS runner: the `release` runner is Ubuntu, so we cross-build
1285
1452
  # the Windows target with Wine and build Linux natively. macOS is skipped
1286
1453
  # (cannot be cross-built off a Mac). All four steps no-op on non-electron repos.
1454
+ # ALSO gated on is_stub != 'true' (#544): a stub-opted repo runs the stub leg
1455
+ # above INSTEAD, so these two paths are mutually exclusive.
1287
1456
  - name: "Setup Node (electron)"
1288
- if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true' && steps.version.outputs.skip != 'true' && vars.ELECTRON_CI_BUILD == 'true'
1457
+ if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true' && steps.stub.outputs.is_stub != 'true' && steps.version.outputs.skip != 'true' && vars.ELECTRON_CI_BUILD == 'true'
1289
1458
  uses: actions/setup-node@v4
1290
1459
  with:
1291
1460
  node-version: 24.18.1
1292
1461
 
1293
1462
  - name: "Setup pnpm (electron)"
1294
- if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true' && steps.version.outputs.skip != 'true' && vars.ELECTRON_CI_BUILD == 'true'
1463
+ if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true' && steps.stub.outputs.is_stub != 'true' && steps.version.outputs.skip != 'true' && vars.ELECTRON_CI_BUILD == 'true'
1295
1464
  uses: pnpm/action-setup@v4
1296
1465
  with:
1297
1466
  version: 11.20.0
1298
1467
 
1299
1468
  - name: "Build electron installers (Windows via Wine + Linux)"
1300
- if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true' && steps.version.outputs.skip != 'true' && vars.ELECTRON_CI_BUILD == 'true'
1469
+ if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true' && steps.stub.outputs.is_stub != 'true' && steps.version.outputs.skip != 'true' && vars.ELECTRON_CI_BUILD == 'true'
1301
1470
  env:
1302
1471
  HUSKY: "0"
1303
1472
  run: |
@@ -1336,7 +1505,7 @@ jobs:
1336
1505
  xvfb-run -a npx --no-install electron-builder --win --linux --publish never || xvfb-run -a npx electron-builder --win --linux --publish never
1337
1506
 
1338
1507
  - name: "Attach electron installers to the release"
1339
- if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true' && steps.version.outputs.skip != 'true' && vars.ELECTRON_CI_BUILD == 'true'
1508
+ if: steps.gate.outputs.has_makefile == 'true' && steps.electron.outputs.is_electron == 'true' && steps.stub.outputs.is_stub != 'true' && steps.version.outputs.skip != 'true' && vars.ELECTRON_CI_BUILD == 'true'
1340
1509
  continue-on-error: true
1341
1510
  run: |
1342
1511
  API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
@@ -1721,7 +1890,7 @@ jobs:
1721
1890
  # Write the safely-extracted blob (NOT a copy of the possibly-symlinked file).
1722
1891
  install -m 0644 "${NOTES_CONTENT}" "${WIKI_DIR}/Release-Notes.md"
1723
1892
  cd "${WIKI_DIR}"
1724
- git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
1893
+ git config --local user.email "mokogit-actions[bot]@mokoenterprises.us"
1725
1894
  git config --local user.name "mokogit-actions[bot]"
1726
1895
  git config --local commit.gpgsign false
1727
1896
  git add -- Release-Notes.md
@@ -1798,7 +1967,7 @@ jobs:
1798
1967
  rm -f .npmrc; echo "$out"
1799
1968
  if [ "$rc" -eq 0 ]; then echo "Published to npmjs.org."
1800
1969
  elif printf '%s' "$out" | grep -qiE 'already exists|cannot publish over|EPUBLISHCONFLICT|409 Conflict'; then echo "::notice::npmjs.org already has this version - no-op."
1801
- elif printf '%s' "$out" | grep -qiE 'not in this registry|ENEEDAUTH|EOTP|need auth|E401|E403|401 Unauthorized|403 Forbidden|404 Not Found|Forbidden'; then echo "::error::npmjs.org publish FAILED — authorization error (npm exit ${rc}). NPM_TOKEN lacks publish/create rights on the @mokoconsulting scope (new-scoped-name first publish, an unauthorized token, or a revoked/expired token). Failing loud so a broken publish token cannot hide behind a green release — a 404 PUT is how npm masks an unauthorized scoped write."; exit "$rc"
1970
+ elif printf '%s' "$out" | grep -qiE 'not in this registry|ENEEDAUTH|EOTP|need auth|E401|E403|401 Unauthorized|403 Forbidden|404 Not Found|Forbidden'; then echo "::error::npmjs.org publish FAILED — authorization error (npm exit ${rc}). NPM_TOKEN lacks publish/create rights on the @mokoenterprises scope (new-scoped-name first publish, an unauthorized token, or a revoked/expired token). Failing loud so a broken publish token cannot hide behind a green release — a 404 PUT is how npm masks an unauthorized scoped write."; exit "$rc"
1802
1971
  else echo "::warning::npmjs.org publish failed (npm exit ${rc}) — transient/5xx/network issue; npmjs is the secondary mirror (MokoGIT registry is primary), not failing the run."; fi
1803
1972
 
1804
1973
  # -- VS Code extension: build .vsix -> attach to the release (Template-Generic#515)
@@ -1,11 +1,11 @@
1
- # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
1
+ # Copyright (C) 2026 Moko Enterprises <hello@mokoenterprises.us>
2
2
  #
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
  #
5
5
  # FILE INFORMATION
6
6
  # DEFGROUP: MokoGIT.Workflow
7
7
  # INGROUP: MokoCLI.Universal
8
- # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
8
+ # REPO: https://git.mokoenterprises.us/MokoEnterprises/Template-Generic
9
9
  # PATH: /.mokogit/workflows/branch-cleanup.yml
10
10
  # VERSION: 01.00.00
11
11
  # BRIEF: Delete feature branches after PR merge
@@ -39,7 +39,7 @@ jobs:
39
39
  env:
40
40
  MOKOGIT_TOKEN: ${{ github.token }}
41
41
  REPO: ${{ github.repository }}
42
- API_BASE: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
42
+ API_BASE: ${{ vars.MOKOGIT_URL || 'https://git.mokoenterprises.us' }}
43
43
  BRANCH: ${{ github.event.pull_request.head.ref }}
44
44
  run: |
45
45
  set -euo pipefail
@@ -1,11 +1,11 @@
1
- # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
1
+ # Copyright (C) 2026 Moko Enterprises <hello@mokoenterprises.us>
2
2
  #
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
  #
5
5
  # FILE INFORMATION
6
6
  # DEFGROUP: MokoGIT.Workflow
7
7
  # INGROUP: MokoCLI.Universal
8
- # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
8
+ # REPO: https://git.mokoenterprises.us/MokoEnterprises/Template-Generic
9
9
  # PATH: /.mokogit/workflows/ci-issue-reporter.yml
10
10
  # VERSION: 01.00.00
11
11
  # BRIEF: Reusable workflow � creates/updates a MokoGIT issue when a CI gate fails.
@@ -62,9 +62,9 @@ jobs:
62
62
  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 || true
63
63
  fi
64
64
  rm -rf /tmp/mokocli
65
- REG='{"type":"composer","url":"https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"}'
65
+ REG='{"type":"composer","url":"https://git.mokoenterprises.us/api/packages/MokoEnterprises/composer"}'
66
66
  if composer create-project --no-install --no-interaction --no-progress --quiet \
67
- --repository="${REG}" mokoconsulting/mokocli /tmp/mokocli >/dev/null 2>&1 \
67
+ --repository="${REG}" mokoenterprises/mokocli /tmp/mokocli >/dev/null 2>&1 \
68
68
  && [ -f /tmp/mokocli/cli/version_bump.php ]; then
69
69
  echo "Installed mokocli fresh from the Composer registry (primary)"
70
70
  echo "MOKO_CLI=/tmp/mokocli/cli" >> $GITHUB_ENV
@@ -83,7 +83,7 @@ jobs:
83
83
  - name: Report CI failure
84
84
  env:
85
85
  MOKOGIT_TOKEN: ${{ github.token }}
86
- MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
86
+ MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoenterprises.us' }}
87
87
  # Route all workflow_call inputs through env vars — referencing ${{ inputs.* }}
88
88
  # directly in a run: block interpolates at YAML-parse time and is a shell-injection
89
89
  # vector. Shell variables are not re-parsed, so a crafted input can't break out.