@mokoconsulting/mokoabs-mcp 0.0.0-stage → 4.0.2
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.
- package/.editorconfig +41 -0
- package/.gitattributes +94 -0
- package/.gitmessage +9 -0
- package/.mokogit/ISSUE_TEMPLATE/adr.md +110 -0
- package/.mokogit/ISSUE_TEMPLATE/bug_report.md +48 -0
- package/.mokogit/ISSUE_TEMPLATE/config.yml +18 -0
- package/.mokogit/ISSUE_TEMPLATE/documentation.md +52 -0
- package/.mokogit/ISSUE_TEMPLATE/feature_request.md +51 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_connection_issue.md +69 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_tool_request.md +50 -0
- package/.mokogit/ISSUE_TEMPLATE/question.md +82 -0
- package/.mokogit/ISSUE_TEMPLATE/rfc.md +126 -0
- package/.mokogit/ISSUE_TEMPLATE/security.md +51 -0
- package/.mokogit/ISSUE_TEMPLATE/version.md +24 -0
- package/.mokogit/actions/resolve-source-dir/action.yml +72 -0
- package/.mokogit/workflows/apidocs.yml +132 -0
- package/.mokogit/workflows/auto-assign.yml +76 -0
- package/.mokogit/workflows/auto-dev-issue.yml +207 -0
- package/.mokogit/workflows/auto-release.yml +1787 -0
- package/.mokogit/workflows/branch-cleanup.yml +63 -0
- package/.mokogit/workflows/cascade.yml +645 -0
- package/.mokogit/workflows/ci-issue-reporter.yml +100 -0
- package/.mokogit/workflows/cleanup.yml +112 -0
- package/.mokogit/workflows/makefile-lint.yml +240 -0
- package/.mokogit/workflows/npm-build-test.yml +507 -0
- package/.mokogit/workflows/npm-sdk-check.yml +110 -0
- package/.mokogit/workflows/npm-tool-inventory.yml +81 -0
- package/.mokogit/workflows/pr-branch-check.yml +92 -0
- package/.mokogit/workflows/pre-release.yml +1134 -0
- package/.mokogit/workflows/rc-revert.yml +75 -0
- package/AGENTS.md +48 -0
- package/CHANGELOG.md +37 -0
- package/CODE_OF_CONDUCT.md +70 -0
- package/CONTRIBUTING.md +164 -0
- package/LICENSE +696 -0
- package/Makefile +94 -0
- package/README.md +109 -2
- package/SECURITY.md +245 -0
- package/build/README.md +7 -0
- package/dist/README.md +7 -0
- package/dist/index.js +153 -0
- package/package.json +34 -5
- package/sample/config.example.json +15 -0
- package/source/client.ts +132 -0
- package/source/config.ts +139 -0
- package/source/credentialStore.ts +216 -0
- package/source/index.ts +235 -0
- package/source/tokenReader.ts +120 -0
- package/source/types.ts +63 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
#
|
|
5
|
+
# FILE INFORMATION
|
|
6
|
+
# DEFGROUP: MokoGIT.Workflow
|
|
7
|
+
# INGROUP: MokoCLI.Release
|
|
8
|
+
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
|
9
|
+
# PATH: /.mokogit/workflows/pre-release.yml
|
|
10
|
+
# VERSION: 07.15.00
|
|
11
|
+
# CHANGE (07.15.00): "Vendor PHP dependencies (build-time)" is no longer a silent no-op on the release runners. The step existed since 07.xx but EVERY vendored build was failing invisibly: the runners' php-cli ships WITHOUT ext-gd, so composer could not RESOLVE mpdf/mpdf at all ("mpdf/mpdf require ext-gd * -> it is missing from your system"), and the step's continue-on-error + `|| warning` fail-soft swallowed that into a green build — the zip then shipped WITHOUT admin/vendor while the component manifest lists <folder>vendor</folder>, so JInstaller aborts the ENTIRE package install on-site ("File does not exist [ROOT][TMP]/.../admin/vendor" → "Failed to copy administrator files"; reproduced on MokonyxCRMDCM + MokonyxCRM dev installs). Fix: (1) provision ext-gd when missing before resolving; (2) verify the package's CONFIGURED vendor-dir (composer config vendor-dir, e.g. admin/vendor) actually materialized autoload.php; (3) when vendoring fails for a package whose Joomla manifest ships <folder>vendor</folder>, FAIL the build loud (::error:: + exit 1, continue-on-error removed) — a broken artifact that bricks installs is strictly worse than a red build. Packages whose manifests do NOT reference vendor/ keep the old fail-soft warning, and repos with no sub-package composer.json are a clean no-op, so non-vendoring repos are 100% unaffected. PREVIOUS (07.14.00): #469 — gate the dev-channel version BUMP on whether the push touched the Makefile SRC_DIR. The former non-code/test paths-ignore denylist on the dev push trigger is REMOVED: a docs/workflow/sample/test-only push no longer skips the build, it now RUNS in no-bump "update the current rolling dev release in place" mode. A new step "Resolve SRC_DIR bump gate (#469)" (id: srcgate) mirrors auto-release.yml: (1) electron (file-detected) or opt-out vars.RELEASE_SRCDIR_GATE=off -> bypass/always-bump; (2) else SRC_DIR REQUIRED, missing -> fail loud; (3) else diff before..sha (HEAD^1 fallback) ∩ ${SRC_DIR}/ -> should_bump. The per-merge dev-base patch bump ("Advance dev base") is now gated on should_bump!='false', so a non-source push keeps the dev base PUT and the rest of the job re-issues the existing rolling dev release. Loop-safety unchanged (job if: still re-checks [skip ci]/[skip bump]). Fleet-wide release-semantics change. PREVIOUS (07.13.00): the dev-base-bump and checkout-fallback steps now name the per-run token env var GITHUB_TOKEN (was the misleadingly-named MOKOGIT_TOKEN; the VALUE was always ${{ github.token }}). Same-repo writes (the dev base bump) use the built-in per-run github.token with contents:write — NEVER the org MOKOGIT_TOKEN secret, which this workflow does not reference at all. Naming-only; no behavior change. (The earlier "it's an org secret" framing was wrong — MokoGIT#1721.) PREVIOUS (07.12.00): "Advance dev base (per-merge patch bump)" now FAILS the step (::error:: + exit 1) when release_bump_dev reports failure (0 version files stamped), instead of the old ::warning:: + exit 0. A stalled bump (e.g. a repo-side Contents-API HTTP 500 on release_bump_dev's temp-branch write) was silently re-cutting the SAME dev version on every merge — for ~10 merges on MokonyxTickets — so the Joomla update feed stayed at the installed version and NO update was ever offered, invisibly, because the step stayed green. Failing loud turns the pre-release RED so the stall is caught immediately. The GITHUB_TOKEN-unset and #1412 "already advanced" branches keep their graceful exit 0 (not failures). See MokoGIT#1721.
|
|
12
|
+
# BRIEF: NEW (07.15.00): build-time PHP vendoring is now LOUD and actually works on the runners — ext-gd provisioned (mpdf resolution failed without it), configured vendor-dir verified (autoload.php), and a vendoring failure for a package whose manifest ships <folder>vendor</folder> FAILS the build (continue-on-error removed) instead of green-shipping a zip that bricks the whole package install on JInstaller "File does not exist .../admin/vendor" (MokonyxCRMDCM/MokonyxCRM). PREVIOUS: NEW (07.13.00): dev-base-bump + checkout-fallback token env var renamed MOKOGIT_TOKEN → GITHUB_TOKEN (value unchanged = ${{ github.token }}; same-repo writes use the per-run github.token, never the org secret — naming clarity, MokoGIT#1721). PREVIOUS: NEW (07.12.00): the per-merge dev base bump now fails the pre-release (::error:: + exit 1) on a 0-file stamp instead of silently re-cutting the current version (MokoGIT#1721 — a stalled bump hid for ~10 merges on MokonyxTickets). PREVIOUS: NEW (07.11.00): Setup MokoCLI tools no longer installs a STALE mokocli during the registry publish-lag window (#1671). After the fresh registry install, the step compares the installed /tmp/mokocli PROJECT_VERSION against the /opt/mokocli failsafe and PREFERS whichever is NEWER (sort -V); 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.09.00): the "Cascade alpha + beta on base-version bump" step is REMOVED entirely (was opt-in via vars.PRERELEASE_CASCADE in 07.08.01). A dev push now cuts ONLY the development channel; alpha, beta and rc are ON-DEMAND ONLY — cut solely via manual workflow_dispatch (stability=alpha|beta|release-candidate). This is the durable fix: no repo/org var can re-enable an automatic alpha/beta cut. PREVIOUS (07.08.01): the "Cascade alpha + beta on base-version bump" step was OPT-IN via vars.PRERELEASE_CASCADE=true (default OFF). A dev push cuts ONLY the development channel; alpha/beta/rc come solely from manual workflow_dispatch. Previously alpha+beta auto-re-cut on every committed base bump, making Joomla offer the -beta build even to Dev-channel sites. NEW (07.08.00): electron repos now create NO release and build NO installer in CI on ANY channel (publish forced off for electron; opt back in with vars.ELECTRON_CI_BUILD=true) — always built locally + uploaded manually; the version BUMP still runs. Template-Generic #390. NEW (07.07.00): the electron installer build/attach steps (electron-builder via Wine/Xvfb) are gated to MANUAL workflow_dispatch only (`&& github.event_name == 'workflow_dispatch'`). A dev push no longer auto-builds electron installers (heavy ~90m, runner-fragile — OOM/Wine-zombie; migrating to mokocli ElectronPackager, MokoCLI #756/#758). The per-merge dev base bump ("Advance dev base") and "Resolve metadata and bump version" STILL run on push — only the electron installer is skipped and pushed MANUALLY via dispatch. Non-electron repos unaffected. Template-Generic #390. Pre-release on dev push + manual dispatch (rolling dev/rc asset, build-metadata version — #404/#1313: the build counter is folded into the PATCH with a single -<stability> suffix and NO dotted ".N" — the retired ".N" counter broke Joomla's updater fleet-wide, see the "Resolve metadata and bump version" step); non-Joomla MokoGIT release + cosmetic release-notes are best-effort. npm publishing is ABSORBED here (07.05.00) and CORRECTED (07.06.00): dev/alpha/beta/rc builds publish via plain `npm publish` (dist-tagged by channel) to the MokoGIT npm registry ONLY (per-run token; public npmjs is stable-only), is_npm-gated so non-npm repos are unaffected. The prior `moko publish` variant only made a git release (mokocli has no npm-publish verb), so dev builds never reached a registry. The dev push trigger skips [skip ci]/[skip bump] commits. NEW (06.04.00): on a dev push that BUMPS the committed base version, alpha + beta are re-cut at the same base so the whole pre-release ladder (dev/alpha/beta, plus rc/stable via auto-release) stays in lockstep — previously alpha/beta were cut only by manual dispatch and froze. Joomla-family + push only; additive, isolated, continue-on-error; single job (no matrix) to keep the fleet wave small. (Ported from Template-Joomla 06.06.00 — same step, `moko package` variant.) NEW (07.00.00): ladder-bootstrap step removed (channel ladder no longer auto-created here); `moko assets:optimize` artifact-time step added before packaging (graceful-skip on non-asset repos); Setup MokoCLI tools makes the fresh `moko` executable + first on PATH to fix exit-126 (mokocli #650). NEW (07.02.00): versioning comments corrected to the current scheme (MokoGIT #1313 — dev/rc patch-bump the base, single suffix, no dotted .N counter). CONSEQUENCE of re-cutting alpha/beta/dev at the SAME base: Joomla keeps the highest-precedence variant of a given base (beta > alpha > dev), so a site — even at minimum-stability Dev — is offered the -beta build of the newest base, not -dev. To pin a site to the -dev line, don't publish alpha/beta at that base (or point the site's update channel at dev), NOT by lowering minimum-stability (Dev is already the most permissive floor).
|
|
13
|
+
|
|
14
|
+
name: "Universal: Pre-Release"
|
|
15
|
+
|
|
16
|
+
# #404 — moved OFF every-push to a nightly schedule + manual dispatch. Building a
|
|
17
|
+
# dev/rc pre-release on every push (and committing a version bump back) caused
|
|
18
|
+
# manifest <version> merge conflicts and wasted CI builds + void versioned dev
|
|
19
|
+
# assets. Batching to nightly removes most of that load; the version now advances
|
|
20
|
+
# as BUILD METADATA (version_build_metadata.php) instead of a committed bump, and
|
|
21
|
+
# the asset is published under a FIXED rolling name replaced in place.
|
|
22
|
+
on:
|
|
23
|
+
# Dev-channel release on every merge to dev. Uses build-metadata versioning
|
|
24
|
+
# (version_build_metadata.php) so nothing is committed back to dev — the #404
|
|
25
|
+
# manifest <version> merge-conflict problem does NOT return. The concurrency guard
|
|
26
|
+
# below collapses a burst of merges into one.
|
|
27
|
+
#
|
|
28
|
+
# #469: the former non-code paths-ignore denylist (which SKIPPED docs / CI config /
|
|
29
|
+
# test-only / repo-metadata pushes entirely) is REMOVED. Such a push must now RUN
|
|
30
|
+
# the pipeline in no-bump "update the current rolling dev release in place" mode
|
|
31
|
+
# rather than be dropped: the in-job SRC_DIR-diff gate (steps.srcgate below) decides
|
|
32
|
+
# bump (push touched ${SRC_DIR}/) vs no-bump (it did not). Loop-safety is unchanged
|
|
33
|
+
# and fully covered by the job if: [skip ci]/[skip bump] re-checks, so the
|
|
34
|
+
# per-merge dev-base bump's own [skip ci] commit never re-triggers this workflow.
|
|
35
|
+
push:
|
|
36
|
+
branches:
|
|
37
|
+
- dev
|
|
38
|
+
workflow_dispatch:
|
|
39
|
+
inputs:
|
|
40
|
+
stability:
|
|
41
|
+
description: 'Pre-release channel'
|
|
42
|
+
required: true
|
|
43
|
+
default: development
|
|
44
|
+
type: choice
|
|
45
|
+
options:
|
|
46
|
+
- development
|
|
47
|
+
- alpha
|
|
48
|
+
- beta
|
|
49
|
+
- release-candidate
|
|
50
|
+
ref:
|
|
51
|
+
description: 'Branch to build (default: dev)'
|
|
52
|
+
required: false
|
|
53
|
+
default: dev
|
|
54
|
+
type: string
|
|
55
|
+
|
|
56
|
+
concurrency:
|
|
57
|
+
# Per channel + ref. A manual alpha/beta/rc dispatch has its own group, so it is
|
|
58
|
+
# never cancelled by a dev push (and vice versa); the nightly runs on the
|
|
59
|
+
# default-branch ref, a different group again.
|
|
60
|
+
#
|
|
61
|
+
# cancel-in-progress is TRUE: the release-runner fleet is capacity-constrained and
|
|
62
|
+
# cannot be scaled, and a full electron build is ~90 min, so a BURST of dev merges
|
|
63
|
+
# must not each run a serial build (that backs the runner up for hours). A
|
|
64
|
+
# superseding build cancels the older one and builds only the latest state.
|
|
65
|
+
# Safe for the pre-release channels: they publish to a ROLLING, last-write-wins
|
|
66
|
+
# tag, so a cancelled build's publish is simply re-done by the winning (latest)
|
|
67
|
+
# build; real STABLE releases go through auto-release.yml, which keeps its own
|
|
68
|
+
# serialized safety (cancel-in-progress:false there). Trade-off: a
|
|
69
|
+
# concurrency-CANCELLED run marks the commit combined status red on Gitea, but
|
|
70
|
+
# that status is cosmetic (CI is non-gating for merges) and is always superseded
|
|
71
|
+
# by the winning build green. The ~150x sync storm stays prevented by the
|
|
72
|
+
# paths-ignore denylist + the job-level ref gate.
|
|
73
|
+
group: pre-release-${{ github.ref }}-${{ inputs.stability || 'development' }}
|
|
74
|
+
cancel-in-progress: true
|
|
75
|
+
|
|
76
|
+
permissions:
|
|
77
|
+
contents: write
|
|
78
|
+
packages: write
|
|
79
|
+
pull-requests: write
|
|
80
|
+
|
|
81
|
+
env:
|
|
82
|
+
GIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
83
|
+
GIT_ORG: ${{ vars.MOKOGIT_ORG || github.repository_owner }}
|
|
84
|
+
GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
|
|
85
|
+
|
|
86
|
+
jobs:
|
|
87
|
+
build:
|
|
88
|
+
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
|
89
|
+
# LITERAL runner label — do NOT use ${{ vars.RELEASE_RUNNER_LABEL || 'release' }}.
|
|
90
|
+
# This MokoGIT/Gitea instance does not resolve the `|| 'release'` fallback for an
|
|
91
|
+
# unset var in the `runs-on` position, so the job gets no runner and aborts at the
|
|
92
|
+
# container level (every step, incl. step-0, marked failure). Runners are correct;
|
|
93
|
+
# the only registered release runner label is `release`. See #366 (regression) and
|
|
94
|
+
# #134 (same class: runs-on ubuntu-latest -> no runner).
|
|
95
|
+
runs-on: release
|
|
96
|
+
# Bound a hung/starved build so it can't squat the scarce release runner for
|
|
97
|
+
# hours (the act_runner default let stuck builds run ~3h before failing). A
|
|
98
|
+
# healthy package/electron build is well under this; anything past it is
|
|
99
|
+
# stuck and should fail fast to free the runner. Repos with a legitimately
|
|
100
|
+
# longer build override via the PRERELEASE_BUILD_TIMEOUT repo variable.
|
|
101
|
+
timeout-minutes: ${{ fromJSON(vars.PRERELEASE_BUILD_TIMEOUT || '90') }}
|
|
102
|
+
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
|
103
|
+
# Triggers: push to dev (per-merge dev-channel build), the nightly schedule
|
|
104
|
+
# (safety-net batch), and manual workflow_dispatch. The push trigger is scoped
|
|
105
|
+
# to `dev` + a non-code paths-ignore, so a template-sync/doc-only push can't
|
|
106
|
+
# reach it (the ~150x storm on chore/template-sync-* stays prevented).
|
|
107
|
+
# As defence-in-depth we also refuse a manual dispatch aimed at a bot/sync ref.
|
|
108
|
+
if: >-
|
|
109
|
+
github.event.repository.name != '.vault' &&
|
|
110
|
+
(
|
|
111
|
+
(
|
|
112
|
+
github.event_name == 'push' &&
|
|
113
|
+
!contains(github.event.head_commit.message, '[skip ci]') &&
|
|
114
|
+
!contains(github.event.head_commit.message, '[skip bump]')
|
|
115
|
+
) ||
|
|
116
|
+
github.event_name == 'schedule' ||
|
|
117
|
+
(
|
|
118
|
+
github.event_name == 'workflow_dispatch' &&
|
|
119
|
+
!startsWith(inputs.ref, 'chore/') &&
|
|
120
|
+
!startsWith(inputs.ref, 'sync/') &&
|
|
121
|
+
!startsWith(inputs.ref, 'bot/') &&
|
|
122
|
+
!startsWith(inputs.ref, 'template-sync')
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
steps:
|
|
127
|
+
# Build ref: the dispatch input if given, else `dev` — which is correct for
|
|
128
|
+
# both a push (only `dev` triggers it) and the nightly schedule (where
|
|
129
|
+
# github.ref_name would otherwise be the default branch, main).
|
|
130
|
+
- name: Resolve build ref
|
|
131
|
+
id: ref
|
|
132
|
+
run: echo "ref=${{ inputs.ref || 'dev' }}" >> "$GITHUB_OUTPUT"
|
|
133
|
+
|
|
134
|
+
- name: Checkout
|
|
135
|
+
uses: actions/checkout@v4
|
|
136
|
+
with:
|
|
137
|
+
fetch-depth: 0
|
|
138
|
+
token: ${{ github.token }}
|
|
139
|
+
ref: ${{ steps.ref.outputs.ref }}
|
|
140
|
+
submodules: false
|
|
141
|
+
|
|
142
|
+
# #1672: transient step-0 checkout aborts on the shared `release` runners under
|
|
143
|
+
# release-time load. This fallback fires ONLY when the checkout above failed
|
|
144
|
+
# (step-level `if: failure()` — a LITERAL, not a discarded continue-on-error
|
|
145
|
+
# expression) and re-materialises the workspace via a bounded 3-attempt git
|
|
146
|
+
# clone + backoff, checking out the SAME resolved build ref
|
|
147
|
+
# (steps.ref.outputs.ref) the checkout targeted. fetch-depth: 0 is preserved
|
|
148
|
+
# with a FULL clone (no --depth) + tags (the bump/version steps below need
|
|
149
|
+
# history + tags). Fails loudly (exit 1) after 3 attempts so a real breakage is
|
|
150
|
+
# never masked. Independent step — no needs:+always() fan-in.
|
|
151
|
+
- name: "Checkout fallback (retry transient flake) (#1672)"
|
|
152
|
+
if: failure()
|
|
153
|
+
env:
|
|
154
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
155
|
+
BUILD_REF: ${{ steps.ref.outputs.ref }}
|
|
156
|
+
run: |
|
|
157
|
+
set -u
|
|
158
|
+
TOK="$(printf '%s' "$GITHUB_TOKEN" | tr -d '[:space:]')"; echo "::add-mask::$TOK"
|
|
159
|
+
SERVER="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}"
|
|
160
|
+
CLONE_URL="https://x-access-token:${TOK}@${SERVER#https://}/${GITHUB_REPOSITORY}.git"
|
|
161
|
+
REF="${BUILD_REF:-${GITHUB_SHA:-}}"
|
|
162
|
+
echo "Retrying full-history checkout of ${GITHUB_REPOSITORY}@${REF:-<HEAD>} into ${GITHUB_WORKSPACE}"
|
|
163
|
+
ok=0
|
|
164
|
+
for attempt in 1 2 3; do
|
|
165
|
+
rm -rf "${GITHUB_WORKSPACE:?}/.git" 2>/dev/null || true
|
|
166
|
+
find "${GITHUB_WORKSPACE}" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true
|
|
167
|
+
if git clone "$CLONE_URL" "${GITHUB_WORKSPACE}" \
|
|
168
|
+
&& git -C "${GITHUB_WORKSPACE}" fetch --tags origin \
|
|
169
|
+
&& { [ -z "$REF" ] || git -C "${GITHUB_WORKSPACE}" checkout -f "$REF"; }; then
|
|
170
|
+
ok=1; echo "Fallback checkout succeeded on attempt ${attempt}."; break
|
|
171
|
+
fi
|
|
172
|
+
echo "::warning::Fallback checkout attempt ${attempt} failed; backing off $((attempt * 5))s"
|
|
173
|
+
sleep $((attempt * 5))
|
|
174
|
+
done
|
|
175
|
+
if [ "$ok" != "1" ]; then
|
|
176
|
+
echo "::error::Checkout failed after 3 retry attempts (#1672) — this is not a transient flake, failing loudly."
|
|
177
|
+
exit 1
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
- name: Update submodules to latest
|
|
181
|
+
# Skip cleanly on submodule-less repos. The old step was a silent no-op:
|
|
182
|
+
# `2>/dev/null || true` swallowed every error and the in-step `git pull`
|
|
183
|
+
# used an UN-credentialed remote, so private submodules never actually
|
|
184
|
+
# updated. Credential the private remote, then update each submodule to its
|
|
185
|
+
# tracked branch (default: the submodule's default branch) so the built
|
|
186
|
+
# package contains the latest submodule content. Per #404 this is
|
|
187
|
+
# working-tree only (no commit) — the package is built from the working tree.
|
|
188
|
+
env:
|
|
189
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
190
|
+
run: |
|
|
191
|
+
if [ -f .gitmodules ]; then
|
|
192
|
+
# Mask the token (trim + ::add-mask::) before embedding it in the git
|
|
193
|
+
# config URL rewrite — matches the checkout-fallback step's hygiene and
|
|
194
|
+
# keeps the derived value out of logs (#1840). --global is required so the
|
|
195
|
+
# rewrite reaches the submodule fetch subprocesses.
|
|
196
|
+
TOK="$(printf '%s' "$GITHUB_TOKEN" | tr -d '[:space:]')"; echo "::add-mask::$TOK"
|
|
197
|
+
git config --global url."https://x-access-token:${TOK}@git.mokoconsulting.tech/".insteadOf "https://git.mokoconsulting.tech/"
|
|
198
|
+
git submodule sync --recursive || true
|
|
199
|
+
# FAIL-OPEN (#430): the per-run Actions token is scoped to THIS repo, so a
|
|
200
|
+
# cross-repo submodule can return "Repository not found" and abort the whole
|
|
201
|
+
# release (seen: MokonyxCRM addons/* -> pre-release red). Warn loudly but do
|
|
202
|
+
# NOT red the build — the package is built from the working tree, and any
|
|
203
|
+
# accessible submodules still update.
|
|
204
|
+
git submodule update --init --remote --recursive \
|
|
205
|
+
|| echo "::warning::submodule update failed (inaccessible/optional cross-repo submodule; run token may lack read). Building from the working tree without it — see Template-Generic #430."
|
|
206
|
+
else
|
|
207
|
+
echo "No .gitmodules — skipping submodule update."
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
- name: Isolate workspace (remove foreign/stale checkouts)
|
|
211
|
+
run: |
|
|
212
|
+
find . -name .git -not -path './.git' -prune -print | while read nested; do
|
|
213
|
+
dir=$(dirname "$nested")
|
|
214
|
+
[ "$dir" = "." ] && continue
|
|
215
|
+
git ls-files --error-unmatch "$dir" >/dev/null 2>&1 && continue
|
|
216
|
+
echo "::warning::Removing untracked nested checkout: $dir"
|
|
217
|
+
rm -rf "$dir"
|
|
218
|
+
done
|
|
219
|
+
|
|
220
|
+
- name: Setup MokoCLI tools
|
|
221
|
+
run: |
|
|
222
|
+
# Install mokocli FRESH from the MokoGIT Composer registry on every run
|
|
223
|
+
# (PRIMARY) so CI always uses the latest published version. /opt/mokocli is
|
|
224
|
+
# a FAILSAFE only — its refresh unit (mokocli-sync.service) is disabled, so
|
|
225
|
+
# the on-disk snapshot rots and must NOT be preferred. Registry path
|
|
226
|
+
# /api/packages/** is anon-allowlisted (no MOKOGIT_TOKEN); it serves a dist
|
|
227
|
+
# zip (no git clone). --no-install: mokocli self-bootstraps via bundled lib/
|
|
228
|
+
# + a fallback PSR-4 autoloader (native ext-curl), so no vendor/ is needed.
|
|
229
|
+
if ! command -v composer > /dev/null 2>&1; then
|
|
230
|
+
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
|
|
231
|
+
fi
|
|
232
|
+
rm -rf /tmp/mokocli
|
|
233
|
+
REG='{"type":"composer","url":"https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"}'
|
|
234
|
+
if composer create-project --no-install --no-interaction --no-progress --quiet \
|
|
235
|
+
--repository="${REG}" mokoconsulting/mokocli /tmp/mokocli >/dev/null 2>&1 \
|
|
236
|
+
&& [ -f /tmp/mokocli/cli/version_bump.php ]; then
|
|
237
|
+
echo "Installed mokocli fresh from the Composer registry (primary)"
|
|
238
|
+
# #1671: registry-publish LAG race. The registry install "succeeds" but can
|
|
239
|
+
# serve the PREVIOUS package for ~tens of seconds after a MokoCLI release
|
|
240
|
+
# (publish finishes AFTER the release run reports success). Meanwhile the
|
|
241
|
+
# /opt/mokocli deploy (pull-timer) may already carry the just-released,
|
|
242
|
+
# NEWER engine. So instead of blindly using the registry copy, compare the
|
|
243
|
+
# two on-disk versions and PREFER whichever is newer. The Makefile
|
|
244
|
+
# PROJECT_VERSION is the version source of truth and ships in both the
|
|
245
|
+
# composer dist and /opt/mokocli. Backward-compatible: with no /opt/mokocli
|
|
246
|
+
# (most consumer runners), the registry copy is used exactly as before.
|
|
247
|
+
mk_ver() { sed -n 's/^[[:space:]]*PROJECT_VERSION[[:space:]]*:*=[[:space:]]*//p' "$1" 2>/dev/null | head -n1 | tr -d '[:space:]'; }
|
|
248
|
+
ver_newer() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n1)" = "$1" ]; }
|
|
249
|
+
REG_VER="$(mk_ver /tmp/mokocli/Makefile)"
|
|
250
|
+
OPT_VER="$(mk_ver /opt/mokocli/Makefile)"
|
|
251
|
+
MOKO_ROOT=/tmp/mokocli
|
|
252
|
+
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -n "$OPT_VER" ] && [ -n "$REG_VER" ] \
|
|
253
|
+
&& ver_newer "$OPT_VER" "$REG_VER"; then
|
|
254
|
+
echo "::notice::/opt/mokocli (${OPT_VER}) is newer than the registry copy (${REG_VER}) — using /opt (#1671 publish-lag race)."
|
|
255
|
+
MOKO_ROOT=/opt/mokocli
|
|
256
|
+
else
|
|
257
|
+
echo "Using registry copy (${REG_VER:-unknown}); /opt/mokocli=${OPT_VER:-none}"
|
|
258
|
+
fi
|
|
259
|
+
echo "MOKO_CLI=${MOKO_ROOT}/cli" >> $GITHUB_ENV
|
|
260
|
+
# #650: make the chosen moko executable + FIRST on PATH so bare `moko`
|
|
261
|
+
# never resolves to a stale/non-executable copy (exit 126).
|
|
262
|
+
for d in bin cli; do
|
|
263
|
+
if [ -f "${MOKO_ROOT}/$d/moko" ]; then
|
|
264
|
+
# #1263 defense-in-depth: strip CR bytes so a CRLF-tainted registry
|
|
265
|
+
# dist or rotted /opt snapshot can't yield `/usr/bin/env: 'php\r'`
|
|
266
|
+
# (exit 127). tr interprets \r as a real CR (unlike portable sed).
|
|
267
|
+
# /opt may be root-owned — write via a temp file + sudo when needed.
|
|
268
|
+
if tr -d '\r' < "${MOKO_ROOT}/$d/moko" > "/tmp/moko.lf" 2>/dev/null; then
|
|
269
|
+
if cp "/tmp/moko.lf" "${MOKO_ROOT}/$d/moko" 2>/dev/null; then :; \
|
|
270
|
+
else sudo cp "/tmp/moko.lf" "${MOKO_ROOT}/$d/moko" 2>/dev/null || true; fi
|
|
271
|
+
rm -f "/tmp/moko.lf"
|
|
272
|
+
fi
|
|
273
|
+
chmod +x "${MOKO_ROOT}/$d/moko" 2>/dev/null || sudo chmod +x "${MOKO_ROOT}/$d/moko" 2>/dev/null || true
|
|
274
|
+
echo "${MOKO_ROOT}/$d" >> "$GITHUB_PATH"
|
|
275
|
+
fi
|
|
276
|
+
done
|
|
277
|
+
sudo chmod +x /opt/mokocli/*/moko 2>/dev/null || true
|
|
278
|
+
# Opportunistically refresh the /opt failsafe from this fresh copy when it
|
|
279
|
+
# differs — keeps the disabled-sync snapshot current so the fallback is
|
|
280
|
+
# never far behind. Best-effort: needs sudo, gated on a diff to avoid
|
|
281
|
+
# fleet-wide churn, never fails the job. #1671: only refresh FORWARD — skip
|
|
282
|
+
# when /opt is the newer copy we just chose, so we never downgrade it.
|
|
283
|
+
if [ "$MOKO_ROOT" = /tmp/mokocli ] && command -v sudo >/dev/null 2>&1 && [ -d /opt/mokocli ] \
|
|
284
|
+
&& ! cmp -s /tmp/mokocli/cli/version_reset_dev.php /opt/mokocli/cli/version_reset_dev.php 2>/dev/null; then
|
|
285
|
+
sudo cp -a /tmp/mokocli/. /opt/mokocli/ 2>/dev/null \
|
|
286
|
+
&& echo "Refreshed /opt/mokocli failsafe from the fresh registry copy" \
|
|
287
|
+
|| echo " (/opt failsafe refresh skipped — no sudo/perms)"
|
|
288
|
+
fi
|
|
289
|
+
elif [ -f /opt/mokocli/cli/version_bump.php ]; then
|
|
290
|
+
echo "::warning::Registry install failed — using /opt/mokocli FAILSAFE (may be stale)"
|
|
291
|
+
echo "MOKO_CLI=/opt/mokocli/cli" >> $GITHUB_ENV
|
|
292
|
+
else
|
|
293
|
+
echo "::error::mokocli unavailable: registry install failed and no /opt/mokocli failsafe present"
|
|
294
|
+
exit 1
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
- name: Detect platform
|
|
298
|
+
id: platform
|
|
299
|
+
run: |
|
|
300
|
+
# PILOT: native-manifest platform detection via the MokoCLI build agent —
|
|
301
|
+
# NO metadata-API read. `moko platform:detect` (maps to platform_detect.php,
|
|
302
|
+
# detection-only) resolves the platform from the in-tree manifest and emits
|
|
303
|
+
# the single `platform` output the eligibility step below consumes. The
|
|
304
|
+
# former metadata-API-first lookup (GET /repos/.../metadata) is REMOVED; the
|
|
305
|
+
# build agent is the source of truth and no longer depends on the registry.
|
|
306
|
+
moko platform:detect --path . --github-output 2>/dev/null || true
|
|
307
|
+
# Fallback: if the agent emitted nothing (e.g. detached manifest), keep the
|
|
308
|
+
# file-based manifest read so repos still resolve a platform.
|
|
309
|
+
if ! grep -q '^platform=' "$GITHUB_OUTPUT" 2>/dev/null; then
|
|
310
|
+
echo "::notice::moko emitted no platform — falling back to manifest read"
|
|
311
|
+
moko manifest:read --path . --github-output 2>/dev/null || true
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
- name: Check platform eligibility
|
|
315
|
+
id: eligibility
|
|
316
|
+
run: |
|
|
317
|
+
# Shared release engine (#122): the pre-release path runs for every
|
|
318
|
+
# artifact platform that produces releases — joomla, npm, mcp, and client. Deploy-only platforms (go) are
|
|
319
|
+
# EXCLUDED: they consume a tag but never bump/release here.
|
|
320
|
+
#
|
|
321
|
+
# is_joomla_family selects the Joomla/PHP-specific steps (manifest
|
|
322
|
+
# element/zip resolution, release_package.php zip, updates.xml). Those
|
|
323
|
+
# steps stay TRUE for the Joomla family so Joomla behavior is unchanged,
|
|
324
|
+
# and are skipped for npm/mcp/client, which ride only the platform-
|
|
325
|
+
# agnostic steps (version, tag, changelog, release-notes, release,
|
|
326
|
+
# cascade). Build/publish stay in the platform shim (ADR #124).
|
|
327
|
+
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
328
|
+
IS_JOOMLA_FAMILY=false
|
|
329
|
+
# STEP 1 — Authoritative baseline (unchanged): the hardcoded case gate
|
|
330
|
+
# remains the source of truth for `proceed` and the default value of
|
|
331
|
+
# is_joomla_family. Everything below (the registry lookup) is PURELY
|
|
332
|
+
# ADDITIVE and can only *refine* is_joomla_family for a cleanly-matched
|
|
333
|
+
# platform — it never flips `proceed`, never downgrades, and any
|
|
334
|
+
# failure/absence leaves this decision exactly as it is today.
|
|
335
|
+
# ADDITIVE + GATED (Template-Generic#326): electron / mokohub-manifest /
|
|
336
|
+
# mokohub-recipes are first-class eligible artifact platforms. They ride
|
|
337
|
+
# the platform-agnostic pre-release path (version + release + attach) and
|
|
338
|
+
# each has its OWN gated packaging step below, so joomla/npm/mcp/client
|
|
339
|
+
# behaviour is 100% unchanged. is_manifest / is_recipes drive those steps.
|
|
340
|
+
IS_MANIFEST=false
|
|
341
|
+
IS_RECIPES=false
|
|
342
|
+
IS_NPM=false
|
|
343
|
+
case "$PLATFORM" in
|
|
344
|
+
joomla*)
|
|
345
|
+
PROCEED=true
|
|
346
|
+
IS_JOOMLA_FAMILY=true
|
|
347
|
+
;;
|
|
348
|
+
npm*)
|
|
349
|
+
PROCEED=true
|
|
350
|
+
IS_NPM=true
|
|
351
|
+
;;
|
|
352
|
+
mcp*|client*)
|
|
353
|
+
PROCEED=true
|
|
354
|
+
;;
|
|
355
|
+
electron*)
|
|
356
|
+
# electron is first-class eligible; the electron-builder installer
|
|
357
|
+
# legs below still self-gate on is_electron (file-detected) so a repo
|
|
358
|
+
# with an electron-builder config builds installers even before the
|
|
359
|
+
# registry classifies it (mokocli electron detection: MokoGIT #1201).
|
|
360
|
+
PROCEED=true
|
|
361
|
+
;;
|
|
362
|
+
mokohub-manifest*)
|
|
363
|
+
PROCEED=true
|
|
364
|
+
IS_MANIFEST=true
|
|
365
|
+
;;
|
|
366
|
+
mokohub-recipes*)
|
|
367
|
+
PROCEED=true
|
|
368
|
+
IS_RECIPES=true
|
|
369
|
+
;;
|
|
370
|
+
*)
|
|
371
|
+
PROCEED=false
|
|
372
|
+
echo "::notice::Platform '$PLATFORM' — not an artifact release platform, skipping pre-release auto-bump"
|
|
373
|
+
;;
|
|
374
|
+
esac
|
|
375
|
+
|
|
376
|
+
# STEP 2 — Registry enhancement (#125): classify the Joomla family from
|
|
377
|
+
# the live platform registry (GET /api/v1/platforms) so newly-registered
|
|
378
|
+
# manifest/packaging platforms get correct family classification without
|
|
379
|
+
# editing this workflow. We key on the registry `family` field: family
|
|
380
|
+
# `joomla` is exactly the Joomla-style manifest/packaging
|
|
381
|
+
# path (the manifest_element + release_package steps). We deliberately do
|
|
382
|
+
# NOT key on `manifest_based`, because npm and mcp are also
|
|
383
|
+
# manifest_based:true yet must ride the platform-agnostic path (family
|
|
384
|
+
# `node`) — so `manifest_based` is ambiguous here and `family` is the
|
|
385
|
+
# clean discriminator.
|
|
386
|
+
#
|
|
387
|
+
# Fail-safe contract: the curl is captured into a var (never allowed to
|
|
388
|
+
# crash the step under set -e -o pipefail), the JSON is parsed with a
|
|
389
|
+
# guarded python3 one-liner, and the result is only applied when it is
|
|
390
|
+
# exactly "true"/"false". On ANY of {non-200, curl error, empty/malformed
|
|
391
|
+
# JSON, parse failure, platform key absent} REGISTRY_FAMILY stays empty
|
|
392
|
+
# and we keep the STEP 1 hardcoded value untouched.
|
|
393
|
+
REGISTRY_JSON="$(curl -sf \
|
|
394
|
+
-H "Authorization: token ${{ github.token }}" \
|
|
395
|
+
"${GIT_URL}/api/v1/platforms" 2>/dev/null || true)"
|
|
396
|
+
REGISTRY_FAMILY=""
|
|
397
|
+
if [ -n "$REGISTRY_JSON" ]; then
|
|
398
|
+
# Single physical line of python3 so we stay safely inside this YAML
|
|
399
|
+
# block scalar (a column-0 continuation would terminate the scalar).
|
|
400
|
+
# Emits exactly: "true" -> matched key, family joomla;
|
|
401
|
+
# "false" -> matched key, some other family;
|
|
402
|
+
# "" -> key absent (fall back to hardcoded).
|
|
403
|
+
# On malformed/empty JSON the parse raises, 2>/dev/null hides the
|
|
404
|
+
# traceback and the trailing "|| true" keeps set -e -o pipefail from
|
|
405
|
+
# crashing the step, so REGISTRY_FAMILY stays "" and we fall back.
|
|
406
|
+
REGISTRY_FAMILY="$(printf '%s' "$REGISTRY_JSON" | PLATFORM="$PLATFORM" python3 -c 'import json,os,sys; p=os.environ.get("PLATFORM",""); f=next((x.get("family","") for x in json.load(sys.stdin) if x.get("key")==p), None); sys.stdout.write("true" if f == "joomla" else ("false" if f is not None else ""))' 2>/dev/null || true)"
|
|
407
|
+
fi
|
|
408
|
+
if [ "$REGISTRY_FAMILY" = "true" ]; then
|
|
409
|
+
IS_JOOMLA_FAMILY=true
|
|
410
|
+
echo "::notice::Registry classified '$PLATFORM' as Joomla family (via /api/v1/platforms)"
|
|
411
|
+
elif [ "$REGISTRY_FAMILY" = "false" ]; then
|
|
412
|
+
IS_JOOMLA_FAMILY=false
|
|
413
|
+
echo "::notice::Registry classified '$PLATFORM' as non-Joomla family (via /api/v1/platforms)"
|
|
414
|
+
else
|
|
415
|
+
echo "::notice::Registry lookup unavailable/absent for '$PLATFORM' — using hardcoded family classification"
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
# electron (ADDITIVE + GATED): mokocli does not detect electron yet
|
|
419
|
+
# (MokoGIT #1201), so gate on an electron-builder config at the repo root
|
|
420
|
+
# and ride the platform-agnostic pre-release path (release + attach).
|
|
421
|
+
IS_ELECTRON=false
|
|
422
|
+
if [ -f electron-builder.yml ] || [ -f electron-builder.yaml ]; then
|
|
423
|
+
IS_ELECTRON=true
|
|
424
|
+
PROCEED=true
|
|
425
|
+
fi
|
|
426
|
+
echo "is_electron=${IS_ELECTRON}" >> "$GITHUB_OUTPUT"
|
|
427
|
+
|
|
428
|
+
echo "is_manifest=${IS_MANIFEST}" >> "$GITHUB_OUTPUT"
|
|
429
|
+
echo "is_recipes=${IS_RECIPES}" >> "$GITHUB_OUTPUT"
|
|
430
|
+
echo "proceed=${PROCEED}" >> "$GITHUB_OUTPUT"
|
|
431
|
+
echo "is_joomla_family=${IS_JOOMLA_FAMILY}" >> "$GITHUB_OUTPUT"
|
|
432
|
+
echo "is_npm=${IS_NPM}" >> "$GITHUB_OUTPUT"
|
|
433
|
+
# Releases on/off publish gate (.mokogit-private#73): the version BUMP
|
|
434
|
+
# runs on every eligible repo (proceed), but PUBLISH is gated on the
|
|
435
|
+
# repo's Releases unit so releases-off repos (Template-*, etc.) still
|
|
436
|
+
# bump WITHOUT publishing. Replaces the old !startsWith(name,Template-)
|
|
437
|
+
# name gate. Defaults ON unless the API explicitly returns
|
|
438
|
+
# has_releases=false (a transient error never disables a real publish).
|
|
439
|
+
HR=$(curl -sf -H "Authorization: token ${{ github.token }}" "${GIT_URL}/api/v1/repos/${{ github.repository }}" | jq -r '.has_releases' 2>/dev/null || true)
|
|
440
|
+
if [ "$PROCEED" = "true" ] && [ "$HR" != "false" ]; then PUBLISH=true; else PUBLISH=false; fi
|
|
441
|
+
# Electron: CI creates NO release and builds NO installer on ANY channel
|
|
442
|
+
# (dev/rc/stable) — always built locally + uploaded manually. The version
|
|
443
|
+
# BUMP (proceed) still runs; only the publish/build path is disabled. Opt a
|
|
444
|
+
# repo back into CI electron builds with vars.ELECTRON_CI_BUILD=true.
|
|
445
|
+
# (Template-Generic #390; electron -> mokocli ElectronPackager MokoCLI #756/#758.)
|
|
446
|
+
if [ "$IS_ELECTRON" = "true" ] && [ "${{ vars.ELECTRON_CI_BUILD }}" != "true" ]; then
|
|
447
|
+
PUBLISH=false
|
|
448
|
+
echo "::notice::electron repo — CI release/build disabled (local build + manual upload); version bump still runs"
|
|
449
|
+
fi
|
|
450
|
+
echo "publish=${PUBLISH}" >> "$GITHUB_OUTPUT"
|
|
451
|
+
echo "Publish gate: publish=${PUBLISH} (proceed=${PROCEED}, has_releases=${HR:-unknown})"
|
|
452
|
+
if [ "$PROCEED" = "true" ]; then
|
|
453
|
+
echo "::notice::Platform '$PLATFORM' eligible for pre-release (joomla_family=${IS_JOOMLA_FAMILY})"
|
|
454
|
+
fi
|
|
455
|
+
|
|
456
|
+
- name: "Resolve SRC_DIR bump gate (#469)"
|
|
457
|
+
id: srcgate
|
|
458
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
459
|
+
# #469 (mirrors auto-release.yml): decide version-BUMP vs NO-BUMP from whether
|
|
460
|
+
# this dev push touched the Makefile source directory (SRC_DIR). Replaces the
|
|
461
|
+
# old non-code paths-ignore denylist — a docs/workflow/sample/test-only push no
|
|
462
|
+
# longer skips the build, it runs in no-bump "update the current rolling dev
|
|
463
|
+
# release in place" mode. On the dev channel the version advance is the
|
|
464
|
+
# per-merge patch bump ("Advance dev base"); should_bump=false skips it so the
|
|
465
|
+
# dev base stays put and the existing rolling dev release is simply re-issued.
|
|
466
|
+
#
|
|
467
|
+
# Gate logic (per #469):
|
|
468
|
+
# 1. ELECTRON (file-detected via steps.eligibility.is_electron) or opt-out
|
|
469
|
+
# vars.RELEASE_SRCDIR_GATE=off -> BYPASS, keep always-bump (electron has
|
|
470
|
+
# NO source dir, so a naive SRC_DIR test would never bump — a trap).
|
|
471
|
+
# 2. Else SRC_DIR is REQUIRED; no resolvable SRC_DIR on a non-excluded repo
|
|
472
|
+
# -> FAIL LOUD (::error:: + exit 1), never silently never-bump.
|
|
473
|
+
# 3. Else diff the push range (before..sha, HEAD^1 fallback) intersected with
|
|
474
|
+
# ${SRC_DIR}/ : source -> should_bump=true; none -> should_bump=false.
|
|
475
|
+
# workflow_dispatch / schedule always bump (explicit/scheduled re-cut).
|
|
476
|
+
run: |
|
|
477
|
+
set -uo pipefail
|
|
478
|
+
if [ "${{ steps.eligibility.outputs.is_electron }}" = "true" ] || [ "${{ vars.RELEASE_SRCDIR_GATE }}" = "off" ]; then
|
|
479
|
+
echo "should_bump=true" >> "$GITHUB_OUTPUT"
|
|
480
|
+
echo "::notice::SRC_DIR gate BYPASSED (electron repo or vars.RELEASE_SRCDIR_GATE=off) — keeping always-bump behaviour (#469)."
|
|
481
|
+
exit 0
|
|
482
|
+
fi
|
|
483
|
+
if [ "${{ github.event_name }}" != "push" ]; then
|
|
484
|
+
echo "should_bump=true" >> "$GITHUB_OUTPUT"
|
|
485
|
+
echo "event=${{ github.event_name }} (dispatch/schedule) — always bump (#469)."
|
|
486
|
+
exit 0
|
|
487
|
+
fi
|
|
488
|
+
# Accept all three GNU make assignment forms: SRC_DIR=, SRC_DIR :=, and
|
|
489
|
+
# the conditional SRC_DIR ?= (the [:?]? matches an optional : or ?).
|
|
490
|
+
SRC_DIR="$(grep -m1 -E '^SRC_DIR[[:space:]]*[:?]?=' Makefile 2>/dev/null | sed -E 's/.*[:?]?=[[:space:]]*//' | tr -d '[:space:]')"
|
|
491
|
+
if [ -z "$SRC_DIR" ]; then
|
|
492
|
+
echo "::error::#469 SRC_DIR gate: no SRC_DIR resolvable from the Makefile on a non-electron repo. This is a misconfiguration — refusing to silently never-bump. Set SRC_DIR in the Makefile, or opt this repo out with vars.RELEASE_SRCDIR_GATE=off (electron-style always-bump)."
|
|
493
|
+
{
|
|
494
|
+
echo "### ❌ Pre-release gate failed — no SRC_DIR (#469)"
|
|
495
|
+
echo ""
|
|
496
|
+
echo "A non-electron repo must declare a Makefile \`SRC_DIR\`. Treating an empty SRC_DIR as \"never bump\" would silently break dev releases, so this fails loud. Fix: set \`SRC_DIR\`, or set \`vars.RELEASE_SRCDIR_GATE=off\`."
|
|
497
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
498
|
+
exit 1
|
|
499
|
+
fi
|
|
500
|
+
SRC_DIR="${SRC_DIR%/}"
|
|
501
|
+
BEFORE="${{ github.event.before }}"
|
|
502
|
+
SHA="${GITHUB_SHA:-}"
|
|
503
|
+
RANGE=""
|
|
504
|
+
ZERO="0000000000000000000000000000000000000000"
|
|
505
|
+
if [ -n "$BEFORE" ] && [ "$BEFORE" != "$ZERO" ] && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then
|
|
506
|
+
RANGE="${BEFORE}..${SHA}"
|
|
507
|
+
elif git rev-parse "${SHA}^1" >/dev/null 2>&1; then
|
|
508
|
+
RANGE="${SHA}^1..${SHA}"
|
|
509
|
+
fi
|
|
510
|
+
if [ -z "$RANGE" ]; then
|
|
511
|
+
echo "should_bump=true" >> "$GITHUB_OUTPUT"
|
|
512
|
+
echo "::notice::#469 SRC_DIR gate: no resolvable diff range (before=${BEFORE:-none}); defaulting to bump."
|
|
513
|
+
exit 0
|
|
514
|
+
fi
|
|
515
|
+
CHANGED="$(git diff --name-only "$RANGE" 2>/dev/null || true)"
|
|
516
|
+
SRC_HITS="$(printf '%s\n' "$CHANGED" | grep -E "^${SRC_DIR}/" || true)"
|
|
517
|
+
if [ -n "$SRC_HITS" ]; then
|
|
518
|
+
echo "should_bump=true" >> "$GITHUB_OUTPUT"
|
|
519
|
+
echo "#469 SRC_DIR gate: push touched ${SRC_DIR}/ (range ${RANGE}) — BUMP dev base + build release."
|
|
520
|
+
else
|
|
521
|
+
echo "should_bump=false" >> "$GITHUB_OUTPUT"
|
|
522
|
+
echo "::notice::#469 SRC_DIR gate: push touched NO ${SRC_DIR}/ paths (range ${RANGE}) — NO dev-base bump; re-issuing the current rolling dev release in place."
|
|
523
|
+
fi
|
|
524
|
+
|
|
525
|
+
- name: "Require Makefile (pre-release gate)"
|
|
526
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
527
|
+
# Enforce the same Makefile requirement as auto-release, but only when the
|
|
528
|
+
# platform is eligible to build/release (pre-release legitimately no-ops for
|
|
529
|
+
# ineligible platforms). Post-checkout step: a job-level if: cannot see the workspace.
|
|
530
|
+
run: |
|
|
531
|
+
if [ ! -f Makefile ] && [ ! -f makefile ] && [ ! -f GNUmakefile ]; then
|
|
532
|
+
echo "::error::pre-release requires a Makefile at the repository root — none found. A release-bearing repo must ship a Makefile (build/package targets). Aborting."
|
|
533
|
+
echo "## Pre-release blocked: no Makefile at repo root" >> "$GITHUB_STEP_SUMMARY"
|
|
534
|
+
exit 1
|
|
535
|
+
fi
|
|
536
|
+
echo "Makefile present — pre-release may proceed."
|
|
537
|
+
|
|
538
|
+
- name: "Advance dev base (per-merge patch bump)"
|
|
539
|
+
# RESTORES per-merge version advancement (fleet-wide dev-version freeze fix).
|
|
540
|
+
#
|
|
541
|
+
# WHY THIS EXISTS: the fleet's documented per-branch bumper — auto-bump.yml,
|
|
542
|
+
# referenced by auto-release.yml's "fix/patch/hotfix already carried their
|
|
543
|
+
# patch bump from auto-bump.yml" comment — DOES NOT EXIST in any template repo
|
|
544
|
+
# (404). Dev advancement therefore relied entirely on version_build_metadata.php
|
|
545
|
+
# folding a commit counter into the PATCH, which was removed (mokocli #459/#712)
|
|
546
|
+
# because it ballooned the dev patch past stable (e.g. 03.03.56 > stable 03.03.00)
|
|
547
|
+
# and stranded Joomla's updater. With neither mechanism, the committed dev base
|
|
548
|
+
# FREEZES between promotions: every dev build re-cuts the SAME <version>, so the
|
|
549
|
+
# updater never offers a newer dev build and merged fixes never reach dev sites.
|
|
550
|
+
#
|
|
551
|
+
# THE FIX: on a real dev merge, advance the committed base by exactly ONE clean
|
|
552
|
+
# patch via release_bump_dev.php --bump patch --no-changelog. This uses the
|
|
553
|
+
# Contents API (protected-branch bypass), is idempotent + monotonic (never moves
|
|
554
|
+
# dev down; a re-synchronize re-run is a safe no-op), and commits with a
|
|
555
|
+
# [skip ci] message so the bump does NOT re-trigger this workflow (the push
|
|
556
|
+
# trigger already filters [skip ci]/[skip bump]). +1-per-merge stays far below
|
|
557
|
+
# the next minor, so dev never outruns stable — the counter's failure mode is
|
|
558
|
+
# avoided. Changelog promotion is deliberately skipped here; it remains a
|
|
559
|
+
# promotion-time action (auto-release's release_bump_dev --bump minor).
|
|
560
|
+
#
|
|
561
|
+
# Only on a genuine dev push (never the nightly schedule or manual dispatch,
|
|
562
|
+
# and never a non-dev ref). Degrades safely: if the bump can't run (no bypass
|
|
563
|
+
# token) the build simply re-cuts the current version, exactly as today.
|
|
564
|
+
# #469: this per-merge patch bump IS the dev-channel version advance, so it is
|
|
565
|
+
# skipped in no-bump mode (steps.srcgate.should_bump=false — a non-source
|
|
566
|
+
# push). The dev base then stays put and the rest of the job re-issues the
|
|
567
|
+
# current rolling dev release in place. On the bump path it runs as before.
|
|
568
|
+
if: >-
|
|
569
|
+
steps.eligibility.outputs.proceed == 'true' &&
|
|
570
|
+
github.event_name == 'push' &&
|
|
571
|
+
steps.ref.outputs.ref == 'dev' &&
|
|
572
|
+
steps.srcgate.outputs.should_bump != 'false'
|
|
573
|
+
env:
|
|
574
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
575
|
+
run: |
|
|
576
|
+
if [ -z "${GITHUB_TOKEN}" ]; then
|
|
577
|
+
echo "::warning::GITHUB_TOKEN unset — cannot advance the dev base; this build re-cuts the current version."
|
|
578
|
+
exit 0
|
|
579
|
+
fi
|
|
580
|
+
# #1412 idempotency: if the merge/push that triggered this build already
|
|
581
|
+
# advanced the base (e.g. a PR that pre-bumped PROJECT_VERSION), do NOT
|
|
582
|
+
# bump again - a second +1 here doubles the version. Compare HEAD's base
|
|
583
|
+
# to its first parent (fetch-depth: 0 guarantees the parent is present).
|
|
584
|
+
if git rev-parse HEAD^1 >/dev/null 2>&1; then
|
|
585
|
+
cur_v="$(grep -E '^[[:space:]]*PROJECT_VERSION[[:space:]]*:?=' Makefile 2>/dev/null | head -1 | grep -oE '[0-9][0-9.]+' | head -1 || true)"
|
|
586
|
+
par_v="$(git show HEAD^1:Makefile 2>/dev/null | grep -E '^[[:space:]]*PROJECT_VERSION[[:space:]]*:?=' | head -1 | grep -oE '[0-9][0-9.]+' | head -1 || true)"
|
|
587
|
+
if [ -n "$cur_v" ] && [ -n "$par_v" ] && [ "$cur_v" != "$par_v" ]; then
|
|
588
|
+
echo "::notice::base already advanced this merge ($par_v -> $cur_v); skipping per-merge auto-bump to avoid doubling (#1412)."
|
|
589
|
+
exit 0
|
|
590
|
+
fi
|
|
591
|
+
fi
|
|
592
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
593
|
+
if ! php ${MOKO_CLI}/release_bump_dev.php --path . --branch dev --bump patch --no-changelog --token "${GITHUB_TOKEN}" --api-base "${API_BASE}"; then
|
|
594
|
+
echo "::error::dev base patch-bump FAILED — release_bump_dev stamped 0 version files, so the dev base did NOT advance. Refusing to silently re-cut the current version: a stalled bump keeps the Joomla update feed at the installed version and hides across many merges (it stayed green for ~10 merges on MokonyxTickets — MokoGIT#1721). This is usually a repo-side Contents-API write failure (HTTP 500 on the bot/version-advance temp branch). Fix the bump and re-run."
|
|
595
|
+
exit 1
|
|
596
|
+
fi
|
|
597
|
+
# release_bump_dev.php writes the BRANCH (Contents API), not the local
|
|
598
|
+
# checkout. Mirror the freshly-committed base into the working tree so the
|
|
599
|
+
# "Resolve metadata and bump version" step below (which reads the LOCAL
|
|
600
|
+
# Makefile) stamps THIS build with the newly-bumped version.
|
|
601
|
+
git fetch origin dev --quiet 2>/dev/null || true
|
|
602
|
+
git checkout origin/dev -- Makefile 2>/dev/null || true
|
|
603
|
+
echo "Dev base advanced; local Makefile mirrored for this build:"
|
|
604
|
+
grep -E '^\s*PROJECT_VERSION' Makefile || true
|
|
605
|
+
|
|
606
|
+
- name: Resolve metadata and bump version
|
|
607
|
+
id: meta
|
|
608
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
609
|
+
run: |
|
|
610
|
+
# #404 — stability comes from the dispatch input; the nightly schedule
|
|
611
|
+
# always builds the development channel. There is no push event.
|
|
612
|
+
if [ "${{ github.event_name }}" = "schedule" ]; then
|
|
613
|
+
STABILITY="development"
|
|
614
|
+
else
|
|
615
|
+
STABILITY="${{ inputs.stability || 'development' }}"
|
|
616
|
+
fi
|
|
617
|
+
|
|
618
|
+
case "$STABILITY" in
|
|
619
|
+
development) SUFFIX="-dev"; TAG="development" ;;
|
|
620
|
+
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
|
621
|
+
beta) SUFFIX="-beta"; TAG="beta" ;;
|
|
622
|
+
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
|
623
|
+
esac
|
|
624
|
+
|
|
625
|
+
# #404 — VERSION AS BUILD METADATA, NOT A COMMITTED BUMP.
|
|
626
|
+
# version_build_metadata.php derives a monotonically-advancing version by
|
|
627
|
+
# FOLDING the build counter (commits-since-base, #468) INTO THE PATCH and
|
|
628
|
+
# appending a SINGLE channel suffix — a clean XX.YY.ZZ-<stability> with NO
|
|
629
|
+
# dotted ".N" counter (MokoGIT #1313). It stamps this into the in-tree
|
|
630
|
+
# manifest ONLY (no git add/commit/push). This keeps <version> advancing so
|
|
631
|
+
# Joomla's updater detects each new dev/rc build, while removing the pushed
|
|
632
|
+
# bump commit that caused manifest <version> merge conflicts on every
|
|
633
|
+
# branch->dev merge.
|
|
634
|
+
php ${MOKO_CLI}/version_build_metadata.php \
|
|
635
|
+
--path . --stability "$STABILITY" --github-output 2>/dev/null || true
|
|
636
|
+
|
|
637
|
+
# #404 + #1313: cascade the FULL derived version (e.g. 02.91.03-dev), NOT
|
|
638
|
+
# the bare Makefile base. version_build_metadata folds the monotonic build
|
|
639
|
+
# counter INTO THE PATCH and emits build_version as a clean XX.YY.ZZ plus a
|
|
640
|
+
# SINGLE channel suffix (-dev/-alpha/-beta/-rc) — no dotted ".N".
|
|
641
|
+
#
|
|
642
|
+
# WHY the ".N" counter was removed (the fleet "version-freeze"): the old
|
|
643
|
+
# XX.YY.ZZ-<stability>.<N> form (e.g. 02.91.00-dev.3) broke the Joomla update
|
|
644
|
+
# chain end-to-end. The MokoGIT updates.xml generator dropped/mis-carried
|
|
645
|
+
# ".N" into <version> (MokoGIT #1307), and Joomla itself cannot parse a
|
|
646
|
+
# dotted pre-release counter: it fused ".N" into <element>, minted duplicate
|
|
647
|
+
# <version>s across builds, and broke version_compare — so dev/rc sites were
|
|
648
|
+
# NEVER offered a newer build (MokoGIT #1313). Folding the counter into the
|
|
649
|
+
# patch keeps <version> strictly increasing AND a plain, comparable
|
|
650
|
+
# XX.YY.ZZ-<stability>. NOTE: the feed is rendered on-demand by the MokoGIT
|
|
651
|
+
# server binary, so a generator fix only takes effect once that binary is
|
|
652
|
+
# deployed (this bit the fleet when a broken prod deploy stranded the fix).
|
|
653
|
+
# Re-reading the base here instead would discard the advance and re-freeze
|
|
654
|
+
# every build at the same <version>.
|
|
655
|
+
VERSION="$(grep '^build_version=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2 || true)"
|
|
656
|
+
if [ -z "$VERSION" ]; then
|
|
657
|
+
# Fallback when build metadata is unavailable: bare base + suffix (legacy,
|
|
658
|
+
# frozen-within-base behaviour — better than an empty version).
|
|
659
|
+
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
|
|
660
|
+
VERSION="$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')${SUFFIX}"
|
|
661
|
+
fi
|
|
662
|
+
|
|
663
|
+
# Pass the version VERBATIM — deliberately NO --stability. version_set_platform
|
|
664
|
+
# only strips a *trailing* -<stability> and only appends the suffix when it is
|
|
665
|
+
# absent, so a full "02.91.03-dev" is written unchanged via its semver branch;
|
|
666
|
+
# passing --stability would re-append and double the suffix (…-dev-dev).
|
|
667
|
+
# #289: also stamp <creationDate> (build date, UTC) + <author> (Makefile
|
|
668
|
+
# PROJECT_AUTHOR) into Joomla manifests at build. No-op on non-Joomla, and
|
|
669
|
+
# harmless on an older MokoCLI lacking --date/--author (2>/dev/null || true).
|
|
670
|
+
MG_BUILD_DATE="$(date -u +%Y-%m-%d)"
|
|
671
|
+
MG_AUTHOR="$(grep -m1 -E '^PROJECT_AUTHOR[[:space:]]*:?=' Makefile 2>/dev/null | sed -E 's/.*:?=[[:space:]]*//' | sed 's/[[:space:]]*$//' || true)"
|
|
672
|
+
php ${MOKO_CLI}/version_set_platform.php \
|
|
673
|
+
--path . --version "$VERSION" --branch "${{ steps.ref.outputs.ref }}" \
|
|
674
|
+
--date "$MG_BUILD_DATE" ${MG_AUTHOR:+--author "$MG_AUTHOR"} 2>/dev/null || true
|
|
675
|
+
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
|
676
|
+
|
|
677
|
+
# Emit a Joomla schema-update stub for each shipped extension's own
|
|
678
|
+
# version so #__schemas tracks the manifest (fixes "database out of date").
|
|
679
|
+
# --prune-above also removes leftover old-scheme (02.x/03.x) update files
|
|
680
|
+
# stranded above the manifest version by the rebrand down-reset, which
|
|
681
|
+
# otherwise keep ChangeSet::getSchema() pinned high (MokoCLI schema drift,
|
|
682
|
+
# Mokonyx #469). No-op on non-Joomla repos, which have no sql/updates dirs.
|
|
683
|
+
php ${MOKO_CLI}/schema_stub.php --path . --prune-above
|
|
684
|
+
|
|
685
|
+
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
|
|
686
|
+
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
|
|
687
|
+
|
|
688
|
+
# VERSION already carries its single -<stability> suffix (from build_version
|
|
689
|
+
# above), so it is NOT re-suffixed here — re-appending $SUFFIX produced the
|
|
690
|
+
# -dev-dev double-suffix (Template-Generic#319).
|
|
691
|
+
|
|
692
|
+
# NOTE: no commit/push here (#404). The derived version lives only in the
|
|
693
|
+
# built artifact + the dynamic updates feed; the branch tip is untouched.
|
|
694
|
+
|
|
695
|
+
# Auto-detect element via manifest_element.php (Joomla-family only:
|
|
696
|
+
# element/manifest resolution is Joomla-specific). For npm/mcp/client
|
|
697
|
+
# the generic fallback below derives ext_element/zip_name from the repo
|
|
698
|
+
# name — no Joomla manifest is present or required.
|
|
699
|
+
if [ "${{ steps.eligibility.outputs.is_joomla_family }}" = "true" ]; then
|
|
700
|
+
# NO --stability: $VERSION already carries its single -<stability> suffix,
|
|
701
|
+
# and manifest_element appends the stability suffix unconditionally — passing
|
|
702
|
+
# it would double the suffix in the zip name (…-dev-dev.zip). (#319)
|
|
703
|
+
php ${MOKO_CLI}/manifest_element.php \
|
|
704
|
+
--path . --version "$VERSION" \
|
|
705
|
+
--repo "${GIT_REPO}" --github-output
|
|
706
|
+
fi
|
|
707
|
+
|
|
708
|
+
# Read back element outputs
|
|
709
|
+
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2 || true)
|
|
710
|
+
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2 || true)
|
|
711
|
+
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
712
|
+
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
|
713
|
+
|
|
714
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
715
|
+
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
|
716
|
+
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
|
717
|
+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
718
|
+
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
|
719
|
+
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
|
720
|
+
|
|
721
|
+
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION} ==="
|
|
722
|
+
|
|
723
|
+
# npm publishing is NOT part of this universal workflow — it lives only in
|
|
724
|
+
# Template-NPM (.mokogit/workflows/npm-publish.yml) so it cannot leak into
|
|
725
|
+
# Joomla / non-npm repos via template sync. Joomla-family repos attach a zip
|
|
726
|
+
# below; npm repos publish from their own dedicated workflow.
|
|
727
|
+
# Release API operations (create / notes / upload / cascade delete) run
|
|
728
|
+
# against THIS repo, so they authenticate with the built-in per-run
|
|
729
|
+
# github.token (contents: write) — NOT the org MOKOGIT_TOKEN. That keeps
|
|
730
|
+
# MOKOGIT_TOKEN least-privilege (read-only, used only for the cross-repo
|
|
731
|
+
# mokocli / submodule clones above) while same-repo release writes stay
|
|
732
|
+
# scoped to the run — matching auto-release.yml (Template-Generic #188).
|
|
733
|
+
# github.token carries no trailing newline, but trim + mask anyway for
|
|
734
|
+
# Authorization-header hygiene.
|
|
735
|
+
- name: Sanitize release API token
|
|
736
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
737
|
+
run: |
|
|
738
|
+
T="$(printf '%s' "${{ github.token }}" | tr -d '[:space:]')"
|
|
739
|
+
echo "::add-mask::${T}"
|
|
740
|
+
echo "MOKO_TOKEN=${T}" >> "$GITHUB_ENV"
|
|
741
|
+
|
|
742
|
+
- name: Create release
|
|
743
|
+
id: release
|
|
744
|
+
if: steps.eligibility.outputs.publish == 'true'
|
|
745
|
+
# The MokoGIT release is the deliverable ONLY for the Joomla family. npm/mcp/
|
|
746
|
+
# client publish their real artifact upstream (npm) and merely mirror a
|
|
747
|
+
# release here, so a MokoGIT release failure must not fail those runs.
|
|
748
|
+
# release_create.php can fail its delete-then-recreate of the rolling dev/rc
|
|
749
|
+
# release (DELETE returns HTTP 200 but the release lingers), which turns an
|
|
750
|
+
# already-successful npm publish red. We branch on family INSIDE the run
|
|
751
|
+
# script (this act runner rejects `continue-on-error: ${{ ... }}` — it only
|
|
752
|
+
# accepts a literal bool): Joomla stays fatal; non-Joomla swallows a release
|
|
753
|
+
# failure so a green npm publish stays green.
|
|
754
|
+
run: |
|
|
755
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
756
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
757
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
758
|
+
if [ "${{ steps.eligibility.outputs.is_joomla_family }}" = "true" ]; then
|
|
759
|
+
php ${MOKO_CLI}/release_create.php \
|
|
760
|
+
--path . --version "$VERSION" --tag "$TAG" \
|
|
761
|
+
--token "${MOKO_TOKEN}" --api-base "$API_BASE" \
|
|
762
|
+
--repo "${GIT_REPO}" --branch "${{ steps.ref.outputs.ref }}" --prerelease
|
|
763
|
+
else
|
|
764
|
+
php ${MOKO_CLI}/release_create.php \
|
|
765
|
+
--path . --version "$VERSION" --tag "$TAG" \
|
|
766
|
+
--token "${MOKO_TOKEN}" --api-base "$API_BASE" \
|
|
767
|
+
--repo "${GIT_REPO}" --branch "${{ steps.ref.outputs.ref }}" --prerelease \
|
|
768
|
+
|| echo "::warning::MokoGIT release failed — non-fatal for npm/mcp/client (npm is the real artifact)"
|
|
769
|
+
fi
|
|
770
|
+
|
|
771
|
+
- name: "Deploy to dev (inline — replaces standalone deploy.yml, #1427)"
|
|
772
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/dev' && vars.DEV_SSH_USERNAME != ''
|
|
773
|
+
env:
|
|
774
|
+
DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
|
775
|
+
run: |
|
|
776
|
+
# #1427: dev-channel deploy inlined here (pre-release runs on dev push);
|
|
777
|
+
# the standalone deploy.yml is deleted. Only a genuine dev PUSH deploys
|
|
778
|
+
# (never the nightly schedule or a manual alpha/beta/rc dispatch). Gate =
|
|
779
|
+
# DEV_SSH_USERNAME set → non-deploy repos skip green. Targets from repo
|
|
780
|
+
# VARIABLES. mokocli deploy.php → restricted moko-deploy pattern.
|
|
781
|
+
set -euo pipefail
|
|
782
|
+
if [ -z "${DEPLOY_KEY:-}" ]; then
|
|
783
|
+
echo "::warning::DEV_SSH_USERNAME set but DEPLOY_SSH_KEY secret missing — skipping dev deploy"; exit 0
|
|
784
|
+
fi
|
|
785
|
+
mkdir -p ~/.ssh
|
|
786
|
+
# DEPLOY_SSH_KEY is base64-encoded (single line) so act_runner masks it in logs
|
|
787
|
+
# and the multi-line key is never echoed. Decode when it base64-decodes to a
|
|
788
|
+
# private key; else treat as a legacy raw PEM (back-compat during rollout).
|
|
789
|
+
install -m600 /dev/null ~/.ssh/deploy_key # 0600 before any bytes (no umask window)
|
|
790
|
+
trap 'rm -f ~/.ssh/deploy_key' EXIT # never leave the key on a shared runner
|
|
791
|
+
if printf '%s' "$DEPLOY_KEY" | base64 -d 2>/dev/null | grep -q 'PRIVATE KEY'; then
|
|
792
|
+
printf '%s' "$DEPLOY_KEY" | base64 -d > ~/.ssh/deploy_key
|
|
793
|
+
else
|
|
794
|
+
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
|
795
|
+
fi
|
|
796
|
+
TAG="$(git describe --tags --always 2>/dev/null || git rev-parse --short HEAD)-dev"
|
|
797
|
+
echo "Deploying dev tag ${TAG} to ${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }}"
|
|
798
|
+
php "${MOKO_CLI}/deploy.php" --tier dev --tag "$TAG" \
|
|
799
|
+
--ssh-host "${{ vars.DEV_SSH_HOST }}" --ssh-port "${{ vars.DEV_SSH_PORT }}" \
|
|
800
|
+
--ssh-user "${{ vars.DEV_SSH_USERNAME }}" --ssh-key ~/.ssh/deploy_key
|
|
801
|
+
|
|
802
|
+
# -- Electron pre-release: build installers (Windows via Wine + Linux) ------
|
|
803
|
+
# No Windows/macOS runner: cross-build Windows with Wine on the Ubuntu
|
|
804
|
+
# `release` runner, build Linux natively, skip macOS. Gated on is_electron
|
|
805
|
+
# so non-electron pre-releases are 100% unaffected.
|
|
806
|
+
- name: "Setup Node (electron)"
|
|
807
|
+
if: steps.eligibility.outputs.proceed == 'true' && steps.eligibility.outputs.is_electron == 'true' && vars.ELECTRON_CI_BUILD == 'true'
|
|
808
|
+
uses: actions/setup-node@v4
|
|
809
|
+
with:
|
|
810
|
+
node-version: 24.18.1
|
|
811
|
+
- name: "Setup pnpm (electron)"
|
|
812
|
+
if: steps.eligibility.outputs.proceed == 'true' && steps.eligibility.outputs.is_electron == 'true' && vars.ELECTRON_CI_BUILD == 'true'
|
|
813
|
+
uses: pnpm/action-setup@v4
|
|
814
|
+
with:
|
|
815
|
+
version: 11.20.0
|
|
816
|
+
- name: "Build electron installers (Windows via Wine + Linux) [pre-release]"
|
|
817
|
+
if: steps.eligibility.outputs.publish == 'true' && steps.eligibility.outputs.is_electron == 'true' && vars.ELECTRON_CI_BUILD == 'true'
|
|
818
|
+
env:
|
|
819
|
+
HUSKY: "0"
|
|
820
|
+
run: |
|
|
821
|
+
# electron-builder's NSIS target RUNS the built installer under Wine to
|
|
822
|
+
# generate + sign the uninstaller, which needs 32-bit Wine libs (the NSIS
|
|
823
|
+
# stub is a WoW64 exe) AND an X display. A bare `wine64` on an
|
|
824
|
+
# under-provisioned `release` runner aborts with "wine process failed 1
|
|
825
|
+
# ... ntdll.dll c0000135 / no driver could be loaded" (intermittent: only
|
|
826
|
+
# when a job lands on a runner without wine32/xvfb pre-seeded). Provision
|
|
827
|
+
# i386 Wine + Xvfb here so the Windows leg builds on ANY runner, and init
|
|
828
|
+
# the prefix once, headless, so electron-builder's first Wine call does
|
|
829
|
+
# not race prefix creation.
|
|
830
|
+
sudo dpkg --add-architecture i386 || true
|
|
831
|
+
sudo apt-get update -qq
|
|
832
|
+
sudo apt-get install -y -qq xvfb >/dev/null 2>&1 || echo "::warning::xvfb install failed"
|
|
833
|
+
sudo apt-get install -y -qq wine64 wine32 >/dev/null 2>&1 || sudo apt-get install -y -qq wine >/dev/null 2>&1 || sudo apt-get install -y -qq wine64 >/dev/null 2>&1 || echo "::warning::wine install failed -- Windows target may be skipped"
|
|
834
|
+
export WINEDEBUG=-all
|
|
835
|
+
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine}"
|
|
836
|
+
xvfb-run -a wineboot --init >/dev/null 2>&1 || true
|
|
837
|
+
pnpm install --frozen-lockfile
|
|
838
|
+
# Compile the app into electron-builder's app dir (directories.app, e.g. ./build)
|
|
839
|
+
# BEFORE packaging. electron-builder validates the app dir up-front, so running
|
|
840
|
+
# it without a prior compile aborts with "Application directory ./build doesn't
|
|
841
|
+
# exist". Prefer an explicit repo script (compile:app); fall back to the esbuild
|
|
842
|
+
# entrypoint that Ferdium-derived electron apps ship.
|
|
843
|
+
if pnpm run 2>/dev/null | grep -qiE '(^|[[:space:]])compile:app([[:space:]]|$)'; then
|
|
844
|
+
pnpm run compile:app
|
|
845
|
+
elif [ -f esbuild.mjs ]; then
|
|
846
|
+
npx --no-install preval-build-info-cli >/dev/null 2>&1 || true
|
|
847
|
+
node esbuild.mjs
|
|
848
|
+
else
|
|
849
|
+
echo "::warning::no compile step found (add a 'compile:app' package.json script or esbuild.mjs) -- electron-builder may fail if the app dir is not pre-built"
|
|
850
|
+
fi
|
|
851
|
+
# xvfb-run gives Wine a virtual X display for the installer-run pass that
|
|
852
|
+
# generates the NSIS uninstaller; without it Wine cannot create a window
|
|
853
|
+
# and the build fails ("no driver could be loaded / $DISPLAY").
|
|
854
|
+
xvfb-run -a npx --no-install electron-builder --win --linux --publish never || xvfb-run -a npx electron-builder --win --linux --publish never
|
|
855
|
+
- name: "Attach electron installers (pre-release)"
|
|
856
|
+
if: steps.eligibility.outputs.publish == 'true' && steps.eligibility.outputs.is_electron == 'true' && vars.ELECTRON_CI_BUILD == 'true'
|
|
857
|
+
continue-on-error: true
|
|
858
|
+
run: |
|
|
859
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
860
|
+
TOKEN="${MOKO_TOKEN}"
|
|
861
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
862
|
+
RID=$(curl -sf -H "Authorization: token ${TOKEN}" "${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
863
|
+
if [ -z "$RID" ]; then echo "::warning::pre-release ${TAG} not found -- skipping electron asset upload"; else
|
|
864
|
+
for f in $(find out dist_electron dist -maxdepth 2 -type f \( -name '*.exe' -o -name '*.AppImage' -o -name '*.deb' -o -name 'latest*.yml' \) 2>/dev/null); do
|
|
865
|
+
name=$(basename "$f")
|
|
866
|
+
curl -sf -X POST -H "Authorization: token ${TOKEN}" "${API_BASE}/releases/${RID}/assets?name=${name}" -F "attachment=@${f}" >/dev/null && echo "attached ${name}" || echo "::warning::upload failed ${name}"
|
|
867
|
+
done
|
|
868
|
+
fi
|
|
869
|
+
|
|
870
|
+
# -- npm pre-release: publish the dev/alpha/beta/rc build to the MokoGIT npm
|
|
871
|
+
# registry (INTERNAL ONLY). ABSORBED from Template-NPM's retired
|
|
872
|
+
# npm-prerelease.yml, but CORRECTED: the old step called `moko publish`, which
|
|
873
|
+
# maps to release_publish.php and does a GIT RELEASE ONLY — mokocli has no npm
|
|
874
|
+
# publish verb, so dev builds never actually reached an npm registry (only
|
|
875
|
+
# npm-publish.yml's plain `npm publish` ever did). This uses plain `npm publish`
|
|
876
|
+
# with the channel as the npm dist-tag, to the MokoGIT npm registry via the
|
|
877
|
+
# per-run Actions token (least-privilege). Public npmjs.org is deliberately NOT
|
|
878
|
+
# used for prereleases. Gated on is_npm; idempotent (existing version = no-op).
|
|
879
|
+
- name: "Setup Node (npm)"
|
|
880
|
+
if: steps.eligibility.outputs.publish == 'true' && steps.eligibility.outputs.is_npm == 'true'
|
|
881
|
+
uses: actions/setup-node@v4
|
|
882
|
+
with:
|
|
883
|
+
node-version: '20'
|
|
884
|
+
- name: "Publish npm package (pre-release -> MokoGIT registry, dist-tag by channel)"
|
|
885
|
+
if: steps.eligibility.outputs.publish == 'true' && steps.eligibility.outputs.is_npm == 'true'
|
|
886
|
+
env:
|
|
887
|
+
NODE_AUTH_TOKEN: ${{ github.token }}
|
|
888
|
+
run: |
|
|
889
|
+
set -uo pipefail
|
|
890
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
891
|
+
case "${{ steps.meta.outputs.stability }}" in
|
|
892
|
+
alpha) TAG=alpha ;;
|
|
893
|
+
beta) TAG=beta ;;
|
|
894
|
+
release-candidate|rc) TAG=rc ;;
|
|
895
|
+
*) TAG=dev ;;
|
|
896
|
+
esac
|
|
897
|
+
# Stamp package.json to the derived build version (in-tree only, never
|
|
898
|
+
# committed) so `npm publish` ships exactly the pre-release version.
|
|
899
|
+
npm version "$VERSION" --no-git-tag-version --allow-same-version >/dev/null 2>&1 || echo "::warning::could not stamp package.json to ${VERSION} (continuing)"
|
|
900
|
+
npm ci --no-audit --no-fund || npm install --no-audit --no-fund
|
|
901
|
+
npm run build --if-present
|
|
902
|
+
HOST="${GIT_URL#https://}"; OWNER="${GIT_ORG}"
|
|
903
|
+
REG="https://${HOST}/api/packages/${OWNER}/npm/"
|
|
904
|
+
echo "//${HOST}/api/packages/${OWNER}/npm/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc
|
|
905
|
+
set +e; out="$(npm publish --ignore-scripts --registry "${REG}" --tag "${TAG}" 2>&1)"; rc=$?; set -e
|
|
906
|
+
rm -f .npmrc; echo "$out"
|
|
907
|
+
if [ "$rc" -eq 0 ]; then echo "Published ${VERSION} (dist-tag ${TAG}) to the MokoGIT npm registry."
|
|
908
|
+
elif printf '%s' "$out" | grep -qiE 'already exists|cannot publish over|EPUBLISHCONFLICT|409 Conflict'; then echo "::notice::MokoGIT registry already has ${VERSION} - no-op (idempotent)."
|
|
909
|
+
else echo "::error::MokoGIT-registry pre-release publish FAILED (npm exit ${rc})."; exit "$rc"; fi
|
|
910
|
+
|
|
911
|
+
# -- mokohub-manifest pre-release: zip manifest.json -> attach ---------------
|
|
912
|
+
# ADDITIVE + GATED (Template-Generic#326). Package the repo's manifest.json
|
|
913
|
+
# into a rolling zip and attach it to the pre-release. Gated on is_manifest,
|
|
914
|
+
# so every other platform is unaffected. Best-effort (continue-on-error): a
|
|
915
|
+
# cosmetic packaging hiccup must not red an otherwise-good pre-release.
|
|
916
|
+
- name: "Package mokohub-manifest (zip manifest.json) [pre-release]"
|
|
917
|
+
if: >-
|
|
918
|
+
steps.eligibility.outputs.publish == 'true' &&
|
|
919
|
+
steps.eligibility.outputs.is_manifest == 'true'
|
|
920
|
+
continue-on-error: true
|
|
921
|
+
run: |
|
|
922
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
923
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
924
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
925
|
+
if [ ! -f manifest.json ]; then
|
|
926
|
+
echo "::warning::mokohub-manifest: no manifest.json at repo root — nothing to package"
|
|
927
|
+
exit 0
|
|
928
|
+
fi
|
|
929
|
+
ZIP="${RUNNER_TEMP}/manifest-${VERSION}.zip"
|
|
930
|
+
( cd "$(dirname manifest.json)" && zip -j "$ZIP" manifest.json ) \
|
|
931
|
+
|| { echo "::warning::mokohub-manifest: zip failed"; exit 0; }
|
|
932
|
+
RID=$(curl -sf -H "Authorization: token ${MOKO_TOKEN}" "${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
933
|
+
if [ -z "$RID" ]; then echo "::warning::pre-release ${TAG} not found — skipping manifest asset upload"; exit 0; fi
|
|
934
|
+
name="$(basename "$ZIP")"
|
|
935
|
+
# Replace-in-place: delete a same-named asset so the rolling zip updates.
|
|
936
|
+
AID=$(curl -sf -H "Authorization: token ${MOKO_TOKEN}" "${API_BASE}/releases/${RID}/assets" | N="$name" python3 -c "import json,sys,os; n=os.environ['N']; print(next((a['id'] for a in json.load(sys.stdin) if a.get('name')==n),''))" 2>/dev/null || true)
|
|
937
|
+
[ -n "$AID" ] && curl -sf -X DELETE -H "Authorization: token ${MOKO_TOKEN}" "${API_BASE}/releases/${RID}/assets/${AID}" >/dev/null 2>&1 || true
|
|
938
|
+
curl -sf -X POST -H "Authorization: token ${MOKO_TOKEN}" "${API_BASE}/releases/${RID}/assets?name=${name}" -F "attachment=@${ZIP}" >/dev/null \
|
|
939
|
+
&& echo "attached ${name}" || echo "::warning::manifest asset upload failed (${name})"
|
|
940
|
+
|
|
941
|
+
# -- mokohub-recipes pre-release: build catalog.zip (via MokoCLI) -> attach --
|
|
942
|
+
# ADDITIVE + GATED (Template-Generic#326). Delegate catalog.zip packaging to
|
|
943
|
+
# the MokoCLI build agent (`moko package`), which builds ONE signed catalog.zip
|
|
944
|
+
# per stream for the mokohub-recipes platform (MokoCLI #684 / epic
|
|
945
|
+
# mokohub-recipes#1). Gated on is_recipes; best-effort so joomla/npm/etc are
|
|
946
|
+
# untouched and a not-yet-shipped MokoCLI verb can't red the run.
|
|
947
|
+
- name: "Package mokohub-recipes (catalog.zip via MokoCLI) [pre-release]"
|
|
948
|
+
if: >-
|
|
949
|
+
steps.eligibility.outputs.publish == 'true' &&
|
|
950
|
+
steps.eligibility.outputs.is_recipes == 'true'
|
|
951
|
+
continue-on-error: true
|
|
952
|
+
run: |
|
|
953
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
954
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
955
|
+
STABILITY="${{ steps.meta.outputs.stability }}"
|
|
956
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
957
|
+
# `moko package` for the mokohub-recipes platform builds the per-stream
|
|
958
|
+
# catalog.zip (all recipes + all.json) and attaches it as the rolling
|
|
959
|
+
# stream asset. Same --stability/--rolling/--tag/attach contract as the
|
|
960
|
+
# Joomla zip step; the platform shim selects the catalog builder.
|
|
961
|
+
moko package \
|
|
962
|
+
--path . --version "$VERSION" --tag "$TAG" \
|
|
963
|
+
--token "${MOKO_TOKEN}" --api-base "$API_BASE" \
|
|
964
|
+
--repo "${GIT_REPO}" --out "$RUNNER_TEMP" \
|
|
965
|
+
--stability "$STABILITY" --rolling \
|
|
966
|
+
|| echo "::warning::mokohub-recipes: moko package (catalog.zip) failed or unavailable — non-fatal (MokoCLI #684)"
|
|
967
|
+
|
|
968
|
+
- name: Update release notes from CHANGELOG.md
|
|
969
|
+
if: steps.eligibility.outputs.publish == 'true'
|
|
970
|
+
# Release notes are cosmetic. The release-body PATCH can 403 on this MokoGIT
|
|
971
|
+
# instance (the per-run github.token lacks release-write), and for npm/mcp/
|
|
972
|
+
# client the published package is the real artifact — never let this fail
|
|
973
|
+
# the run. (Matches the cascade step's best-effort contract.)
|
|
974
|
+
continue-on-error: true
|
|
975
|
+
run: |
|
|
976
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
977
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
978
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
979
|
+
|
|
980
|
+
# Extract [Unreleased] section via the shared mokocli command (#364 centralization).
|
|
981
|
+
if [ -f "CHANGELOG.md" ]; then
|
|
982
|
+
NOTES=$(php ${MOKO_CLI}/release_notes.php --path . --version Unreleased 2>/dev/null || true)
|
|
983
|
+
# release_notes.php echoes "Release Unreleased" when the section is empty; normalize.
|
|
984
|
+
[ "$NOTES" = "Release Unreleased" ] && NOTES=""
|
|
985
|
+
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
986
|
+
else
|
|
987
|
+
NOTES="Release ${VERSION}"
|
|
988
|
+
fi
|
|
989
|
+
|
|
990
|
+
# Update release body via API
|
|
991
|
+
RELEASE_ID=$(curl -sf -H "Authorization: token ${MOKO_TOKEN}" \
|
|
992
|
+
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
993
|
+
|
|
994
|
+
if [ -n "$RELEASE_ID" ]; then
|
|
995
|
+
python3 -c "
|
|
996
|
+
import json, urllib.request
|
|
997
|
+
body = open('/dev/stdin').read()
|
|
998
|
+
payload = json.dumps({'body': body}).encode()
|
|
999
|
+
req = urllib.request.Request(
|
|
1000
|
+
'${API_BASE}/releases/${RELEASE_ID}',
|
|
1001
|
+
data=payload, method='PATCH',
|
|
1002
|
+
headers={
|
|
1003
|
+
'Authorization': 'token ${MOKO_TOKEN}',
|
|
1004
|
+
'Content-Type': 'application/json'
|
|
1005
|
+
})
|
|
1006
|
+
urllib.request.urlopen(req)
|
|
1007
|
+
" <<< "$NOTES"
|
|
1008
|
+
echo "Release notes updated from CHANGELOG.md"
|
|
1009
|
+
fi
|
|
1010
|
+
|
|
1011
|
+
# Build-time PHP dependency vendoring (universal, guarded). If a sub-package
|
|
1012
|
+
# ships a composer.json, fetch its runtime deps into that package's CONFIGURED
|
|
1013
|
+
# vendor dir (composer config vendor-dir — e.g. admin/vendor) BEFORE
|
|
1014
|
+
# release_package.php zips the working tree — so a component can declare a PHP
|
|
1015
|
+
# library (e.g. mPDF) via composer.json instead of committing vendor/. No-op
|
|
1016
|
+
# for any repo without a sub-package composer.json.
|
|
1017
|
+
#
|
|
1018
|
+
# (07.15.00) This step used to be a SILENT NO-OP on the release runners:
|
|
1019
|
+
# php-cli ships without ext-gd, so composer could not even RESOLVE mpdf/mpdf
|
|
1020
|
+
# ("require ext-gd * -> it is missing from your system"), and the old
|
|
1021
|
+
# continue-on-error + `|| warning` swallowed that into a green build. The zip
|
|
1022
|
+
# then shipped WITHOUT admin/vendor while the component manifest lists
|
|
1023
|
+
# <folder>vendor</folder>, so JInstaller aborts the ENTIRE package install
|
|
1024
|
+
# on-site ("File does not exist [ROOT][TMP]/.../admin/vendor" → "Failed to
|
|
1025
|
+
# copy administrator files"; reproduced on MokonyxCRMDCM + MokonyxCRM). Now:
|
|
1026
|
+
# ext-gd is provisioned when missing, the configured vendor-dir is verified
|
|
1027
|
+
# to have materialized autoload.php, and a vendoring failure for a package
|
|
1028
|
+
# whose manifest ships <folder>vendor</folder> FAILS the build loud — a
|
|
1029
|
+
# broken artifact that bricks installs is strictly worse than a red build.
|
|
1030
|
+
# Packages whose manifests do NOT reference vendor/ keep the fail-soft
|
|
1031
|
+
# warning, so non-vendoring repos are unaffected.
|
|
1032
|
+
- name: Vendor PHP dependencies (build-time)
|
|
1033
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
1034
|
+
run: |
|
|
1035
|
+
set -uo pipefail
|
|
1036
|
+
CJS="$(find source/packages src/packages -maxdepth 2 -name composer.json 2>/dev/null || true)"
|
|
1037
|
+
if [ -z "$CJS" ]; then echo "No sub-package composer.json — nothing to vendor."; exit 0; fi
|
|
1038
|
+
command -v composer >/dev/null 2>&1 || { sudo apt-get update -qq && sudo apt-get install -y -qq composer >/dev/null 2>&1 || true; }
|
|
1039
|
+
# mPDF (and most PHP PDF/image libs) hard-require ext-gd; without it
|
|
1040
|
+
# composer fails dependency RESOLUTION outright, not just a sub-dep.
|
|
1041
|
+
if ! php -m 2>/dev/null | grep -qi '^gd$'; then
|
|
1042
|
+
PHPV="$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null || true)"
|
|
1043
|
+
sudo apt-get update -qq || true
|
|
1044
|
+
sudo apt-get install -y -qq "php${PHPV}-gd" || sudo apt-get install -y -qq php-gd || echo "::warning::php-gd install failed (php ${PHPV})"
|
|
1045
|
+
php -m 2>/dev/null | grep -qi '^gd$' || echo "::warning::ext-gd STILL missing after install attempt — vendoring will fail for gd-requiring packages"
|
|
1046
|
+
fi
|
|
1047
|
+
rc=0
|
|
1048
|
+
for cj in $CJS; do
|
|
1049
|
+
dir="$(dirname "$cj")"
|
|
1050
|
+
echo "::notice::composer install (no-dev) in ${dir}"
|
|
1051
|
+
ok=true
|
|
1052
|
+
composer install --no-dev --no-interaction --no-progress --optimize-autoloader --working-dir="$dir" || ok=false
|
|
1053
|
+
# Verify the CONFIGURED vendor dir actually materialized — composer can
|
|
1054
|
+
# exit 0 while writing nothing the manifest needs.
|
|
1055
|
+
vd="$(composer config vendor-dir --working-dir="$dir" 2>/dev/null || true)"; vd="${vd:-vendor}"
|
|
1056
|
+
[ -f "${dir}/${vd}/autoload.php" ] || ok=false
|
|
1057
|
+
if [ "$ok" = "true" ]; then
|
|
1058
|
+
echo "vendored: ${dir}/${vd}"
|
|
1059
|
+
elif grep -qs '<folder>vendor</folder>' "${dir}"/*.xml; then
|
|
1060
|
+
echo "::error::vendoring FAILED in ${dir} but its Joomla manifest ships <folder>vendor</folder> — the built zip would abort the whole package install on JInstaller ('File does not exist .../vendor'). Failing the build instead of shipping a broken package."
|
|
1061
|
+
rc=1
|
|
1062
|
+
else
|
|
1063
|
+
echo "::warning::composer install failed in ${dir} — manifest does not ship vendor/; packaging the existing tree"
|
|
1064
|
+
fi
|
|
1065
|
+
done
|
|
1066
|
+
exit $rc
|
|
1067
|
+
|
|
1068
|
+
# Joomla-family only: mokocli release_package.php builds the extension zip
|
|
1069
|
+
# (+ sha256, attach). npm/mcp/client package/publish in their platform shim
|
|
1070
|
+
# (trust boundary, ADR #124), so this PHP zip step is skipped for them.
|
|
1071
|
+
- name: Optimize assets (artifact-time)
|
|
1072
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
1073
|
+
continue-on-error: true
|
|
1074
|
+
run: |
|
|
1075
|
+
moko assets:optimize --path . 2>/dev/null || echo "::notice::moko assets:optimize unavailable or no assets — skipping"
|
|
1076
|
+
|
|
1077
|
+
- name: Build package and upload
|
|
1078
|
+
id: package
|
|
1079
|
+
if: >-
|
|
1080
|
+
steps.eligibility.outputs.publish == 'true' &&
|
|
1081
|
+
steps.eligibility.outputs.is_joomla_family == 'true'
|
|
1082
|
+
run: |
|
|
1083
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
1084
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
1085
|
+
STABILITY="${{ steps.meta.outputs.stability }}"
|
|
1086
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
1087
|
+
# Publish the pre-release asset under a VERSIONED rolling name
|
|
1088
|
+
# (pkg_<ext>-<version>-dev.zip / -rc.zip) replaced in place each build. The
|
|
1089
|
+
# release version is stable within a base version, so the asset URL stays
|
|
1090
|
+
# stable while the version is visible in the filename. The in-package
|
|
1091
|
+
# manifest <version> still advances (build metadata) so the updater keeps
|
|
1092
|
+
# detecting new builds.
|
|
1093
|
+
# PILOT: build the package via the MokoCLI build agent. `moko package`
|
|
1094
|
+
# wraps release_package.php UNCHANGED (standards preserved) and writes the
|
|
1095
|
+
# artifact to $RUNNER_TEMP (CI-provided temp dir) instead of a hardcoded
|
|
1096
|
+
# /tmp. The --stability/--rolling/--tag/attach contract is identical.
|
|
1097
|
+
moko package \
|
|
1098
|
+
--path . --version "$VERSION" --tag "$TAG" \
|
|
1099
|
+
--token "${MOKO_TOKEN}" --api-base "$API_BASE" \
|
|
1100
|
+
--repo "${GIT_REPO}" --out "$RUNNER_TEMP" \
|
|
1101
|
+
--stability "$STABILITY" --rolling
|
|
1102
|
+
|
|
1103
|
+
# updates.xml <downloadurl> is generated server-side by MokoGIT (the license
|
|
1104
|
+
# server; being moved to MokoSuiteLicensing). It must reference the VERSIONED
|
|
1105
|
+
# rolling asset (pkg_<ext>-<version>-dev.zip) that release_package.php now
|
|
1106
|
+
# publishes — keep the server-side generator in sync with that filename.
|
|
1107
|
+
|
|
1108
|
+
- name: "Delete lesser pre-release channels (cascade)"
|
|
1109
|
+
if: steps.eligibility.outputs.publish == 'true'
|
|
1110
|
+
continue-on-error: true
|
|
1111
|
+
run: |
|
|
1112
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
1113
|
+
TOKEN="${MOKO_TOKEN}"
|
|
1114
|
+
|
|
1115
|
+
php ${MOKO_CLI}/release_cascade.php \
|
|
1116
|
+
--stability "${{ steps.meta.outputs.stability }}" \
|
|
1117
|
+
--token "${TOKEN}" \
|
|
1118
|
+
--api-base "${API_BASE}"
|
|
1119
|
+
|
|
1120
|
+
- name: Summary
|
|
1121
|
+
if: always()
|
|
1122
|
+
run: |
|
|
1123
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
1124
|
+
STABILITY="${{ steps.meta.outputs.stability }}"
|
|
1125
|
+
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
|
1126
|
+
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
|
1127
|
+
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
|
1128
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1129
|
+
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
|
1130
|
+
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
|
1131
|
+
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
|
1132
|
+
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
|
1133
|
+
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
|
1134
|
+
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|