project-auto-wizard 0.15.0 → 0.15.1

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.
Files changed (59) hide show
  1. package/README.ja.md +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +2 -2
  4. package/README.zh-CN.md +1 -1
  5. package/package.json +1 -1
  6. package/payload/config/breaking-changes.json +31 -5
  7. package/payload/config/wizard-prompts.yml +0 -4
  8. package/payload/scripts/messages.py +156 -26
  9. package/payload/scripts/version_manager.py +69 -40
  10. package/payload/version.yml.template +2 -2
  11. package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +3 -0
  12. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +15 -1
  13. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +61 -12
  14. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +46 -10
  15. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +40 -11
  16. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +24 -8
  17. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +29 -10
  18. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +57 -23
  19. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +155 -72
  20. package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +49 -20
  21. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +162 -62
  22. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +184 -87
  23. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +38 -15
  24. package/payload/workflows/go/PROJECT-GO-CI.yaml +14 -5
  25. package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +375 -120
  26. package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +9 -4
  27. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +12 -4
  28. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +375 -120
  29. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +9 -4
  30. package/payload/workflows/react/PROJECT-REACT-CI.yaml +38 -9
  31. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +19 -5
  32. package/payload/workflows/spring/PROJECT-SPRING-CI.yml +71 -35
  33. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +9 -6
  34. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +7 -4
  35. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +425 -144
  36. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +7 -4
  37. package/src/cli/args.js +17 -7
  38. package/src/commands/doctor.js +11 -0
  39. package/src/commands/dry-run.js +9 -1
  40. package/src/commands/full.js +5 -2
  41. package/src/commands/interactive.js +4 -1
  42. package/src/commands/status.js +14 -1
  43. package/src/core/copy/simple.js +31 -0
  44. package/src/core/copy/workflows.js +2 -2
  45. package/src/core/deploy-style.js +1 -1
  46. package/src/core/detect.js +3 -2
  47. package/src/core/types.js +15 -8
  48. package/src/core/version-yml.js +52 -6
  49. package/src/i18n/catalog/en/cli.js +1 -0
  50. package/src/i18n/catalog/en/commands.js +8 -0
  51. package/src/i18n/catalog/en/core2.js +3 -0
  52. package/src/i18n/catalog/ko/cli.js +1 -0
  53. package/src/i18n/catalog/ko/commands.js +8 -0
  54. package/src/i18n/catalog/ko/core2.js +3 -0
  55. package/src/index.js +4 -1
  56. package/src/ui/env-plan.js +1 -1
  57. package/src/ui/prompts.js +7 -3
  58. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +0 -241
  59. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +0 -330
package/src/ui/prompts.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import * as engine from "./readline-engine.js";
6
6
  import { t } from "../i18n/index.js";
7
7
  import { DEPLOY_STYLES, NO_DEPLOY_STYLE } from "../core/deploy-style.js";
8
- import { TYPE_IDS } from "../core/types.js";
8
+ import { TYPE_IDS, typeInfo } from "../core/types.js";
9
9
  import { ENV_MODES, DEFAULT_ENV_MODE, STORE_PLATFORMS, DEPLOY_MODES, DEFAULT_DEPLOY_MODE, deployModeWarning } from "../core/flutter-options.js";
10
10
 
11
11
  export const CANCEL = engine.CANCEL;
@@ -71,7 +71,7 @@ export const ALL_TYPES = TYPE_IDS;
71
71
  export async function selectTypes(current = []) {
72
72
  return engine.multiselect({
73
73
  message: t("ui.prompts.types.select"),
74
- options: ALL_TYPES.map((type) => ({ value: type, label: type })),
74
+ options: ALL_TYPES.map((type) => ({ value: type, label: type, hint: typeInfo(type).displayName })),
75
75
  initialValues: current.length ? current : ["basic"],
76
76
  required: true,
77
77
  });
@@ -91,7 +91,11 @@ export async function confirmTypes({ types = [], markers = null } = {}) {
91
91
  options: ALL_TYPES.map((type) => {
92
92
  const marker = markers?.get?.(type);
93
93
  // Only detected types get evidence - the rest are just listed as candidates.
94
- return { value: type, label: detected.has(type) && marker ? t("ui.prompts.types.withMarker", { type, marker }) : type };
94
+ return {
95
+ value: type,
96
+ label: detected.has(type) && marker ? t("ui.prompts.types.withMarker", { type, marker }) : type,
97
+ hint: typeInfo(type).displayName,
98
+ };
95
99
  }),
96
100
  initialValues: types.length ? types : ["basic"],
97
101
  required: true,
@@ -1,241 +0,0 @@
1
- # project-auto-wizard:managed-workflow
2
- # Next.js CI (build verification)
3
- # On push/PR to the {{DEVELOP_BRANCH}} branch, verifies with npm ci, npm test (when a test script exists) and npm run build. Caches node_modules and .next/cache.
4
- #
5
- # GitHub Secrets
6
- # ENV_FILE (optional): contents of the .env file. CI only verifies the build, so no deploy-related Secrets are needed
7
- #
8
- # Project settings (env section)
9
- # PROJECT_NAME : project name
10
- # NODE_VERSION : Node.js version (default: 20.15.0)
11
- # ENV_FILE : environment file name (default: .env)
12
-
13
- name: PROJECT-NEXT-CI
14
-
15
- on:
16
- push:
17
- branches: ["{{DEVELOP_BRANCH}}"]
18
- pull_request:
19
- branches: ["{{DEVELOP_BRANCH}}"]
20
- workflow_dispatch:
21
-
22
- env:
23
- PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
24
- NODE_VERSION: "20.15.0"
25
- ENV_FILE: ".env"
26
- # Subdirectory of this project in a monorepo ('.' means always verify)
27
- PROJECT_PATH: "." # @wizard auto:project-path
28
-
29
- permissions:
30
- contents: read
31
- pull-requests: write
32
- issues: write
33
-
34
- jobs:
35
- # Job: change detection (in a monorepo, checks whether this project's path changed)
36
- changes:
37
- name: Detect changes
38
- runs-on: ubuntu-latest
39
- permissions:
40
- contents: read
41
- pull-requests: read
42
- outputs:
43
- project: ${{ steps.filter.outputs.project }}
44
- steps:
45
- - name: Checkout repository
46
- uses: actions/checkout@v7
47
-
48
- - name: Check whether the project path changed
49
- id: filter
50
- if: ${{ github.event_name != 'workflow_dispatch' }}
51
- uses: dorny/paths-filter@v4
52
- with:
53
- # A push only looks at this push's range (against the previous commit, 'before'). Left empty it would be the cumulative diff against the default branch.
54
- # The first push of a new branch (before is 0) and PRs get an empty value -> default behavior (against the default branch / PR base)
55
- base: ${{ github.event_name == 'push' && github.event.before != '0000000000000000000000000000000000000000' && github.event.before || '' }}
56
- filters: |
57
- project:
58
- - '${{ env.PROJECT_PATH == '.' && '**' || format('{0}/**', env.PROJECT_PATH) }}'
59
-
60
- build:
61
- name: Build Next.js application
62
- runs-on: ubuntu-latest
63
- needs: changes
64
- if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.project == 'true' }}
65
- # In a monorepo (--paths), build inside that subfolder
66
- defaults:
67
- run:
68
- working-directory: ${{ env.PROJECT_PATH }}
69
-
70
- steps:
71
- - name: Checkout repository
72
- uses: actions/checkout@v7
73
-
74
- - name: Create environment file (${{ env.ENV_FILE }})
75
- run: |
76
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
77
- echo -e "${{ secrets.ENV_FILE || secrets.ENV }}" > ${{ env.ENV_FILE }}
78
- m cicd.env_file_created file="${{ env.ENV_FILE }}"
79
-
80
- - name: Set up Node.js
81
- uses: actions/setup-node@v7
82
- with:
83
- node-version: ${{ env.NODE_VERSION }}
84
- cache: "npm"
85
- cache-dependency-path: ${{ env.PROJECT_PATH }}/package-lock.json
86
-
87
- - name: Cache npm dependencies
88
- uses: actions/cache@v6
89
- with:
90
- path: ~/.npm
91
- key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
92
- restore-keys: |
93
- ${{ runner.os }}-npm-
94
-
95
- - name: Cache node_modules
96
- id: node-modules-cache
97
- uses: actions/cache@v6
98
- with:
99
- path: "**/node_modules"
100
- key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
101
- restore-keys: |
102
- ${{ runner.os }}-node-modules-
103
-
104
- - name: Install dependencies
105
- if: steps.node-modules-cache.outputs.cache-hit != 'true'
106
- run: |
107
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
108
- m cibuild.node_deps_installing
109
- npm ci
110
- m cibuild.node_deps_done
111
-
112
- # Cache the Next.js build (.next/cache)
113
- # Caches the .next/cache folder for incremental builds
114
- - name: Cache Next.js build
115
- uses: actions/cache@v6
116
- with:
117
- path: ${{ env.PROJECT_PATH }}/.next/cache
118
- key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
119
- restore-keys: |
120
- ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
121
- ${{ runner.os }}-nextjs-
122
-
123
- # Runs only when package.json has a test script (the npm init default "no test specified" counts as none)
124
- - name: Run tests
125
- run: |
126
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
127
- TEST_SCRIPT=$(node -p "(require('./package.json').scripts || {}).test || ''")
128
- if [ -z "$TEST_SCRIPT" ] || echo "$TEST_SCRIPT" | grep -q "no test specified"; then
129
- m cibuild.node_no_test_script
130
- exit 0
131
- fi
132
- m cibuild.node_test_run script="$TEST_SCRIPT"
133
- # CI=true: run watch-mode test runners (react-scripts, etc.) only once
134
- CI=true npm test
135
-
136
- - name: Build project
137
- id: build
138
- run: |
139
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
140
- m cibuild.node_build_start_next
141
- m cibuild.node_build_project project="${{ env.PROJECT_NAME }}"
142
- m cibuild.node_build_node node="${{ env.NODE_VERSION }}"
143
- echo ""
144
-
145
- # Run the build and save the log
146
- npm run build > build_log.txt 2>&1 || {
147
- echo "build_failed=true" >> $GITHUB_OUTPUT
148
- echo "status=failed" >> $GITHUB_OUTPUT
149
- echo ""
150
- m cibuild.node_build_failed
151
- exit 0
152
- }
153
-
154
- echo "build_failed=false" >> $GITHUB_OUTPUT
155
- echo "status=success" >> $GITHUB_OUTPUT
156
- m cibuild.node_build_ok
157
-
158
- # Check the build output
159
- if [ -d ".next" ]; then
160
- echo ""
161
- m cibuild.node_build_output
162
- m cibuild.node_build_next_dir
163
- du -sh .next/ || true
164
- fi
165
-
166
- - name: Print build error log
167
- if: steps.build.outputs.build_failed == 'true'
168
- run: |
169
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
170
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
171
- m cibuild.node_errlog_title
172
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
173
- echo ""
174
-
175
- if [ -f "build_log.txt" ]; then
176
- tail -n 50 build_log.txt
177
- else
178
- m cibuild.node_errlog_missing
179
- fi
180
-
181
- echo ""
182
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
183
- exit 1
184
-
185
- - name: Upload build log
186
- if: always()
187
- uses: actions/upload-artifact@v7
188
- with:
189
- name: build-log
190
- path: ${{ env.PROJECT_PATH }}/build_log.txt
191
- retention-days: 7
192
-
193
- - name: Build result summary
194
- if: always()
195
- run: |
196
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
197
- echo ""
198
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
199
- if [ "${{ steps.build.outputs.status }}" = "success" ]; then
200
- m cibuild.node_result_next_ok
201
- else
202
- m cibuild.node_result_next_fail
203
- fi
204
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
205
- echo ""
206
- m cibuild.node_result_title
207
- m cibuild.node_result_project project="${{ env.PROJECT_NAME }}"
208
- m cibuild.node_result_node node="${{ env.NODE_VERSION }}"
209
- m cibuild.node_result_status status="${{ steps.build.outputs.status }}"
210
- m cibuild.node_result_branch branch="${{ github.ref_name }}"
211
- m cibuild.node_result_commit sha="${{ github.sha }}"
212
- m cibuild.node_result_time time="$(date '+%Y-%m-%d %H:%M:%S UTC')"
213
- echo ""
214
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
215
-
216
- # Job: CI Gate (the single job to register as a required check)
217
- ci-gate:
218
- name: CI Gate
219
- if: ${{ always() }}
220
- needs: [changes, build]
221
- runs-on: ubuntu-latest
222
- steps:
223
- # Only the message catalog is needed here, so check out just that
224
- - name: Checkout message catalog
225
- uses: actions/checkout@v7
226
- with:
227
- sparse-checkout: |
228
- .github/scripts/messages.py
229
- version.yml
230
- sparse-checkout-cone-mode: false
231
-
232
- - name: Aggregate results
233
- env:
234
- RESULTS: ${{ toJSON(needs.*.result) }}
235
- run: |
236
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
237
- m cibuild.gate_results results="$RESULTS"
238
- if echo "$RESULTS" | grep -Eq '"(failure|cancelled)"'; then
239
- m cibuild.gate_failed
240
- exit 1
241
- fi
@@ -1,330 +0,0 @@
1
- # project-auto-wizard:managed-workflow
2
- # Next.js CI/CD deployment
3
- # Builds a Docker image and deploys it when the {{MAIN_BRANCH}} branch is pushed. Deployed as a Node.js runtime container to support SSR.
4
- #
5
- # GitHub Secrets
6
- # ENV_FILE: contents of the .env file
7
- # DOCKERHUB_USERNAME: DockerHub username
8
- # DOCKERHUB_TOKEN: DockerHub access token
9
- # SERVER_HOST: deploy server address
10
- # SERVER_USER: SSH username
11
- # SERVER_PASSWORD: SSH password (when SSH_AUTH_METHOD=password)
12
- # SSH_KEY: contents of the SSH private key .pem (when SSH_AUTH_METHOD=key, e.g. AWS EC2)
13
- # PROJECT_DEPLOY_PORT (optional): deploy port (default: 3000)
14
- #
15
- # Environment variables (set in the env section)
16
- # SSH_AUTH_METHOD: SSH auth method password | key (default: password)
17
- # SSH_PORT (default: 2022), SSH_COMMAND_TIMEOUT (default: 600s), SSH_CONNECTION_TIMEOUT (default: 60s)
18
-
19
- name: PROJECT-NEXT-CICD
20
-
21
- on:
22
- push:
23
- branches:
24
- - "{{MAIN_BRANCH}}"
25
- # @wizard paths-anchor (for a monorepo the integrator injects the paths filter here)
26
- workflow_dispatch:
27
-
28
- env:
29
- PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
30
- # Subdirectory of this project in a monorepo (--paths). Base for the build and Docker context ('.' means the repo root)
31
- PROJECT_PATH: "." # @wizard auto:project-path
32
- DOCKER_IMAGE_PREFIX: "nextjs-container"
33
- NODE_VERSION: "20.15.0"
34
- ENV_FILE_NAME: ".env"
35
-
36
- # SSH auth method: "password" (Synology, generic servers) | "key" (AWS EC2, .pem)
37
- # password -> uses the SERVER_PASSWORD secret
38
- # key -> uses the SSH_KEY secret (the whole .pem file content as the secret value)
39
- SSH_AUTH_METHOD: "password" # @wizard ask:password
40
-
41
- SSH_PORT: "__SSH_PORT__" # @wizard ask:2022
42
- SSH_COMMAND_TIMEOUT: "600s" # SSH command execution timeout
43
- SSH_CONNECTION_TIMEOUT: "60s" # SSH connection timeout
44
-
45
- jobs:
46
- build:
47
- name: Build Next.js application
48
- runs-on: ubuntu-latest
49
- # In a monorepo (--paths), build inside that subfolder
50
- defaults:
51
- run:
52
- working-directory: ${{ env.PROJECT_PATH }}
53
-
54
- steps:
55
- - name: Checkout repository
56
- uses: actions/checkout@v7
57
-
58
- # If a required Secret or the Dockerfile is missing, report the missing item and stop
59
- # instead of failing later with a third-party error such as docker login
60
- - name: Deploy pre-check (Secrets and Dockerfile)
61
- working-directory: ${{ env.PROJECT_PATH }}
62
- env:
63
- DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
64
- DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
65
- SERVER_HOST: ${{ secrets.SERVER_HOST }}
66
- SERVER_USER: ${{ secrets.SERVER_USER }}
67
- SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
68
- SSH_KEY: ${{ secrets.SSH_KEY }}
69
- DOCKERFILE: Dockerfile
70
- run: |
71
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
72
- MISSING=""
73
- for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
74
- [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
75
- done
76
- # SSH_AUTH_METHOD=key needs SSH_KEY, otherwise SERVER_PASSWORD is required
77
- if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
78
- [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
79
- else
80
- [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
81
- fi
82
- FAILED=false
83
- if [ -n "$MISSING" ]; then
84
- echo "::error title=$(m cicd.precheck_secret_title)::$(m cicd.precheck_secret_body missing="$MISSING")"
85
- FAILED=true
86
- fi
87
- if [ ! -f "$DOCKERFILE" ]; then
88
- echo "::error title=$(m cicd.precheck_dockerfile_title)::$(m cicd.precheck_dockerfile_body path="${PROJECT_PATH}/${DOCKERFILE#./}")"
89
- FAILED=true
90
- fi
91
- if [ "$FAILED" = "true" ]; then
92
- exit 1
93
- fi
94
- m cicd.precheck_ok
95
-
96
- - name: Set up Node.js
97
- uses: actions/setup-node@v7
98
- with:
99
- node-version: ${{ env.NODE_VERSION }}
100
- cache: 'npm'
101
- cache-dependency-path: ${{ env.PROJECT_PATH }}/package-lock.json
102
-
103
- - name: Create environment file (${{ env.ENV_FILE_NAME }})
104
- run: |
105
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
106
- cat << 'EOF' > ./${{ env.ENV_FILE_NAME }}
107
- ${{ secrets.ENV_FILE || secrets.ENV }}
108
- EOF
109
- m cicd.env_file_created file="${{ env.ENV_FILE_NAME }}"
110
-
111
- - name: Cache npm dependencies
112
- uses: actions/cache@v6
113
- with:
114
- path: ~/.npm
115
- key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
116
- restore-keys: |
117
- ${{ runner.os }}-npm-
118
-
119
- - name: Cache node_modules
120
- id: node-modules-cache
121
- uses: actions/cache@v6
122
- with:
123
- path: "**/node_modules"
124
- key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
125
- restore-keys: |
126
- ${{ runner.os }}-node-modules-
127
-
128
- - name: Install dependencies
129
- if: steps.node-modules-cache.outputs.cache-hit != 'true'
130
- run: npm ci
131
-
132
- - name: Cache Next.js build
133
- uses: actions/cache@v6
134
- with:
135
- path: ${{ env.PROJECT_PATH }}/.next/cache
136
- key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
137
- restore-keys: |
138
- ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
139
- ${{ runner.os }}-nextjs-
140
-
141
- - name: Build with npm
142
- run: npm run build
143
-
144
- - name: Set up Docker Buildx
145
- uses: docker/setup-buildx-action@v3
146
-
147
- - name: Log in to DockerHub
148
- uses: docker/login-action@v3
149
- with:
150
- username: ${{ secrets.DOCKERHUB_USERNAME }}
151
- password: ${{ secrets.DOCKERHUB_TOKEN }}
152
-
153
- - name: Cache Docker layers
154
- uses: actions/cache@v6
155
- with:
156
- path: /tmp/.buildx-cache
157
- key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }}-${{ hashFiles('package-lock.json') }}
158
- restore-keys: |
159
- ${{ runner.os }}-buildx-
160
-
161
- - name: Build and push Docker image
162
- uses: docker/build-push-action@v5
163
- with:
164
- context: ${{ env.PROJECT_PATH }}
165
- file: ${{ env.PROJECT_PATH }}/Dockerfile
166
- push: true
167
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}-${{ env.DOCKER_IMAGE_PREFIX }}:${{ github.ref_name }}
168
- cache-from: type=local,src=/tmp/.buildx-cache
169
- cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
170
-
171
- - name: Move Docker cache
172
- run: |
173
- rm -rf /tmp/.buildx-cache
174
- mv /tmp/.buildx-cache-new /tmp/.buildx-cache
175
-
176
- deploy:
177
- name: Deploy to remote server
178
- needs: build
179
- runs-on: ubuntu-latest
180
-
181
- steps:
182
- # The deploy script runs on the remote server, so the messages are resolved here
183
- # and handed to it through env vars (PAW_M_*)
184
- - name: Checkout message catalog
185
- uses: actions/checkout@v7
186
- with:
187
- sparse-checkout: |
188
- .github/scripts/messages.py
189
- version.yml
190
- sparse-checkout-cone-mode: false
191
-
192
- - name: Load messages
193
- run: |
194
- python3 .github/scripts/messages.py dump cicd. | PYTHONIOENCODING=utf-8 python3 -c 'import json,sys; [print("PAW_M_" + k.replace(".", "_") + "=" + v) for k, v in json.load(sys.stdin).items()]' >> "$GITHUB_ENV"
195
-
196
- - name: Deploy
197
- uses: appleboy/ssh-action@v1.0.3
198
- env:
199
- SSH_AUTH_METHOD: ${{ env.SSH_AUTH_METHOD }}
200
- with:
201
- host: ${{ secrets.SERVER_HOST }}
202
- username: ${{ secrets.SERVER_USER }}
203
- password: ${{ secrets.SERVER_PASSWORD }}
204
- key: ${{ secrets.SSH_KEY }}
205
- port: ${{ env.SSH_PORT }}
206
- command_timeout: ${{ env.SSH_COMMAND_TIMEOUT }}
207
- timeout: ${{ env.SSH_CONNECTION_TIMEOUT }}
208
- envs: SSH_AUTH_METHOD
209
- script: |
210
- set -e
211
-
212
- # Fill the {name} placeholders of a message template (usage: m "template" name=value ...)
213
- m() { local s="$1" kv; shift; for kv in "$@"; do s="${s//\{${kv%%=*}\}/${kv#*=}}"; done; printf '%s\n' "$s"; }
214
-
215
- # Set deploy environment variables
216
- m "${{ env.PAW_M_cicd_setup_env }}"
217
- export PATH=$PATH:/usr/local/bin
218
- export PW=${{ secrets.SERVER_PASSWORD }}
219
-
220
- # sudo abstraction per SSH auth method
221
- # password mode: echo $PW | sudo -S (servers that need a sudo password, e.g. Synology)
222
- # key mode: plain sudo (passwordless-sudo servers, e.g. AWS EC2)
223
- SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
224
- if [ "${SSH_AUTH_METHOD}" = "key" ]; then
225
- SUDO() { sudo "$@"; }
226
- else
227
- SUDO() { echo "$PW" | sudo -S "$@"; }
228
- fi
229
- m "${{ env.PAW_M_cicd_ssh_auth }}" method="${SSH_AUTH_METHOD}"
230
-
231
- BRANCH=${{ github.ref_name }}
232
- PROJECT_NAME="${{ env.PROJECT_NAME }}"
233
-
234
- # Per-branch port and container name
235
- PORT=3000
236
- CONTAINER_NAME="${PROJECT_NAME}-nextjs"
237
-
238
- if [ "$BRANCH" == "{{MAIN_BRANCH}}" ]; then
239
- PORT=${{ secrets.PROJECT_DEPLOY_PORT || '3000' }}
240
- CONTAINER_NAME="${PROJECT_NAME}-nextjs-deploy"
241
- m "${{ env.PAW_M_cicd_deploy_start }}" branch="$BRANCH"
242
- else
243
- m "${{ env.PAW_M_cicd_branch_unsupported }}" branch="$BRANCH"
244
- exit 1
245
- fi
246
-
247
- m "${{ env.PAW_M_cicd_config_title }}"
248
- m "${{ env.PAW_M_cicd_config_branch }}" branch="$BRANCH"
249
- m "${{ env.PAW_M_cicd_config_project }}" project="$PROJECT_NAME"
250
- m "${{ env.PAW_M_cicd_config_container }}" container="$CONTAINER_NAME"
251
- m "${{ env.PAW_M_cicd_config_port }}" port="$PORT"
252
- m "${{ env.PAW_M_cicd_config_image }}" image="${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}-${{ env.DOCKER_IMAGE_PREFIX }}:${BRANCH}"
253
-
254
- # Pull the Docker image
255
- m "${{ env.PAW_M_cicd_pull_image_short }}"
256
- SUDO docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}-${{ env.DOCKER_IMAGE_PREFIX }}:${BRANCH}
257
-
258
- # Clean up the existing container
259
- m "${{ env.PAW_M_cicd_container_check }}" container="$CONTAINER_NAME"
260
-
261
- if SUDO docker ps -a --format '{{.Names}}' | grep -Eq "^${CONTAINER_NAME}\$"; then
262
- m "${{ env.PAW_M_cicd_container_exists }}" container="$CONTAINER_NAME"
263
- SUDO docker rm -f $CONTAINER_NAME
264
- m "${{ env.PAW_M_cicd_container_removed }}" container="$CONTAINER_NAME"
265
- else
266
- m "${{ env.PAW_M_cicd_container_none }}" container="$CONTAINER_NAME"
267
- fi
268
-
269
- # Start the new container
270
- m "${{ env.PAW_M_cicd_container_run }}" container="$CONTAINER_NAME"
271
-
272
- # Next.js needs a Node.js server for SSR
273
- # With standalone mode, run it with node server.js
274
- SUDO docker run -d \
275
- -p ${PORT}:3000 \
276
- --name $CONTAINER_NAME \
277
- -e TZ=Asia/Seoul \
278
- -e NODE_ENV=production \
279
- -v /etc/localtime:/etc/localtime:ro \
280
- --restart unless-stopped \
281
- ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}-${{ env.DOCKER_IMAGE_PREFIX }}:${BRANCH}
282
-
283
- # Confirm the deployment
284
- m "${{ env.PAW_M_cicd_deploy_done }}"
285
- echo ""
286
- m "${{ env.PAW_M_cicd_summary_title }}"
287
- m "${{ env.PAW_M_cicd_summary_project }}" project="$PROJECT_NAME"
288
- m "${{ env.PAW_M_cicd_summary_branch }}" branch="$BRANCH"
289
- m "${{ env.PAW_M_cicd_summary_container }}" container="$CONTAINER_NAME"
290
- m "${{ env.PAW_M_cicd_summary_port }}" port="$PORT"
291
- m "${{ env.PAW_M_cicd_summary_time }}" time="$(date '+%Y-%m-%d %H:%M:%S')"
292
- echo ""
293
- m "${{ env.PAW_M_cicd_summary_url }}" url="http://${{ secrets.SERVER_HOST }}:${PORT}"
294
-
295
- # Next.js Dockerfile example (standalone mode)
296
- #
297
- # # Stage 1: Dependencies
298
- # FROM node:20-alpine AS deps
299
- # WORKDIR /app
300
- # COPY package*.json ./
301
- # RUN npm ci --only=production
302
- #
303
- # # Stage 2: Builder
304
- # FROM node:20-alpine AS builder
305
- # WORKDIR /app
306
- # COPY --from=deps /app/node_modules ./node_modules
307
- # COPY . .
308
- # RUN npm run build
309
- #
310
- # # Stage 3: Runner
311
- # FROM node:20-alpine AS runner
312
- # WORKDIR /app
313
- # ENV NODE_ENV production
314
- #
315
- # RUN addgroup --system --gid 1001 nodejs
316
- # RUN adduser --system --uid 1001 nextjs
317
- #
318
- # COPY --from=builder /app/public ./public
319
- # COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
320
- # COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
321
- #
322
- # USER nextjs
323
- # EXPOSE 3000
324
- # ENV PORT 3000
325
- # CMD ["node", "server.js"]
326
- #
327
- # Add the standalone setting to next.config.js:
328
- # module.exports = {
329
- # output: 'standalone',
330
- # }