@mokoconsulting/mcp-mokogitea-api 1.2.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.
- package/.gitattributes +94 -0
- package/.gitmessage +9 -0
- package/.mokogitea/ISSUE_TEMPLATE/adr.md +110 -0
- package/.mokogitea/ISSUE_TEMPLATE/bug_report.md +48 -0
- package/.mokogitea/ISSUE_TEMPLATE/config.yml +18 -0
- package/.mokogitea/ISSUE_TEMPLATE/documentation.md +52 -0
- package/.mokogitea/ISSUE_TEMPLATE/enterprise_support.md +85 -0
- package/.mokogitea/ISSUE_TEMPLATE/feature_request.md +51 -0
- package/.mokogitea/ISSUE_TEMPLATE/firewall-request.md +190 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +67 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_tool_request.md +49 -0
- package/.mokogitea/ISSUE_TEMPLATE/question.md +82 -0
- package/.mokogitea/ISSUE_TEMPLATE/rfc.md +126 -0
- package/.mokogitea/ISSUE_TEMPLATE/security.md +51 -0
- package/.mokogitea/ISSUE_TEMPLATE/version.md +24 -0
- package/.mokogitea/auto-assign.yml +76 -0
- package/.mokogitea/auto-dev-issue.yml +207 -0
- package/.mokogitea/auto-release.yml +337 -0
- package/.mokogitea/branch-protection.yml +251 -0
- package/.mokogitea/changelog-validation.yml +101 -0
- package/.mokogitea/codeql-analysis.yml +115 -0
- package/.mokogitea/copilot-agent.yml +44 -0
- package/.mokogitea/deploy-demo.yml +734 -0
- package/.mokogitea/deploy-dev.yml +700 -0
- package/.mokogitea/enterprise-firewall-setup.yml +758 -0
- package/.mokogitea/manifest.xml +25 -0
- package/.mokogitea/mcp-auto-release.yml +278 -0
- package/.mokogitea/mcp-build-test.yml +65 -0
- package/.mokogitea/mcp-sdk-check.yml +109 -0
- package/.mokogitea/mcp-tool-inventory.yml +61 -0
- package/.mokogitea/pr-branch-check.yml +90 -0
- package/.mokogitea/repository-cleanup.yml +525 -0
- package/.mokogitea/standards-compliance.yml +2614 -0
- package/.mokogitea/sync-version-on-merge.yml +133 -0
- package/.mokogitea/workflows/auto-assign.yml +76 -0
- package/.mokogitea/workflows/auto-bump.yml +66 -0
- package/.mokogitea/workflows/auto-dev-issue.yml +207 -0
- package/.mokogitea/workflows/auto-release.yml +341 -0
- package/.mokogitea/workflows/branch-cleanup.yml +48 -0
- package/.mokogitea/workflows/cascade-dev.yml +10 -0
- package/.mokogitea/workflows/changelog-validation.yml +101 -0
- package/.mokogitea/workflows/ci-generic.yml +204 -0
- package/.mokogitea/workflows/cleanup.yml +87 -0
- package/.mokogitea/workflows/codeql-analysis.yml +115 -0
- package/.mokogitea/workflows/copilot-agent.yml +44 -0
- package/.mokogitea/workflows/deploy-manual.yml +126 -0
- package/.mokogitea/workflows/enterprise-firewall-setup.yml +758 -0
- package/.mokogitea/workflows/gitleaks.yml +96 -0
- package/.mokogitea/workflows/issue-branch.yml +73 -0
- package/.mokogitea/workflows/mcp-auto-release.yml +280 -0
- package/.mokogitea/workflows/mcp-build-test.yml +65 -0
- package/.mokogitea/workflows/mcp-sdk-check.yml +109 -0
- package/.mokogitea/workflows/mcp-tool-inventory.yml +61 -0
- package/.mokogitea/workflows/notify.yml +70 -0
- package/.mokogitea/workflows/npm-publish.yml +51 -0
- package/.mokogitea/workflows/pr-check.yml +508 -0
- package/.mokogitea/workflows/pre-release.yml +11 -0
- package/.mokogitea/workflows/repo-health.yml +711 -0
- package/.mokogitea/workflows/repository-cleanup.yml +525 -0
- package/.mokogitea/workflows/security-audit.yml +82 -0
- package/.mokogitea/workflows/standards-compliance.yml +2614 -0
- package/.mokogitea/workflows/sync-version-on-merge.yml +130 -0
- package/.mokogitea/workflows/update-server.yml +312 -0
- package/CHANGELOG.md +145 -0
- package/CLAUDE.md +43 -0
- package/CONTRIBUTING.md +161 -0
- package/README.md +286 -0
- package/SECURITY.md +91 -0
- package/automation/ci-issue-reporter.sh +237 -0
- package/config.example.json +13 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.js +104 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +48 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1119 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +16 -0
- package/package.json +34 -0
- package/scripts/setup.mjs +40 -0
- package/src/client.ts +120 -0
- package/src/config.ts +58 -0
- package/src/index.ts +1712 -0
- package/src/types.ts +37 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
#
|
|
3
|
+
# This file is part of a Moko Consulting project.
|
|
4
|
+
#
|
|
5
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
6
|
+
#
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
#
|
|
20
|
+
# FILE INFORMATION
|
|
21
|
+
# DEFGROUP: GitHub.Workflow
|
|
22
|
+
# INGROUP: MokoStandards.Deploy
|
|
23
|
+
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
|
24
|
+
# PATH: /templates/workflows/shared/deploy-dev.yml.template
|
|
25
|
+
# VERSION: 04.06.00
|
|
26
|
+
# BRIEF: SFTP deployment workflow for development server — synced to all governed repos
|
|
27
|
+
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-dev.yml in all governed repos.
|
|
28
|
+
# Port is resolved in order: DEV_FTP_PORT variable → :port suffix in DEV_FTP_HOST → 22.
|
|
29
|
+
|
|
30
|
+
name: Deploy to Dev Server (SFTP)
|
|
31
|
+
|
|
32
|
+
# Deploys the contents of the src/ directory to the development server via SFTP.
|
|
33
|
+
# Triggers on every pull_request to development branches (so the dev server always
|
|
34
|
+
# reflects the latest PR state) and on push/merge to main branches.
|
|
35
|
+
#
|
|
36
|
+
# Required org-level variables: DEV_FTP_HOST, DEV_FTP_PATH, DEV_FTP_USERNAME
|
|
37
|
+
# Optional org-level variable: DEV_FTP_PORT (auto-detected from host or defaults to 22)
|
|
38
|
+
# Optional org/repo variable: DEV_FTP_SUFFIX — when set, appended to DEV_FTP_PATH to form the
|
|
39
|
+
# full remote destination: DEV_FTP_PATH/DEV_FTP_SUFFIX
|
|
40
|
+
# Ignore rules: Place a .ftpignore file in the src/ directory. Each non-empty,
|
|
41
|
+
# non-comment line is a glob pattern tested against the relative path
|
|
42
|
+
# of each file (e.g. "subdir/file.txt"). The .gitignore is NOT used.
|
|
43
|
+
# Required org-level secret: DEV_FTP_KEY (preferred) or DEV_FTP_PASSWORD
|
|
44
|
+
#
|
|
45
|
+
# Access control: only users with admin or maintain role on the repository may deploy.
|
|
46
|
+
|
|
47
|
+
on:
|
|
48
|
+
push:
|
|
49
|
+
branches:
|
|
50
|
+
- 'dev/**'
|
|
51
|
+
- 'rc/**'
|
|
52
|
+
- develop
|
|
53
|
+
- development
|
|
54
|
+
paths:
|
|
55
|
+
- 'src/**'
|
|
56
|
+
- 'htdocs/**'
|
|
57
|
+
pull_request:
|
|
58
|
+
types: [opened, synchronize, reopened, closed]
|
|
59
|
+
branches:
|
|
60
|
+
- 'dev/**'
|
|
61
|
+
- 'rc/**'
|
|
62
|
+
- develop
|
|
63
|
+
- development
|
|
64
|
+
paths:
|
|
65
|
+
- 'src/**'
|
|
66
|
+
- 'htdocs/**'
|
|
67
|
+
workflow_dispatch:
|
|
68
|
+
inputs:
|
|
69
|
+
clear_remote:
|
|
70
|
+
description: 'Delete all files inside the remote destination folder before uploading'
|
|
71
|
+
required: false
|
|
72
|
+
default: false
|
|
73
|
+
type: boolean
|
|
74
|
+
|
|
75
|
+
permissions:
|
|
76
|
+
contents: read
|
|
77
|
+
pull-requests: write
|
|
78
|
+
|
|
79
|
+
env:
|
|
80
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
81
|
+
|
|
82
|
+
jobs:
|
|
83
|
+
check-permission:
|
|
84
|
+
name: Verify Deployment Permission
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- name: Check actor permission
|
|
88
|
+
env:
|
|
89
|
+
# Prefer the org-scoped GH_TOKEN secret (needed for the org membership
|
|
90
|
+
# fallback). Falls back to the built-in github.token so the collaborator
|
|
91
|
+
# endpoint still works even if GH_TOKEN is not configured.
|
|
92
|
+
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
|
93
|
+
run: |
|
|
94
|
+
ACTOR="${{ github.actor }}"
|
|
95
|
+
REPO="${{ github.repository }}"
|
|
96
|
+
ORG="${{ github.repository_owner }}"
|
|
97
|
+
|
|
98
|
+
METHOD=""
|
|
99
|
+
AUTHORIZED="false"
|
|
100
|
+
|
|
101
|
+
# Hardcoded authorized users — always allowed to deploy
|
|
102
|
+
AUTHORIZED_USERS="jmiller github-actions[bot]"
|
|
103
|
+
for user in $AUTHORIZED_USERS; do
|
|
104
|
+
if [ "$ACTOR" = "$user" ]; then
|
|
105
|
+
AUTHORIZED="true"
|
|
106
|
+
METHOD="hardcoded allowlist"
|
|
107
|
+
PERMISSION="admin"
|
|
108
|
+
break
|
|
109
|
+
fi
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
# For other actors, check repo/org permissions via API
|
|
113
|
+
if [ "$AUTHORIZED" != "true" ]; then
|
|
114
|
+
PERMISSION=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" \
|
|
115
|
+
--jq '.permission' 2>/dev/null)
|
|
116
|
+
METHOD="repo collaborator API"
|
|
117
|
+
|
|
118
|
+
if [ -z "$PERMISSION" ]; then
|
|
119
|
+
ORG_ROLE=$(gh api "orgs/${ORG}/memberships/${ACTOR}" \
|
|
120
|
+
--jq '.role' 2>/dev/null)
|
|
121
|
+
METHOD="org membership API"
|
|
122
|
+
if [ "$ORG_ROLE" = "owner" ]; then
|
|
123
|
+
PERMISSION="admin"
|
|
124
|
+
else
|
|
125
|
+
PERMISSION="none"
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
case "$PERMISSION" in
|
|
130
|
+
admin|maintain) AUTHORIZED="true" ;;
|
|
131
|
+
esac
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# Write detailed summary
|
|
135
|
+
{
|
|
136
|
+
echo "## 🔐 Deploy Authorization"
|
|
137
|
+
echo ""
|
|
138
|
+
echo "| Field | Value |"
|
|
139
|
+
echo "|-------|-------|"
|
|
140
|
+
echo "| **Actor** | \`${ACTOR}\` |"
|
|
141
|
+
echo "| **Repository** | \`${REPO}\` |"
|
|
142
|
+
echo "| **Permission** | \`${PERMISSION}\` |"
|
|
143
|
+
echo "| **Method** | ${METHOD} |"
|
|
144
|
+
echo "| **Authorized** | ${AUTHORIZED} |"
|
|
145
|
+
echo "| **Trigger** | \`${{ github.event_name }}\` |"
|
|
146
|
+
echo "| **Branch** | \`${{ github.ref_name }}\` |"
|
|
147
|
+
echo ""
|
|
148
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
149
|
+
|
|
150
|
+
if [ "$AUTHORIZED" = "true" ]; then
|
|
151
|
+
echo "✅ ${ACTOR} authorized to deploy (${METHOD})" >> "$GITHUB_STEP_SUMMARY"
|
|
152
|
+
else
|
|
153
|
+
echo "❌ ${ACTOR} is NOT authorized to deploy." >> "$GITHUB_STEP_SUMMARY"
|
|
154
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
155
|
+
echo "Deployment requires one of:" >> "$GITHUB_STEP_SUMMARY"
|
|
156
|
+
echo "- Being in the hardcoded allowlist" >> "$GITHUB_STEP_SUMMARY"
|
|
157
|
+
echo "- Having \`admin\` or \`maintain\` role on the repository" >> "$GITHUB_STEP_SUMMARY"
|
|
158
|
+
exit 1
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
deploy:
|
|
162
|
+
name: SFTP Deploy → Dev
|
|
163
|
+
runs-on: ubuntu-latest
|
|
164
|
+
needs: [check-permission]
|
|
165
|
+
if: >-
|
|
166
|
+
!startsWith(github.head_ref || github.ref_name, 'chore/') &&
|
|
167
|
+
(github.event_name == 'workflow_dispatch' ||
|
|
168
|
+
github.event_name == 'push' ||
|
|
169
|
+
(github.event_name == 'pull_request' &&
|
|
170
|
+
(github.event.action == 'opened' ||
|
|
171
|
+
github.event.action == 'synchronize' ||
|
|
172
|
+
github.event.action == 'reopened' ||
|
|
173
|
+
github.event.pull_request.merged == true)))
|
|
174
|
+
|
|
175
|
+
steps:
|
|
176
|
+
- name: Checkout repository
|
|
177
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
178
|
+
|
|
179
|
+
- name: Resolve source directory
|
|
180
|
+
id: source
|
|
181
|
+
run: |
|
|
182
|
+
# Resolve source directory: src/ preferred, htdocs/ as fallback
|
|
183
|
+
if [ -d "src" ]; then
|
|
184
|
+
SRC="src"
|
|
185
|
+
elif [ -d "htdocs" ]; then
|
|
186
|
+
SRC="htdocs"
|
|
187
|
+
else
|
|
188
|
+
echo "⚠️ No src/ or htdocs/ directory found — skipping deployment"
|
|
189
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
190
|
+
exit 0
|
|
191
|
+
fi
|
|
192
|
+
COUNT=$(find "$SRC" -type f | wc -l)
|
|
193
|
+
echo "✅ Source: ${SRC}/ (${COUNT} file(s))"
|
|
194
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
195
|
+
echo "dir=${SRC}" >> "$GITHUB_OUTPUT"
|
|
196
|
+
|
|
197
|
+
- name: Preview files to deploy
|
|
198
|
+
if: steps.source.outputs.skip == 'false'
|
|
199
|
+
env:
|
|
200
|
+
SOURCE_DIR: ${{ steps.source.outputs.dir }}
|
|
201
|
+
run: |
|
|
202
|
+
# ── Convert a ftpignore-style glob line to an ERE pattern ──────────────
|
|
203
|
+
ftpignore_to_regex() {
|
|
204
|
+
local line="$1"
|
|
205
|
+
local anchored=false
|
|
206
|
+
# Strip inline comments and whitespace
|
|
207
|
+
line=$(printf '%s' "$line" | sed 's/[[:space:]]*#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
208
|
+
[ -z "$line" ] && return
|
|
209
|
+
# Skip negation patterns (not supported)
|
|
210
|
+
[[ "$line" == !* ]] && return
|
|
211
|
+
# Trailing slash = directory marker; strip it
|
|
212
|
+
line="${line%/}"
|
|
213
|
+
# Leading slash = anchored to root; strip it
|
|
214
|
+
if [[ "$line" == /* ]]; then
|
|
215
|
+
anchored=true
|
|
216
|
+
line="${line#/}"
|
|
217
|
+
fi
|
|
218
|
+
# Escape ERE special chars, then restore glob semantics
|
|
219
|
+
local regex
|
|
220
|
+
regex=$(printf '%s' "$line" \
|
|
221
|
+
| sed 's/[.+^${}()|[\\]/\\&/g' \
|
|
222
|
+
| sed 's/\\\*\\\*/\x01/g' \
|
|
223
|
+
| sed 's/\\\*/[^\/]*/g' \
|
|
224
|
+
| sed 's/\x01/.*/g' \
|
|
225
|
+
| sed 's/\\\?/[^\/]/g')
|
|
226
|
+
if $anchored; then
|
|
227
|
+
printf '^%s(/|$)' "$regex"
|
|
228
|
+
else
|
|
229
|
+
printf '(^|/)%s(/|$)' "$regex"
|
|
230
|
+
fi
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# ── Read .ftpignore (ftpignore-style globs) ─────────────────────────
|
|
234
|
+
IGNORE_PATTERNS=()
|
|
235
|
+
IGNORE_SOURCES=()
|
|
236
|
+
if [ -f "${SOURCE_DIR}/.ftpignore" ]; then
|
|
237
|
+
while IFS= read -r line; do
|
|
238
|
+
[[ "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]] && continue
|
|
239
|
+
regex=$(ftpignore_to_regex "$line")
|
|
240
|
+
[ -n "$regex" ] && IGNORE_PATTERNS+=("$regex") && IGNORE_SOURCES+=("$line")
|
|
241
|
+
done < "${SOURCE_DIR}/.ftpignore"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# ── Walk src/ and classify every file ────────────────────────────────
|
|
245
|
+
WILL_UPLOAD=()
|
|
246
|
+
IGNORED_FILES=()
|
|
247
|
+
while IFS= read -r -d '' file; do
|
|
248
|
+
rel="${file#${SOURCE_DIR}/}"
|
|
249
|
+
SKIP=false
|
|
250
|
+
for i in "${!IGNORE_PATTERNS[@]}"; do
|
|
251
|
+
if echo "$rel" | grep -qE "${IGNORE_PATTERNS[$i]}" 2>/dev/null; then
|
|
252
|
+
IGNORED_FILES+=("$rel | .ftpignore \`${IGNORE_SOURCES[$i]}\`")
|
|
253
|
+
SKIP=true; break
|
|
254
|
+
fi
|
|
255
|
+
done
|
|
256
|
+
$SKIP && continue
|
|
257
|
+
WILL_UPLOAD+=("$rel")
|
|
258
|
+
done < <(find "$SOURCE_DIR" -type f -print0 | sort -z)
|
|
259
|
+
|
|
260
|
+
UPLOAD_COUNT="${#WILL_UPLOAD[@]}"
|
|
261
|
+
IGNORE_COUNT="${#IGNORED_FILES[@]}"
|
|
262
|
+
|
|
263
|
+
echo "ℹ️ ${UPLOAD_COUNT} file(s) will be uploaded, ${IGNORE_COUNT} ignored"
|
|
264
|
+
|
|
265
|
+
# ── Write deployment preview to step summary ──────────────────────────
|
|
266
|
+
{
|
|
267
|
+
echo "## 📋 Deployment Preview"
|
|
268
|
+
echo ""
|
|
269
|
+
echo "| Field | Value |"
|
|
270
|
+
echo "|---|---|"
|
|
271
|
+
echo "| Source | \`${SOURCE_DIR}/\` |"
|
|
272
|
+
echo "| Files to upload | **${UPLOAD_COUNT}** |"
|
|
273
|
+
echo "| Files ignored | **${IGNORE_COUNT}** |"
|
|
274
|
+
echo ""
|
|
275
|
+
if [ "${UPLOAD_COUNT}" -gt 0 ]; then
|
|
276
|
+
echo "### 📂 Files that will be uploaded"
|
|
277
|
+
echo '```'
|
|
278
|
+
printf '%s\n' "${WILL_UPLOAD[@]}"
|
|
279
|
+
echo '```'
|
|
280
|
+
echo ""
|
|
281
|
+
fi
|
|
282
|
+
if [ "${IGNORE_COUNT}" -gt 0 ]; then
|
|
283
|
+
echo "### ⏭️ Files excluded"
|
|
284
|
+
echo "| File | Reason |"
|
|
285
|
+
echo "|---|---|"
|
|
286
|
+
for entry in "${IGNORED_FILES[@]}"; do
|
|
287
|
+
f="${entry% | *}"; r="${entry##* | }"
|
|
288
|
+
echo "| \`${f}\` | ${r} |"
|
|
289
|
+
done
|
|
290
|
+
echo ""
|
|
291
|
+
fi
|
|
292
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
293
|
+
|
|
294
|
+
- name: Resolve SFTP host and port
|
|
295
|
+
if: steps.source.outputs.skip == 'false'
|
|
296
|
+
id: conn
|
|
297
|
+
env:
|
|
298
|
+
HOST_RAW: ${{ vars.DEV_FTP_HOST }}
|
|
299
|
+
PORT_VAR: ${{ vars.DEV_FTP_PORT }}
|
|
300
|
+
run: |
|
|
301
|
+
HOST="$HOST_RAW"
|
|
302
|
+
PORT="$PORT_VAR"
|
|
303
|
+
|
|
304
|
+
# Priority 1 — explicit DEV_FTP_PORT variable
|
|
305
|
+
if [ -n "$PORT" ]; then
|
|
306
|
+
echo "ℹ️ Using explicit DEV_FTP_PORT=${PORT}"
|
|
307
|
+
|
|
308
|
+
# Priority 2 — port embedded in DEV_FTP_HOST (host:port)
|
|
309
|
+
elif [[ "$HOST" == *:* ]]; then
|
|
310
|
+
PORT="${HOST##*:}"
|
|
311
|
+
HOST="${HOST%:*}"
|
|
312
|
+
echo "ℹ️ Extracted port ${PORT} from DEV_FTP_HOST"
|
|
313
|
+
|
|
314
|
+
# Priority 3 — SFTP default
|
|
315
|
+
else
|
|
316
|
+
PORT="22"
|
|
317
|
+
echo "ℹ️ No port specified — defaulting to SFTP port 22"
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
echo "host=${HOST}" >> "$GITHUB_OUTPUT"
|
|
321
|
+
echo "port=${PORT}" >> "$GITHUB_OUTPUT"
|
|
322
|
+
echo "SFTP target: ${HOST}:${PORT}"
|
|
323
|
+
|
|
324
|
+
- name: Build remote path
|
|
325
|
+
if: steps.source.outputs.skip == 'false'
|
|
326
|
+
id: remote
|
|
327
|
+
env:
|
|
328
|
+
DEV_FTP_PATH: ${{ vars.DEV_FTP_PATH }}
|
|
329
|
+
DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
|
330
|
+
run: |
|
|
331
|
+
BASE="$DEV_FTP_PATH"
|
|
332
|
+
|
|
333
|
+
if [ -z "$BASE" ]; then
|
|
334
|
+
echo "❌ DEV_FTP_PATH is not set."
|
|
335
|
+
echo " Configure it as an org-level variable (Settings → Variables) and"
|
|
336
|
+
echo " ensure this repository has been granted access to it."
|
|
337
|
+
exit 1
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
# DEV_FTP_SUFFIX is required — it identifies the remote subdirectory for this repo.
|
|
341
|
+
# Without it we cannot safely determine the deployment target.
|
|
342
|
+
if [ -z "$DEV_FTP_SUFFIX" ]; then
|
|
343
|
+
echo "⏭️ DEV_FTP_SUFFIX variable is not set — skipping deployment."
|
|
344
|
+
echo " Set DEV_FTP_SUFFIX as a repo or org variable to enable deploy-dev."
|
|
345
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
346
|
+
echo "path=" >> "$GITHUB_OUTPUT"
|
|
347
|
+
exit 0
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
REMOTE="${BASE%/}/${DEV_FTP_SUFFIX#/}"
|
|
351
|
+
|
|
352
|
+
# ── Platform-specific path safety guards ──────────────────────────────
|
|
353
|
+
PLATFORM=""
|
|
354
|
+
MOKO_FILE=".github/.mokostandards"; [ ! -f "$MOKO_FILE" ] && MOKO_FILE=".mokostandards"; if [ -f "$MOKO_FILE" ]; then
|
|
355
|
+
PLATFORM=$(grep -oP '^platform:.*' "$MOKO_FILE" 2>/dev/null || true)
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
if [ "$PLATFORM" = "crm-module" ]; then
|
|
359
|
+
# Dolibarr modules must deploy under htdocs/custom/ — guard against
|
|
360
|
+
# accidentally overwriting server root or unrelated directories.
|
|
361
|
+
if [[ "$REMOTE" != *custom* ]]; then
|
|
362
|
+
echo "❌ Safety check failed: Dolibarr (crm-module) remote path must contain 'custom'."
|
|
363
|
+
echo " Current path: ${REMOTE}"
|
|
364
|
+
echo " Set DEV_FTP_SUFFIX to the module's htdocs/custom/ subdirectory."
|
|
365
|
+
exit 1
|
|
366
|
+
fi
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
if [ "$PLATFORM" = "waas-component" ]; then
|
|
370
|
+
# Joomla extensions may only deploy to the server's tmp/ directory.
|
|
371
|
+
if [[ "$REMOTE" != *tmp* ]]; then
|
|
372
|
+
echo "❌ Safety check failed: Joomla (waas-component) remote path must contain 'tmp'."
|
|
373
|
+
echo " Current path: ${REMOTE}"
|
|
374
|
+
echo " Set DEV_FTP_SUFFIX to a path under the server tmp/ directory."
|
|
375
|
+
exit 1
|
|
376
|
+
fi
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
echo "ℹ️ Remote path: ${REMOTE}"
|
|
380
|
+
echo "path=${REMOTE}" >> "$GITHUB_OUTPUT"
|
|
381
|
+
|
|
382
|
+
- name: Detect SFTP authentication method
|
|
383
|
+
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true'
|
|
384
|
+
id: auth
|
|
385
|
+
env:
|
|
386
|
+
HAS_KEY: ${{ secrets.DEV_FTP_KEY }}
|
|
387
|
+
HAS_PASSWORD: ${{ secrets.DEV_FTP_PASSWORD }}
|
|
388
|
+
run: |
|
|
389
|
+
if [ -n "$HAS_KEY" ] && [ -n "$HAS_PASSWORD" ]; then
|
|
390
|
+
# Both set: key auth with password as passphrase; falls back to password-only if key fails
|
|
391
|
+
echo "method=key" >> "$GITHUB_OUTPUT"
|
|
392
|
+
echo "use_passphrase=true" >> "$GITHUB_OUTPUT"
|
|
393
|
+
echo "has_password=true" >> "$GITHUB_OUTPUT"
|
|
394
|
+
echo "ℹ️ Primary: SSH key + passphrase (DEV_FTP_KEY / DEV_FTP_PASSWORD)"
|
|
395
|
+
echo "ℹ️ Fallback: password-only auth if key authentication fails"
|
|
396
|
+
elif [ -n "$HAS_KEY" ]; then
|
|
397
|
+
# Key only: no passphrase, no password fallback
|
|
398
|
+
echo "method=key" >> "$GITHUB_OUTPUT"
|
|
399
|
+
echo "use_passphrase=false" >> "$GITHUB_OUTPUT"
|
|
400
|
+
echo "has_password=false" >> "$GITHUB_OUTPUT"
|
|
401
|
+
echo "ℹ️ Using SSH key authentication (DEV_FTP_KEY, no passphrase, no fallback)"
|
|
402
|
+
elif [ -n "$HAS_PASSWORD" ]; then
|
|
403
|
+
# Password only: direct SFTP password auth
|
|
404
|
+
echo "method=password" >> "$GITHUB_OUTPUT"
|
|
405
|
+
echo "use_passphrase=false" >> "$GITHUB_OUTPUT"
|
|
406
|
+
echo "has_password=true" >> "$GITHUB_OUTPUT"
|
|
407
|
+
echo "ℹ️ Using password authentication (DEV_FTP_PASSWORD)"
|
|
408
|
+
else
|
|
409
|
+
echo "❌ No SFTP credentials configured."
|
|
410
|
+
echo " Set DEV_FTP_KEY (preferred) or DEV_FTP_PASSWORD as an org-level secret."
|
|
411
|
+
exit 1
|
|
412
|
+
fi
|
|
413
|
+
|
|
414
|
+
- name: Setup PHP
|
|
415
|
+
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true'
|
|
416
|
+
uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0
|
|
417
|
+
with:
|
|
418
|
+
php-version: '8.1'
|
|
419
|
+
tools: composer
|
|
420
|
+
|
|
421
|
+
- name: Setup MokoStandards deploy tools
|
|
422
|
+
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true'
|
|
423
|
+
env:
|
|
424
|
+
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
|
425
|
+
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
|
426
|
+
run: |
|
|
427
|
+
git clone --depth 1 --branch version/04 --quiet \
|
|
428
|
+
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
|
429
|
+
/tmp/mokostandards
|
|
430
|
+
cd /tmp/mokostandards
|
|
431
|
+
composer install --no-dev --no-interaction --quiet
|
|
432
|
+
|
|
433
|
+
- name: Clear remote destination folder (manual only)
|
|
434
|
+
if: >-
|
|
435
|
+
steps.source.outputs.skip == 'false' &&
|
|
436
|
+
steps.remote.outputs.skip != 'true' &&
|
|
437
|
+
inputs.clear_remote == true
|
|
438
|
+
env:
|
|
439
|
+
SFTP_HOST: ${{ steps.conn.outputs.host }}
|
|
440
|
+
SFTP_PORT: ${{ steps.conn.outputs.port }}
|
|
441
|
+
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
|
442
|
+
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
|
443
|
+
SFTP_PASSWORD: ${{ secrets.DEV_FTP_PASSWORD }}
|
|
444
|
+
AUTH_METHOD: ${{ steps.auth.outputs.method }}
|
|
445
|
+
USE_PASSPHRASE: ${{ steps.auth.outputs.use_passphrase }}
|
|
446
|
+
HAS_PASSWORD: ${{ steps.auth.outputs.has_password }}
|
|
447
|
+
REMOTE_PATH: ${{ steps.remote.outputs.path }}
|
|
448
|
+
run: |
|
|
449
|
+
cat > /tmp/moko_clear.php << 'PHPEOF'
|
|
450
|
+
<?php
|
|
451
|
+
declare(strict_types=1);
|
|
452
|
+
require '/tmp/mokostandards/vendor/autoload.php';
|
|
453
|
+
|
|
454
|
+
use phpseclib3\Net\SFTP;
|
|
455
|
+
use phpseclib3\Crypt\PublicKeyLoader;
|
|
456
|
+
|
|
457
|
+
$host = (string) getenv('SFTP_HOST');
|
|
458
|
+
$port = (int) getenv('SFTP_PORT');
|
|
459
|
+
$username = (string) getenv('SFTP_USER');
|
|
460
|
+
$authMethod = (string) getenv('AUTH_METHOD');
|
|
461
|
+
$usePassphrase = getenv('USE_PASSPHRASE') === 'true';
|
|
462
|
+
$hasPassword = getenv('HAS_PASSWORD') === 'true';
|
|
463
|
+
$remotePath = rtrim((string) getenv('REMOTE_PATH'), '/');
|
|
464
|
+
|
|
465
|
+
echo "⚠️ Clearing remote folder: {$remotePath}\n";
|
|
466
|
+
|
|
467
|
+
$sftp = new SFTP($host, $port);
|
|
468
|
+
|
|
469
|
+
// ── Authentication ──────────────────────────────────────────────
|
|
470
|
+
if ($authMethod === 'key') {
|
|
471
|
+
$keyData = (string) getenv('SFTP_KEY');
|
|
472
|
+
$passphrase = $usePassphrase ? (string) getenv('SFTP_PASSWORD') : false;
|
|
473
|
+
$password = $hasPassword ? (string) getenv('SFTP_PASSWORD') : '';
|
|
474
|
+
$key = PublicKeyLoader::load($keyData, $passphrase);
|
|
475
|
+
if (!$sftp->login($username, $key)) {
|
|
476
|
+
if ($password !== '') {
|
|
477
|
+
echo "⚠️ Key auth failed — falling back to password\n";
|
|
478
|
+
if (!$sftp->login($username, $password)) {
|
|
479
|
+
fwrite(STDERR, "❌ Both key and password authentication failed\n");
|
|
480
|
+
exit(1);
|
|
481
|
+
}
|
|
482
|
+
echo "✅ Connected via password authentication (key fallback)\n";
|
|
483
|
+
} else {
|
|
484
|
+
fwrite(STDERR, "❌ Key authentication failed and no password fallback is available\n");
|
|
485
|
+
exit(1);
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
echo "✅ Connected via SSH key authentication\n";
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
if (!$sftp->login($username, (string) getenv('SFTP_PASSWORD'))) {
|
|
492
|
+
fwrite(STDERR, "❌ Password authentication failed\n");
|
|
493
|
+
exit(1);
|
|
494
|
+
}
|
|
495
|
+
echo "✅ Connected via password authentication\n";
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ── Recursive delete ────────────────────────────────────────────
|
|
499
|
+
function rmrf(SFTP $sftp, string $path): void
|
|
500
|
+
{
|
|
501
|
+
$entries = $sftp->nlist($path);
|
|
502
|
+
if ($entries === false) {
|
|
503
|
+
return; // path does not exist — nothing to clear
|
|
504
|
+
}
|
|
505
|
+
foreach ($entries as $name) {
|
|
506
|
+
if ($name === '.' || $name === '..') {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
$entry = "{$path}/{$name}";
|
|
510
|
+
if ($sftp->is_dir($entry)) {
|
|
511
|
+
rmrf($sftp, $entry);
|
|
512
|
+
$sftp->rmdir($entry);
|
|
513
|
+
echo " 🗑️ Removed dir: {$entry}\n";
|
|
514
|
+
} else {
|
|
515
|
+
$sftp->delete($entry);
|
|
516
|
+
echo " 🗑️ Removed file: {$entry}\n";
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ── Create remote directory tree ────────────────────────────────
|
|
522
|
+
function sftpMakedirs(SFTP $sftp, string $path): void
|
|
523
|
+
{
|
|
524
|
+
$parts = array_values(array_filter(explode('/', $path), fn(string $p) => $p !== ''));
|
|
525
|
+
$current = str_starts_with($path, '/') ? '' : '';
|
|
526
|
+
foreach ($parts as $part) {
|
|
527
|
+
$current .= '/' . $part;
|
|
528
|
+
$sftp->mkdir($current); // silently returns false if already exists
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
rmrf($sftp, $remotePath);
|
|
533
|
+
sftpMakedirs($sftp, $remotePath);
|
|
534
|
+
echo "✅ Remote folder ready: {$remotePath}\n";
|
|
535
|
+
PHPEOF
|
|
536
|
+
php /tmp/moko_clear.php
|
|
537
|
+
|
|
538
|
+
- name: Deploy via SFTP
|
|
539
|
+
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true'
|
|
540
|
+
env:
|
|
541
|
+
SFTP_HOST: ${{ steps.conn.outputs.host }}
|
|
542
|
+
SFTP_PORT: ${{ steps.conn.outputs.port }}
|
|
543
|
+
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
|
544
|
+
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
|
545
|
+
SFTP_PASSWORD: ${{ secrets.DEV_FTP_PASSWORD }}
|
|
546
|
+
AUTH_METHOD: ${{ steps.auth.outputs.method }}
|
|
547
|
+
USE_PASSPHRASE: ${{ steps.auth.outputs.use_passphrase }}
|
|
548
|
+
REMOTE_PATH: ${{ steps.remote.outputs.path }}
|
|
549
|
+
SOURCE_DIR: ${{ steps.source.outputs.dir }}
|
|
550
|
+
run: |
|
|
551
|
+
# ── Write SSH key to temp file (key auth only) ────────────────────────
|
|
552
|
+
if [ "$AUTH_METHOD" = "key" ]; then
|
|
553
|
+
printf '%s' "$SFTP_KEY" > /tmp/deploy_key
|
|
554
|
+
chmod 600 /tmp/deploy_key
|
|
555
|
+
fi
|
|
556
|
+
|
|
557
|
+
# ── Generate sftp-config.json safely via jq ───────────────────────────
|
|
558
|
+
if [ "$AUTH_METHOD" = "key" ]; then
|
|
559
|
+
jq -n \
|
|
560
|
+
--arg host "$SFTP_HOST" \
|
|
561
|
+
--argjson port "${SFTP_PORT:-22}" \
|
|
562
|
+
--arg user "$SFTP_USER" \
|
|
563
|
+
--arg path "$REMOTE_PATH" \
|
|
564
|
+
--arg key "/tmp/deploy_key" \
|
|
565
|
+
'{host:$host, port:$port, user:$user, remote_path:$path, ssh_key_file:$key}' \
|
|
566
|
+
> /tmp/sftp-config.json
|
|
567
|
+
else
|
|
568
|
+
jq -n \
|
|
569
|
+
--arg host "$SFTP_HOST" \
|
|
570
|
+
--argjson port "${SFTP_PORT:-22}" \
|
|
571
|
+
--arg user "$SFTP_USER" \
|
|
572
|
+
--arg path "$REMOTE_PATH" \
|
|
573
|
+
--arg pass "$SFTP_PASSWORD" \
|
|
574
|
+
'{host:$host, port:$port, user:$user, remote_path:$path, password:$pass}' \
|
|
575
|
+
> /tmp/sftp-config.json
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
# Dev deploys skip minified files — use unminified sources for debugging
|
|
579
|
+
echo "*.min.js" >> "${SOURCE_DIR}/.ftpignore"
|
|
580
|
+
echo "*.min.css" >> "${SOURCE_DIR}/.ftpignore"
|
|
581
|
+
|
|
582
|
+
# ── Run deploy-sftp.php from MokoStandards ────────────────────────────
|
|
583
|
+
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
|
|
584
|
+
if [ "$USE_PASSPHRASE" = "true" ]; then
|
|
585
|
+
DEPLOY_ARGS+=(--key-passphrase "$SFTP_PASSWORD")
|
|
586
|
+
fi
|
|
587
|
+
|
|
588
|
+
# Set platform version to "development" before deploy (Dolibarr + Joomla)
|
|
589
|
+
php /tmp/mokostandards/api/cli/version_set_platform.php --path . --version development
|
|
590
|
+
|
|
591
|
+
# Write update files — dev/** = development, rc/** = rc
|
|
592
|
+
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true)
|
|
593
|
+
REPO="${{ github.repository }}"
|
|
594
|
+
BRANCH="${{ github.ref_name }}"
|
|
595
|
+
|
|
596
|
+
# Determine stability tag from branch prefix
|
|
597
|
+
STABILITY="development"
|
|
598
|
+
VERSION_LABEL="development"
|
|
599
|
+
if [[ "$BRANCH" == rc/* ]]; then
|
|
600
|
+
STABILITY="rc"
|
|
601
|
+
VERSION_LABEL=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "${BRANCH#rc/}")-rc
|
|
602
|
+
fi
|
|
603
|
+
|
|
604
|
+
if [ "$PLATFORM" = "crm-module" ]; then
|
|
605
|
+
printf '%s' "$VERSION_LABEL" > update.txt
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
if [ "$PLATFORM" = "waas-component" ]; then
|
|
609
|
+
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
|
610
|
+
if [ -n "$MANIFEST" ]; then
|
|
611
|
+
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
|
|
612
|
+
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
|
|
613
|
+
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
|
|
614
|
+
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
|
|
615
|
+
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
|
|
616
|
+
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/' "$MANIFEST" 2>/dev/null | head -1 || true)
|
|
617
|
+
[ -n "$TARGET_PLATFORM" ] && TARGET_PLATFORM="${TARGET_PLATFORM}>"
|
|
618
|
+
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
|
619
|
+
|
|
620
|
+
CLIENT_TAG=""
|
|
621
|
+
if [ -n "$EXT_CLIENT" ]; then
|
|
622
|
+
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
|
623
|
+
elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
|
|
624
|
+
CLIENT_TAG="<client>site</client>"
|
|
625
|
+
fi
|
|
626
|
+
|
|
627
|
+
FOLDER_TAG=""
|
|
628
|
+
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
|
|
629
|
+
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
|
630
|
+
fi
|
|
631
|
+
|
|
632
|
+
DOWNLOAD_URL="https://github.com/${REPO}/archive/refs/heads/${BRANCH}.zip"
|
|
633
|
+
|
|
634
|
+
{
|
|
635
|
+
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
|
636
|
+
printf '%s\n' '<updates>'
|
|
637
|
+
printf '%s\n' ' <update>'
|
|
638
|
+
printf '%s\n' " <name>${EXT_NAME}</name>"
|
|
639
|
+
printf '%s\n' " <description>${EXT_NAME} ${STABILITY} build</description>"
|
|
640
|
+
printf '%s\n' " <element>${EXT_ELEMENT}</element>"
|
|
641
|
+
printf '%s\n' " <type>${EXT_TYPE}</type>"
|
|
642
|
+
printf '%s\n' " <version>${VERSION_LABEL}</version>"
|
|
643
|
+
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
|
644
|
+
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
|
645
|
+
printf '%s\n' ' <tags>'
|
|
646
|
+
printf '%s\n' " <tag>${STABILITY}</tag>"
|
|
647
|
+
printf '%s\n' ' </tags>'
|
|
648
|
+
printf '%s\n' " <infourl title=\"${EXT_NAME}\">https://github.com/${REPO}/tree/${BRANCH}</infourl>"
|
|
649
|
+
printf '%s\n' ' <downloads>'
|
|
650
|
+
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
|
|
651
|
+
printf '%s\n' ' </downloads>'
|
|
652
|
+
printf '%s\n' " ${TARGET_PLATFORM}"
|
|
653
|
+
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
|
|
654
|
+
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
|
|
655
|
+
printf '%s\n' ' </update>'
|
|
656
|
+
printf '%s\n' '</updates>'
|
|
657
|
+
} > updates.xml
|
|
658
|
+
sed -i '/^[[:space:]]*$/d' updates.xml
|
|
659
|
+
fi
|
|
660
|
+
fi
|
|
661
|
+
|
|
662
|
+
# Use Joomla-aware deploy for waas-component (routes files to correct Joomla dirs)
|
|
663
|
+
# Use standard SFTP deploy for everything else
|
|
664
|
+
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true)
|
|
665
|
+
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards/api/deploy/deploy-joomla.php" ]; then
|
|
666
|
+
php /tmp/mokostandards/api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
|
667
|
+
else
|
|
668
|
+
php /tmp/mokostandards/api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
|
669
|
+
fi
|
|
670
|
+
# (both scripts handle dotfile skipping and .ftpignore natively)
|
|
671
|
+
# Remove temp files that should never be left behind
|
|
672
|
+
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
|
673
|
+
|
|
674
|
+
# Dev deploys fail silently — no issue creation.
|
|
675
|
+
# Demo and RS deploys create failure issues (production-facing).
|
|
676
|
+
|
|
677
|
+
- name: Deployment summary
|
|
678
|
+
if: always()
|
|
679
|
+
run: |
|
|
680
|
+
if [ "${{ steps.source.outputs.skip }}" == "true" ]; then
|
|
681
|
+
echo "### ⏭️ Deployment Skipped" >> "$GITHUB_STEP_SUMMARY"
|
|
682
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
683
|
+
echo "No \`src/\` directory found in this repository." >> "$GITHUB_STEP_SUMMARY"
|
|
684
|
+
elif [ "${{ job.status }}" == "success" ]; then
|
|
685
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
686
|
+
echo "### ✅ Dev Deployment Successful" >> "$GITHUB_STEP_SUMMARY"
|
|
687
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
688
|
+
echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY"
|
|
689
|
+
echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY"
|
|
690
|
+
echo "| Host | \`${{ steps.conn.outputs.host }}:${{ steps.conn.outputs.port }}\` |" >> "$GITHUB_STEP_SUMMARY"
|
|
691
|
+
echo "| Remote path | \`${{ steps.remote.outputs.path }}\` |" >> "$GITHUB_STEP_SUMMARY"
|
|
692
|
+
echo "| Source | \`src/\` |" >> "$GITHUB_STEP_SUMMARY"
|
|
693
|
+
echo "| Trigger | ${{ github.event_name }} |" >> "$GITHUB_STEP_SUMMARY"
|
|
694
|
+
echo "| Auth | ${{ steps.auth.outputs.method }} |" >> "$GITHUB_STEP_SUMMARY"
|
|
695
|
+
echo "| Clear remote | ${{ inputs.clear_remote || 'false' }} |" >> "$GITHUB_STEP_SUMMARY"
|
|
696
|
+
else
|
|
697
|
+
echo "### ❌ Dev Deployment Failed" >> "$GITHUB_STEP_SUMMARY"
|
|
698
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
699
|
+
echo "Check the job log above for error details." >> "$GITHUB_STEP_SUMMARY"
|
|
700
|
+
fi
|