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 CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- npm package that distributes iOS TestFlight and Android Google Play actions.
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 and Android Google Play delivery. It supports
4
- native Swift/SwiftUI iOS projects, native Kotlin/Gradle Android projects, and
5
- Flutter apps. Put the platform keys under `creds/`, push to `main`, and both
6
- store builds run only on GitHub Actions.
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.
@@ -1 +1 @@
1
- 1.0.40
1
+ 1.0.42
@@ -1 +1 @@
1
- 1.0.40
1
+ 1.0.42
@@ -1 +1 @@
1
- 1.0.40
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 || log "warn: certbot failed (DNS not pointed yet?)"
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 (best effort — cert/DNS may still be propagating)
105
- if curl -fsSk "https://$DOMAIN$HEALTH" >/dev/null 2>&1; then
106
- log "public https://$DOMAIN$HEALTH healthy"
107
- else
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 one deploy
37
- workflow into the current app project. Re-run anytime to update them.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.40",
3
+ "version": "1.0.42",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
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.
@@ -39,4 +39,5 @@ jobs:
39
39
  api-domain: ${{ vars.BACKEND_API_DOMAIN }}
40
40
  backend-dir: ${{ steps.dir.outputs.path }}
41
41
  health-path: ${{ vars.BACKEND_HEALTH_PATH || '/health' }}
42
+ certbot-account: ${{ vars.BACKEND_CERTBOT_ACCOUNT }}
42
43
  runtime-env: ${{ secrets.BACKEND_RUNTIME_ENV }}
@@ -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
@@ -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 }}