gowalk-cicd 1.0.40 → 1.0.42
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/CLAUDE.md +10 -1
- package/README.md +27 -6
- package/action/.daemux-version +1 -1
- package/android-action/.daemux-version +1 -1
- package/backend-action/.daemux-version +1 -1
- package/backend-action/action.yml +8 -1
- package/backend-action/remote_deploy.sh +37 -7
- package/bin/cli.mjs +4 -2
- package/package.json +1 -1
- package/src/install.mjs +16 -0
- package/templates/deploy-backend.yml +1 -0
- package/templates/deploy-web.yml +58 -0
- package/templates/deploy.yml +1 -0
package/CLAUDE.md
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
npm package that distributes iOS TestFlight
|
|
5
|
+
npm package that distributes iOS TestFlight, Android Google Play, and Flutter
|
|
6
|
+
web release workflows.
|
|
6
7
|
Consumer runs `npx gowalk-cicd` and gets:
|
|
7
8
|
|
|
8
9
|
- `.github/actions/swift-app/` — vendored composite action
|
|
9
10
|
- `.github/actions/android-app/` — vendored Android action (Flutter or Gradle)
|
|
10
11
|
- `.github/workflows/deploy.yml` — dual-platform workflow
|
|
12
|
+
- `.github/workflows/deploy-web.yml` — Flutter web release artifact workflow
|
|
13
|
+
(only when `web/index.html` exists)
|
|
11
14
|
|
|
12
15
|
Re-running the command is both install and update. No scope flag, no
|
|
13
16
|
uninstall. Keep it simple.
|
|
@@ -25,6 +28,7 @@ gowalk-cicd/ ← repo root IS the gowalk-cicd package
|
|
|
25
28
|
├── bin/cli.mjs ← CLI entrypoint (parses flags, calls runInstall)
|
|
26
29
|
├── src/install.mjs ← copy logic, gitignore-check, summary print
|
|
27
30
|
├── templates/deploy.yml ← dual-platform workflow copied verbatim
|
|
31
|
+
├── templates/deploy-web.yml ← Flutter web workflow copied conditionally
|
|
28
32
|
├── backend-action/ ← backend deploy action + remote host script
|
|
29
33
|
├── templates/deploy-backend.yml ← backend workflow copied when compose exists
|
|
30
34
|
├── scripts/auto-version.mjs ← patch-bump-from-npm used by the publish workflow
|
|
@@ -60,6 +64,11 @@ workflow (`.github/workflows/publish.yml`):
|
|
|
60
64
|
attach the `publish.yml` trusted-publisher relationship; the workflow
|
|
61
65
|
supplies that token as a fallback and uses OIDC after trust is configured.
|
|
62
66
|
|
|
67
|
+
An explicitly dispatched run may publish a tested feature ref by setting its
|
|
68
|
+
`release` boolean input. The input defaults to false, so ordinary manual runs
|
|
69
|
+
remain test-only. Use this path when a deploy repair must ship without pushing
|
|
70
|
+
the feature branch directly to `main`.
|
|
71
|
+
|
|
63
72
|
Manual publish (from repo root):
|
|
64
73
|
|
|
65
74
|
```bash
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# gowalk-cicd
|
|
2
2
|
|
|
3
|
-
One installer for iOS TestFlight
|
|
4
|
-
native Swift/SwiftUI iOS projects, native Kotlin/Gradle
|
|
5
|
-
Flutter apps. Put the platform keys under `creds/`, push
|
|
6
|
-
|
|
3
|
+
One installer for iOS TestFlight, Android Google Play, and Flutter web release
|
|
4
|
+
artifacts. It supports native Swift/SwiftUI iOS projects, native Kotlin/Gradle
|
|
5
|
+
Android projects, and Flutter apps. Put the platform keys under `creds/`, push
|
|
6
|
+
to `main`, and the applicable builds run only on GitHub Actions.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -13,14 +13,31 @@ From the root of your app repo:
|
|
|
13
13
|
npx --yes gowalk-cicd
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
Writes three things into your repo:
|
|
16
|
+
Writes three core things into your repo:
|
|
17
17
|
|
|
18
18
|
- `.github/actions/swift-app/` — the vendored composite action (action.yml + scripts)
|
|
19
19
|
- `.github/actions/android-app/` — Android build/sign action (Flutter or Gradle)
|
|
20
20
|
- `.github/workflows/deploy.yml` — workflow that builds and deploys both platforms
|
|
21
21
|
|
|
22
|
+
Flutter projects that contain `web/index.html` also get
|
|
23
|
+
`.github/workflows/deploy-web.yml`. It creates a version-stamped release build
|
|
24
|
+
and retains `build/web` as a 30-day workflow artifact. Native-only repositories
|
|
25
|
+
are unaffected.
|
|
26
|
+
|
|
22
27
|
Re-run the same command anytime to pull the latest version.
|
|
23
28
|
|
|
29
|
+
## Flutter web release
|
|
30
|
+
|
|
31
|
+
The conditional `deploy-web.yml` workflow runs on changes to Flutter source,
|
|
32
|
+
web assets, package metadata, localization config, or the workflow itself. It
|
|
33
|
+
also supports manual dispatch. The build uses `GITHUB_RUN_NUMBER` as its build
|
|
34
|
+
number and uploads the complete `build/web` directory as a release artifact.
|
|
35
|
+
|
|
36
|
+
Private git dependencies use the same optional `GIT_PRIVATE_TOKEN` secret as
|
|
37
|
+
the mobile workflow. No hosting provider is assumed: consumers can deploy the
|
|
38
|
+
artifact to their chosen host without granting this package an unrelated cloud
|
|
39
|
+
account or domain.
|
|
40
|
+
|
|
24
41
|
## iOS credentials
|
|
25
42
|
|
|
26
43
|
1. Create an **App Store Connect API key** with the **App Manager** role
|
|
@@ -51,6 +68,8 @@ The backend workflow runs on a GitHub-hosted runner and, on a push touching
|
|
|
51
68
|
it rsyncs the backend dir to `/opt/gowalk-backends/<app>/`, runs
|
|
52
69
|
`docker compose up -d --build`, auto-detects the published `127.0.0.1:<port>`,
|
|
53
70
|
wires an nginx vhost + Let's Encrypt cert for the API domain, and health-checks.
|
|
71
|
+
Certificate issuance and the public health check are strict: a deploy fails if
|
|
72
|
+
the hostname cannot present a trusted matching certificate and reach the API.
|
|
54
73
|
|
|
55
74
|
Requirements on the consumer repo:
|
|
56
75
|
|
|
@@ -67,7 +86,9 @@ Requirements on the consumer repo:
|
|
|
67
86
|
- Repo **variables** (optional): `BACKEND_API_DOMAIN` (its DNS A record must point
|
|
68
87
|
at the host for the cert; empty = deploy the container only, skip nginx/cert),
|
|
69
88
|
`BACKEND_APP_NAME` (defaults to the repo name), `BACKEND_HEALTH_PATH`
|
|
70
|
-
(default `/health`), `BACKEND_DEPLOY_HOST` (default `138.197.36.107`)
|
|
89
|
+
(default `/health`), `BACKEND_DEPLOY_HOST` (default `138.197.36.107`), and
|
|
90
|
+
`BACKEND_CERTBOT_ACCOUNT` (optional account ID for hosts with multiple ACME
|
|
91
|
+
accounts; otherwise the sole or host-created account is selected).
|
|
71
92
|
|
|
72
93
|
The action directory is canonical here (like the iOS/Android actions): edit it in
|
|
73
94
|
`backend-action/`, never in a consumer's vendored copy.
|
package/action/.daemux-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.42
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.42
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.42
|
|
@@ -39,6 +39,12 @@ inputs:
|
|
|
39
39
|
description: Email for the Let's Encrypt registration.
|
|
40
40
|
required: false
|
|
41
41
|
default: "admin@gowalk.com"
|
|
42
|
+
certbot-account:
|
|
43
|
+
description: >
|
|
44
|
+
Optional Certbot account ID. When omitted, the deploy selects the sole
|
|
45
|
+
account or the account created on the current host.
|
|
46
|
+
required: false
|
|
47
|
+
default: ""
|
|
42
48
|
runtime-env:
|
|
43
49
|
description: >
|
|
44
50
|
Optional newline-delimited KEY=value secrets written to a mode-0600
|
|
@@ -104,7 +110,8 @@ runs:
|
|
|
104
110
|
$SSH "${{ inputs.ssh-user }}@${{ inputs.host }}" \
|
|
105
111
|
"bash '$DEST/.remote_deploy.sh' \
|
|
106
112
|
'${{ inputs.app-name }}' '${{ inputs.api-domain }}' \
|
|
107
|
-
'${{ inputs.health-path }}' '${{ inputs.cert-email }}'
|
|
113
|
+
'${{ inputs.health-path }}' '${{ inputs.cert-email }}' \
|
|
114
|
+
'${{ inputs.certbot-account }}'"
|
|
108
115
|
|
|
109
116
|
- name: Cleanup key
|
|
110
117
|
if: always()
|
|
@@ -8,11 +8,37 @@ APP="$1" # slug — compose project + dir name
|
|
|
8
8
|
DOMAIN="${2:-}" # api.<app>.gowalk.com, or empty to skip nginx/cert
|
|
9
9
|
HEALTH="${3:-/health}"
|
|
10
10
|
CERT_EMAIL="${4:-admin@gowalk.com}"
|
|
11
|
+
CERT_ACCOUNT="${5:-}"
|
|
11
12
|
DIR="/opt/gowalk-backends/$APP"
|
|
12
13
|
cd "$DIR"
|
|
13
14
|
|
|
14
15
|
log() { echo "[deploy $APP] $*"; }
|
|
15
16
|
|
|
17
|
+
select_certbot_account() {
|
|
18
|
+
[ -n "$CERT_ACCOUNT" ] && return
|
|
19
|
+
local accounts_root host account_dir creation_host
|
|
20
|
+
local -a accounts=()
|
|
21
|
+
accounts_root="/etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory"
|
|
22
|
+
[ -d "$accounts_root" ] || return
|
|
23
|
+
host="$(hostname)"
|
|
24
|
+
while IFS= read -r account_dir; do
|
|
25
|
+
accounts+=("$(basename "$account_dir")")
|
|
26
|
+
creation_host="$(sed -nE \
|
|
27
|
+
's/.*"creation_host"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' \
|
|
28
|
+
"$account_dir/meta.json" 2>/dev/null | head -1)"
|
|
29
|
+
if [ "$creation_host" = "$host" ]; then
|
|
30
|
+
CERT_ACCOUNT="$(basename "$account_dir")"
|
|
31
|
+
fi
|
|
32
|
+
done < <(find "$accounts_root" -mindepth 1 -maxdepth 1 -type d | sort)
|
|
33
|
+
if [ -z "$CERT_ACCOUNT" ] && [ "${#accounts[@]}" -eq 1 ]; then
|
|
34
|
+
CERT_ACCOUNT="${accounts[0]}"
|
|
35
|
+
fi
|
|
36
|
+
if [ -z "$CERT_ACCOUNT" ] && [ "${#accounts[@]}" -gt 1 ]; then
|
|
37
|
+
log "ERROR: multiple Certbot accounts; set BACKEND_CERTBOT_ACCOUNT"
|
|
38
|
+
return 1
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
# ── secrets: generate a per-app .env once, then preserve it (rsync excludes it)
|
|
17
43
|
if [ ! -f .env ]; then
|
|
18
44
|
log "first deploy — generating .env (POSTGRES_PASSWORD)"
|
|
@@ -95,16 +121,20 @@ fi
|
|
|
95
121
|
# ── Let's Encrypt cert (idempotent; certbot skips if current)
|
|
96
122
|
if ! certbot certificates 2>/dev/null | grep -q "Domains: $DOMAIN\b"; then
|
|
97
123
|
log "requesting LE cert for $DOMAIN"
|
|
124
|
+
select_certbot_account
|
|
125
|
+
CERTBOT_ARGS=()
|
|
126
|
+
if [ -n "$CERT_ACCOUNT" ]; then
|
|
127
|
+
log "using Certbot account ${CERT_ACCOUNT:0:4}…"
|
|
128
|
+
CERTBOT_ARGS+=(--account "$CERT_ACCOUNT")
|
|
129
|
+
fi
|
|
98
130
|
certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos \
|
|
99
|
-
--email "$CERT_EMAIL" --redirect
|
|
131
|
+
--email "$CERT_EMAIL" --redirect "${CERTBOT_ARGS[@]}"
|
|
100
132
|
else
|
|
101
133
|
log "cert for $DOMAIN already present"
|
|
102
134
|
fi
|
|
103
135
|
|
|
104
|
-
# ── public health-check
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
log "warn: public URL not answering yet (DNS/cert propagation) — container is up on :$PORT"
|
|
109
|
-
fi
|
|
136
|
+
# ── public health-check: validates DNS, certificate/SNI, routing and service.
|
|
137
|
+
curl -fsS --retry 6 --retry-all-errors --retry-delay 2 \
|
|
138
|
+
"https://$DOMAIN$HEALTH" >/dev/null
|
|
139
|
+
log "public https://$DOMAIN$HEALTH healthy with verified TLS"
|
|
110
140
|
log "done"
|
package/bin/cli.mjs
CHANGED
|
@@ -33,13 +33,15 @@ for (const arg of args) {
|
|
|
33
33
|
case '--help':
|
|
34
34
|
console.log(`Usage: npx gowalk-cicd [options]
|
|
35
35
|
|
|
36
|
-
Installs iOS TestFlight and Android Google Play actions plus
|
|
37
|
-
workflow into the current app project.
|
|
36
|
+
Installs iOS TestFlight and Android Google Play actions plus their deploy
|
|
37
|
+
workflow into the current app project. Flutter web apps also get a release
|
|
38
|
+
artifact workflow. Re-run anytime to update them.
|
|
38
39
|
|
|
39
40
|
What it writes:
|
|
40
41
|
.github/actions/swift-app/ - vendored composite action (action.yml + scripts/)
|
|
41
42
|
.github/actions/android-app/ - Flutter Android build/sign action
|
|
42
43
|
.github/workflows/deploy.yml - dual-platform workflow
|
|
44
|
+
.github/workflows/deploy-web.yml - Flutter web release workflow when web/ exists
|
|
43
45
|
|
|
44
46
|
Options:
|
|
45
47
|
--dry-run Print what would happen without writing files
|
package/package.json
CHANGED
package/src/install.mjs
CHANGED
|
@@ -28,6 +28,7 @@ const ACTION_SRC = join(PACKAGE_DIR, 'action');
|
|
|
28
28
|
const ANDROID_ACTION_SRC = join(PACKAGE_DIR, 'android-action');
|
|
29
29
|
const BACKEND_ACTION_SRC = join(PACKAGE_DIR, 'backend-action');
|
|
30
30
|
const WORKFLOW_TEMPLATE = join(PACKAGE_DIR, 'templates', 'deploy.yml');
|
|
31
|
+
const WEB_WORKFLOW_TEMPLATE = join(PACKAGE_DIR, 'templates', 'deploy-web.yml');
|
|
31
32
|
const BACKEND_WORKFLOW_TEMPLATE = join(PACKAGE_DIR, 'templates', 'deploy-backend.yml');
|
|
32
33
|
const PACKAGE_JSON = join(PACKAGE_DIR, 'package.json');
|
|
33
34
|
|
|
@@ -37,6 +38,7 @@ const ACTION_DEST_REL = join('.github', 'actions', 'swift-app');
|
|
|
37
38
|
const ANDROID_ACTION_DEST_REL = join('.github', 'actions', 'android-app');
|
|
38
39
|
const BACKEND_ACTION_DEST_REL = join('.github', 'actions', 'backend-app');
|
|
39
40
|
const WORKFLOW_DEST_REL = join('.github', 'workflows', 'deploy.yml');
|
|
41
|
+
const WEB_WORKFLOW_DEST_REL = join('.github', 'workflows', 'deploy-web.yml');
|
|
40
42
|
const BACKEND_WORKFLOW_DEST_REL = join('.github', 'workflows', 'deploy-backend.yml');
|
|
41
43
|
const VERSION_MARKER_REL = join('.github', 'actions', 'swift-app', '.daemux-version');
|
|
42
44
|
const ANDROID_VERSION_MARKER_REL = join('.github', 'actions', 'android-app', '.daemux-version');
|
|
@@ -219,6 +221,20 @@ export async function runInstall({ dryRun = false } = {}) {
|
|
|
219
221
|
writeVersionMarkers(repoRoot, dryRun);
|
|
220
222
|
copyWorkflow(repoRoot, dryRun);
|
|
221
223
|
|
|
224
|
+
if (existsSync(join(repoRoot, 'pubspec.yaml'))
|
|
225
|
+
&& existsSync(join(repoRoot, 'web', 'index.html'))) {
|
|
226
|
+
console.log(' Flutter web detected (web/index.html) — installing web release workflow');
|
|
227
|
+
const webWorkflowDest = join(repoRoot, WEB_WORKFLOW_DEST_REL);
|
|
228
|
+
const webWorkflowExisted = existsSync(webWorkflowDest);
|
|
229
|
+
if (dryRun) {
|
|
230
|
+
console.log(` [dry-run] ${webWorkflowExisted ? 'overwrite' : 'create'} ${WEB_WORKFLOW_DEST_REL}`);
|
|
231
|
+
} else {
|
|
232
|
+
ensureDir(dirname(webWorkflowDest));
|
|
233
|
+
cpSync(WEB_WORKFLOW_TEMPLATE, webWorkflowDest, { force: true });
|
|
234
|
+
console.log(` ${webWorkflowExisted ? 'Overwrote' : 'Created'} ${WEB_WORKFLOW_DEST_REL}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
222
238
|
// Backend deploy path — installed only when the repo has a Dockerized
|
|
223
239
|
// backend (backend/ or server/ with a docker-compose.yml). Mobile-only
|
|
224
240
|
// repos are unaffected, so this is backward compatible.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Web Release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
paths:
|
|
6
|
+
- 'lib/**'
|
|
7
|
+
- 'web/**'
|
|
8
|
+
- 'assets/**'
|
|
9
|
+
- 'pubspec.yaml'
|
|
10
|
+
- 'pubspec.lock'
|
|
11
|
+
- 'l10n.yaml'
|
|
12
|
+
- 'analysis_options.yaml'
|
|
13
|
+
- '.github/workflows/deploy-web.yml'
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: web-release-${{ github.ref }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
web:
|
|
25
|
+
name: Flutter Web Artifact
|
|
26
|
+
runs-on: ubuntu-24.04
|
|
27
|
+
timeout-minutes: 20
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
30
|
+
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
|
31
|
+
with:
|
|
32
|
+
channel: stable
|
|
33
|
+
cache: true
|
|
34
|
+
- name: Authenticate private git dependencies
|
|
35
|
+
shell: bash
|
|
36
|
+
env:
|
|
37
|
+
GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }}
|
|
38
|
+
run: |
|
|
39
|
+
if [ -z "${GIT_PRIVATE_TOKEN:-}" ]; then
|
|
40
|
+
echo "No GIT_PRIVATE_TOKEN secret; private git dependencies must carry their own credentials."
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
echo "::add-mask::$GIT_PRIVATE_TOKEN"
|
|
44
|
+
git config --global \
|
|
45
|
+
url."https://x-access-token:${GIT_PRIVATE_TOKEN}@github.com/".insteadOf "https://github.com/"
|
|
46
|
+
- name: Build Flutter web release
|
|
47
|
+
shell: bash
|
|
48
|
+
run: |
|
|
49
|
+
flutter pub get
|
|
50
|
+
if [ -f l10n.yaml ]; then flutter gen-l10n; fi
|
|
51
|
+
flutter build web --release --build-number="${GITHUB_RUN_NUMBER}"
|
|
52
|
+
- name: Retain Flutter web release
|
|
53
|
+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
54
|
+
with:
|
|
55
|
+
name: web-${{ github.event.repository.name }}-${{ github.run_number }}
|
|
56
|
+
path: build/web
|
|
57
|
+
if-no-files-found: error
|
|
58
|
+
retention-days: 30
|
package/templates/deploy.yml
CHANGED
|
@@ -428,4 +428,5 @@ jobs:
|
|
|
428
428
|
api-domain: ${{ vars.BACKEND_API_DOMAIN }}
|
|
429
429
|
backend-dir: ${{ steps.dir.outputs.path }}
|
|
430
430
|
health-path: ${{ vars.BACKEND_HEALTH_PATH || '/health' }}
|
|
431
|
+
certbot-account: ${{ vars.BACKEND_CERTBOT_ACCOUNT }}
|
|
431
432
|
runtime-env: ${{ secrets.BACKEND_RUNTIME_ENV }}
|