project-auto-wizard 0.13.3 → 0.15.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/README.ja.md +2 -0
- package/README.ko.md +2 -0
- package/README.md +3 -1
- package/README.zh-CN.md +2 -0
- package/bin/project-auto-wizard.js +18 -4
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +28 -14
- package/payload/config/wizard-prompts.yml +102 -46
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
- package/payload/flutter-app/ios/ExportOptions.plist +8 -8
- package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
- package/payload/scripts/changelog_manager.py +304 -265
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2585 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +110 -107
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +23 -16
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +106 -63
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +55 -45
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +90 -72
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +59 -45
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
- package/src/cli/args.js +98 -52
- package/src/cli/help.js +8 -41
- package/src/commands/doctor.js +90 -89
- package/src/commands/dry-run.js +64 -43
- package/src/commands/full.js +79 -77
- package/src/commands/install-settings.js +16 -15
- package/src/commands/interactive-flutter.js +20 -19
- package/src/commands/interactive.js +135 -132
- package/src/commands/purge.js +47 -42
- package/src/commands/status.js +36 -36
- package/src/commands/uninstall.js +43 -41
- package/src/context.js +12 -11
- package/src/core/assets.js +12 -11
- package/src/core/baseline.js +27 -27
- package/src/core/branches.js +29 -27
- package/src/core/branding.js +7 -6
- package/src/core/breaking-check.js +22 -21
- package/src/core/breaking.js +15 -8
- package/src/core/copy/app-files.js +5 -4
- package/src/core/copy/flutter-app.js +8 -8
- package/src/core/copy/gitignore.js +58 -50
- package/src/core/copy/readme.js +102 -36
- package/src/core/copy/simple.js +16 -15
- package/src/core/copy/workflows.js +131 -124
- package/src/core/deploy-style.js +51 -48
- package/src/core/detect-fs.js +57 -57
- package/src/core/detect.js +67 -65
- package/src/core/errors.js +2 -2
- package/src/core/flutter-doctor.js +17 -16
- package/src/core/flutter-hooks.js +33 -32
- package/src/core/flutter-options.js +34 -33
- package/src/core/fsutil.js +12 -12
- package/src/core/installed-stores.js +4 -4
- package/src/core/logger.js +42 -40
- package/src/core/paths-resolve.js +88 -86
- package/src/core/paths.js +10 -10
- package/src/core/release-options.js +8 -8
- package/src/core/removal-exec.js +14 -13
- package/src/core/removal-plan.js +37 -37
- package/src/core/types.js +47 -47
- package/src/core/verify.js +44 -38
- package/src/core/version-yml.js +76 -68
- package/src/core/wizard-env.js +46 -46
- package/src/core/wizard-labels.js +63 -41
- package/src/i18n/catalog/en/cli.js +50 -0
- package/src/i18n/catalog/en/commands.js +188 -0
- package/src/i18n/catalog/en/copy.js +20 -0
- package/src/i18n/catalog/en/core.js +74 -0
- package/src/i18n/catalog/en/core2.js +15 -0
- package/src/i18n/catalog/en/ui.js +221 -0
- package/src/i18n/catalog/en.js +12 -0
- package/src/i18n/catalog/index.js +5 -0
- package/src/i18n/catalog/ko/cli.js +50 -0
- package/src/i18n/catalog/ko/commands.js +188 -0
- package/src/i18n/catalog/ko/copy.js +20 -0
- package/src/i18n/catalog/ko/core.js +74 -0
- package/src/i18n/catalog/ko/core2.js +15 -0
- package/src/i18n/catalog/ko/ui.js +221 -0
- package/src/i18n/catalog/ko.js +10 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/languages.js +9 -0
- package/src/index.js +132 -97
- package/src/ui/ansi.js +7 -7
- package/src/ui/banner.js +7 -6
- package/src/ui/env-plan.js +82 -81
- package/src/ui/prompts.js +97 -122
- package/src/ui/readline-engine.js +59 -58
- package/src/ui/status-cards.js +37 -31
- package/src/ui/summary.js +90 -89
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# project-auto-wizard:managed-workflow
|
|
2
|
-
# Next.js CI (
|
|
3
|
-
# {{DEVELOP_BRANCH}}
|
|
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
4
|
#
|
|
5
5
|
# GitHub Secrets
|
|
6
|
-
# ENV_FILE (
|
|
6
|
+
# ENV_FILE (optional): contents of the .env file. CI only verifies the build, so no deploy-related Secrets are needed
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
# PROJECT_NAME :
|
|
10
|
-
# NODE_VERSION : Node.js
|
|
11
|
-
# ENV_FILE :
|
|
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
12
|
|
|
13
13
|
name: PROJECT-NEXT-CI
|
|
14
14
|
|
|
@@ -23,7 +23,7 @@ env:
|
|
|
23
23
|
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
24
24
|
NODE_VERSION: "20.15.0"
|
|
25
25
|
ENV_FILE: ".env"
|
|
26
|
-
#
|
|
26
|
+
# Subdirectory of this project in a monorepo ('.' means always verify)
|
|
27
27
|
PROJECT_PATH: "." # @wizard auto:project-path
|
|
28
28
|
|
|
29
29
|
permissions:
|
|
@@ -32,9 +32,9 @@ permissions:
|
|
|
32
32
|
issues: write
|
|
33
33
|
|
|
34
34
|
jobs:
|
|
35
|
-
# Job:
|
|
35
|
+
# Job: change detection (in a monorepo, checks whether this project's path changed)
|
|
36
36
|
changes:
|
|
37
|
-
name:
|
|
37
|
+
name: Detect changes
|
|
38
38
|
runs-on: ubuntu-latest
|
|
39
39
|
permissions:
|
|
40
40
|
contents: read
|
|
@@ -45,45 +45,46 @@ jobs:
|
|
|
45
45
|
- name: Checkout repository
|
|
46
46
|
uses: actions/checkout@v7
|
|
47
47
|
|
|
48
|
-
- name:
|
|
48
|
+
- name: Check whether the project path changed
|
|
49
49
|
id: filter
|
|
50
50
|
if: ${{ github.event_name != 'workflow_dispatch' }}
|
|
51
51
|
uses: dorny/paths-filter@v4
|
|
52
52
|
with:
|
|
53
|
-
# push
|
|
54
|
-
#
|
|
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
55
|
base: ${{ github.event_name == 'push' && github.event.before != '0000000000000000000000000000000000000000' && github.event.before || '' }}
|
|
56
56
|
filters: |
|
|
57
57
|
project:
|
|
58
58
|
- '${{ env.PROJECT_PATH == '.' && '**' || format('{0}/**', env.PROJECT_PATH) }}'
|
|
59
59
|
|
|
60
60
|
build:
|
|
61
|
-
name: Next.js
|
|
61
|
+
name: Build Next.js application
|
|
62
62
|
runs-on: ubuntu-latest
|
|
63
63
|
needs: changes
|
|
64
64
|
if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.project == 'true' }}
|
|
65
|
-
#
|
|
65
|
+
# In a monorepo (--paths), build inside that subfolder
|
|
66
66
|
defaults:
|
|
67
67
|
run:
|
|
68
68
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
69
69
|
|
|
70
70
|
steps:
|
|
71
|
-
- name:
|
|
71
|
+
- name: Checkout repository
|
|
72
72
|
uses: actions/checkout@v7
|
|
73
73
|
|
|
74
|
-
- name:
|
|
74
|
+
- name: Create environment file (${{ env.ENV_FILE }})
|
|
75
75
|
run: |
|
|
76
|
+
m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
|
|
76
77
|
echo -e "${{ secrets.ENV_FILE || secrets.ENV }}" > ${{ env.ENV_FILE }}
|
|
77
|
-
|
|
78
|
+
m cicd.env_file_created file="${{ env.ENV_FILE }}"
|
|
78
79
|
|
|
79
|
-
- name: Node.js
|
|
80
|
+
- name: Set up Node.js
|
|
80
81
|
uses: actions/setup-node@v7
|
|
81
82
|
with:
|
|
82
83
|
node-version: ${{ env.NODE_VERSION }}
|
|
83
84
|
cache: "npm"
|
|
84
85
|
cache-dependency-path: ${{ env.PROJECT_PATH }}/package-lock.json
|
|
85
86
|
|
|
86
|
-
- name: npm
|
|
87
|
+
- name: Cache npm dependencies
|
|
87
88
|
uses: actions/cache@v6
|
|
88
89
|
with:
|
|
89
90
|
path: ~/.npm
|
|
@@ -91,7 +92,7 @@ jobs:
|
|
|
91
92
|
restore-keys: |
|
|
92
93
|
${{ runner.os }}-npm-
|
|
93
94
|
|
|
94
|
-
- name: node_modules
|
|
95
|
+
- name: Cache node_modules
|
|
95
96
|
id: node-modules-cache
|
|
96
97
|
uses: actions/cache@v6
|
|
97
98
|
with:
|
|
@@ -100,16 +101,17 @@ jobs:
|
|
|
100
101
|
restore-keys: |
|
|
101
102
|
${{ runner.os }}-node-modules-
|
|
102
103
|
|
|
103
|
-
- name:
|
|
104
|
+
- name: Install dependencies
|
|
104
105
|
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
|
105
106
|
run: |
|
|
106
|
-
|
|
107
|
+
m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
|
|
108
|
+
m cibuild.node_deps_installing
|
|
107
109
|
npm ci
|
|
108
|
-
|
|
110
|
+
m cibuild.node_deps_done
|
|
109
111
|
|
|
110
|
-
# Next.js
|
|
111
|
-
#
|
|
112
|
-
- name: Next.js
|
|
112
|
+
# Cache the Next.js build (.next/cache)
|
|
113
|
+
# Caches the .next/cache folder for incremental builds
|
|
114
|
+
- name: Cache Next.js build
|
|
113
115
|
uses: actions/cache@v6
|
|
114
116
|
with:
|
|
115
117
|
path: ${{ env.PROJECT_PATH }}/.next/cache
|
|
@@ -118,66 +120,69 @@ jobs:
|
|
|
118
120
|
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
|
119
121
|
${{ runner.os }}-nextjs-
|
|
120
122
|
|
|
121
|
-
# package.json
|
|
122
|
-
- name:
|
|
123
|
+
# Runs only when package.json has a test script (the npm init default "no test specified" counts as none)
|
|
124
|
+
- name: Run tests
|
|
123
125
|
run: |
|
|
126
|
+
m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
|
|
124
127
|
TEST_SCRIPT=$(node -p "(require('./package.json').scripts || {}).test || ''")
|
|
125
128
|
if [ -z "$TEST_SCRIPT" ] || echo "$TEST_SCRIPT" | grep -q "no test specified"; then
|
|
126
|
-
|
|
129
|
+
m cibuild.node_no_test_script
|
|
127
130
|
exit 0
|
|
128
131
|
fi
|
|
129
|
-
|
|
130
|
-
# CI=true: watch
|
|
132
|
+
m cibuild.node_test_run script="$TEST_SCRIPT"
|
|
133
|
+
# CI=true: run watch-mode test runners (react-scripts, etc.) only once
|
|
131
134
|
CI=true npm test
|
|
132
135
|
|
|
133
|
-
- name:
|
|
136
|
+
- name: Build project
|
|
134
137
|
id: build
|
|
135
138
|
run: |
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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 }}"
|
|
139
143
|
echo ""
|
|
140
144
|
|
|
141
|
-
#
|
|
145
|
+
# Run the build and save the log
|
|
142
146
|
npm run build > build_log.txt 2>&1 || {
|
|
143
147
|
echo "build_failed=true" >> $GITHUB_OUTPUT
|
|
144
148
|
echo "status=failed" >> $GITHUB_OUTPUT
|
|
145
149
|
echo ""
|
|
146
|
-
|
|
150
|
+
m cibuild.node_build_failed
|
|
147
151
|
exit 0
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
echo "build_failed=false" >> $GITHUB_OUTPUT
|
|
151
155
|
echo "status=success" >> $GITHUB_OUTPUT
|
|
152
|
-
|
|
156
|
+
m cibuild.node_build_ok
|
|
153
157
|
|
|
154
|
-
#
|
|
158
|
+
# Check the build output
|
|
155
159
|
if [ -d ".next" ]; then
|
|
156
160
|
echo ""
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
m cibuild.node_build_output
|
|
162
|
+
m cibuild.node_build_next_dir
|
|
159
163
|
du -sh .next/ || true
|
|
160
164
|
fi
|
|
161
165
|
|
|
162
|
-
- name:
|
|
166
|
+
- name: Print build error log
|
|
163
167
|
if: steps.build.outputs.build_failed == 'true'
|
|
164
168
|
run: |
|
|
169
|
+
m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
|
|
165
170
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
166
|
-
|
|
171
|
+
m cibuild.node_errlog_title
|
|
167
172
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
168
173
|
echo ""
|
|
169
174
|
|
|
170
175
|
if [ -f "build_log.txt" ]; then
|
|
171
176
|
tail -n 50 build_log.txt
|
|
172
177
|
else
|
|
173
|
-
|
|
178
|
+
m cibuild.node_errlog_missing
|
|
174
179
|
fi
|
|
175
180
|
|
|
176
181
|
echo ""
|
|
177
182
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
178
183
|
exit 1
|
|
179
184
|
|
|
180
|
-
- name:
|
|
185
|
+
- name: Upload build log
|
|
181
186
|
if: always()
|
|
182
187
|
uses: actions/upload-artifact@v7
|
|
183
188
|
with:
|
|
@@ -185,41 +190,52 @@ jobs:
|
|
|
185
190
|
path: ${{ env.PROJECT_PATH }}/build_log.txt
|
|
186
191
|
retention-days: 7
|
|
187
192
|
|
|
188
|
-
- name:
|
|
193
|
+
- name: Build result summary
|
|
189
194
|
if: always()
|
|
190
195
|
run: |
|
|
196
|
+
m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
|
|
191
197
|
echo ""
|
|
192
198
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
193
199
|
if [ "${{ steps.build.outputs.status }}" = "success" ]; then
|
|
194
|
-
|
|
200
|
+
m cibuild.node_result_next_ok
|
|
195
201
|
else
|
|
196
|
-
|
|
202
|
+
m cibuild.node_result_next_fail
|
|
197
203
|
fi
|
|
198
204
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
199
205
|
echo ""
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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')"
|
|
207
213
|
echo ""
|
|
208
214
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
209
215
|
|
|
210
|
-
# Job: CI Gate (
|
|
216
|
+
# Job: CI Gate (the single job to register as a required check)
|
|
211
217
|
ci-gate:
|
|
212
218
|
name: CI Gate
|
|
213
219
|
if: ${{ always() }}
|
|
214
220
|
needs: [changes, build]
|
|
215
221
|
runs-on: ubuntu-latest
|
|
216
222
|
steps:
|
|
217
|
-
|
|
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
|
|
218
233
|
env:
|
|
219
234
|
RESULTS: ${{ toJSON(needs.*.result) }}
|
|
220
235
|
run: |
|
|
221
|
-
|
|
236
|
+
m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
|
|
237
|
+
m cibuild.gate_results results="$RESULTS"
|
|
222
238
|
if echo "$RESULTS" | grep -Eq '"(failure|cancelled)"'; then
|
|
223
|
-
|
|
239
|
+
m cibuild.gate_failed
|
|
224
240
|
exit 1
|
|
225
241
|
fi
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# project-auto-wizard:managed-workflow
|
|
2
|
-
# Next.js CI/CD
|
|
3
|
-
# {{MAIN_BRANCH}}
|
|
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
4
|
#
|
|
5
5
|
# GitHub Secrets
|
|
6
|
-
# ENV_FILE: .env
|
|
7
|
-
# DOCKERHUB_USERNAME: DockerHub
|
|
8
|
-
# DOCKERHUB_TOKEN: DockerHub
|
|
9
|
-
# SERVER_HOST:
|
|
10
|
-
# SERVER_USER: SSH
|
|
11
|
-
# SERVER_PASSWORD: SSH
|
|
12
|
-
# SSH_KEY: SSH
|
|
13
|
-
# PROJECT_DEPLOY_PORT (
|
|
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
14
|
#
|
|
15
|
-
#
|
|
16
|
-
# SSH_AUTH_METHOD: SSH
|
|
17
|
-
# SSH_PORT (
|
|
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
18
|
|
|
19
19
|
name: PROJECT-NEXT-CICD
|
|
20
20
|
|
|
@@ -22,41 +22,42 @@ on:
|
|
|
22
22
|
push:
|
|
23
23
|
branches:
|
|
24
24
|
- "{{MAIN_BRANCH}}"
|
|
25
|
-
# @wizard paths-anchor (
|
|
25
|
+
# @wizard paths-anchor (for a monorepo the integrator injects the paths filter here)
|
|
26
26
|
workflow_dispatch:
|
|
27
27
|
|
|
28
28
|
env:
|
|
29
29
|
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
30
|
-
#
|
|
30
|
+
# Subdirectory of this project in a monorepo (--paths). Base for the build and Docker context ('.' means the repo root)
|
|
31
31
|
PROJECT_PATH: "." # @wizard auto:project-path
|
|
32
32
|
DOCKER_IMAGE_PREFIX: "nextjs-container"
|
|
33
33
|
NODE_VERSION: "20.15.0"
|
|
34
34
|
ENV_FILE_NAME: ".env"
|
|
35
35
|
|
|
36
|
-
# SSH
|
|
37
|
-
# password
|
|
38
|
-
# key
|
|
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
39
|
SSH_AUTH_METHOD: "password" # @wizard ask:password
|
|
40
40
|
|
|
41
41
|
SSH_PORT: "__SSH_PORT__" # @wizard ask:2022
|
|
42
|
-
SSH_COMMAND_TIMEOUT: "600s" # SSH
|
|
43
|
-
SSH_CONNECTION_TIMEOUT: "60s" # SSH
|
|
42
|
+
SSH_COMMAND_TIMEOUT: "600s" # SSH command execution timeout
|
|
43
|
+
SSH_CONNECTION_TIMEOUT: "60s" # SSH connection timeout
|
|
44
44
|
|
|
45
45
|
jobs:
|
|
46
46
|
build:
|
|
47
|
-
name: Next.js
|
|
47
|
+
name: Build Next.js application
|
|
48
48
|
runs-on: ubuntu-latest
|
|
49
|
-
#
|
|
49
|
+
# In a monorepo (--paths), build inside that subfolder
|
|
50
50
|
defaults:
|
|
51
51
|
run:
|
|
52
52
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
53
53
|
|
|
54
54
|
steps:
|
|
55
|
-
- name:
|
|
55
|
+
- name: Checkout repository
|
|
56
56
|
uses: actions/checkout@v7
|
|
57
57
|
|
|
58
|
-
#
|
|
59
|
-
-
|
|
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)
|
|
60
61
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
61
62
|
env:
|
|
62
63
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
@@ -67,11 +68,12 @@ jobs:
|
|
|
67
68
|
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
68
69
|
DOCKERFILE: Dockerfile
|
|
69
70
|
run: |
|
|
71
|
+
m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
|
|
70
72
|
MISSING=""
|
|
71
73
|
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
72
74
|
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
73
75
|
done
|
|
74
|
-
# SSH_AUTH_METHOD=key
|
|
76
|
+
# SSH_AUTH_METHOD=key needs SSH_KEY, otherwise SERVER_PASSWORD is required
|
|
75
77
|
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
76
78
|
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
77
79
|
else
|
|
@@ -79,33 +81,34 @@ jobs:
|
|
|
79
81
|
fi
|
|
80
82
|
FAILED=false
|
|
81
83
|
if [ -n "$MISSING" ]; then
|
|
82
|
-
echo "::error title
|
|
84
|
+
echo "::error title=$(m cicd.precheck_secret_title)::$(m cicd.precheck_secret_body missing="$MISSING")"
|
|
83
85
|
FAILED=true
|
|
84
86
|
fi
|
|
85
87
|
if [ ! -f "$DOCKERFILE" ]; then
|
|
86
|
-
echo "::error title=
|
|
88
|
+
echo "::error title=$(m cicd.precheck_dockerfile_title)::$(m cicd.precheck_dockerfile_body path="${PROJECT_PATH}/${DOCKERFILE#./}")"
|
|
87
89
|
FAILED=true
|
|
88
90
|
fi
|
|
89
91
|
if [ "$FAILED" = "true" ]; then
|
|
90
92
|
exit 1
|
|
91
93
|
fi
|
|
92
|
-
|
|
94
|
+
m cicd.precheck_ok
|
|
93
95
|
|
|
94
|
-
- name: Node.js
|
|
96
|
+
- name: Set up Node.js
|
|
95
97
|
uses: actions/setup-node@v7
|
|
96
98
|
with:
|
|
97
99
|
node-version: ${{ env.NODE_VERSION }}
|
|
98
100
|
cache: 'npm'
|
|
99
101
|
cache-dependency-path: ${{ env.PROJECT_PATH }}/package-lock.json
|
|
100
102
|
|
|
101
|
-
- name:
|
|
103
|
+
- name: Create environment file (${{ env.ENV_FILE_NAME }})
|
|
102
104
|
run: |
|
|
105
|
+
m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
|
|
103
106
|
cat << 'EOF' > ./${{ env.ENV_FILE_NAME }}
|
|
104
107
|
${{ secrets.ENV_FILE || secrets.ENV }}
|
|
105
108
|
EOF
|
|
106
|
-
|
|
109
|
+
m cicd.env_file_created file="${{ env.ENV_FILE_NAME }}"
|
|
107
110
|
|
|
108
|
-
- name: npm
|
|
111
|
+
- name: Cache npm dependencies
|
|
109
112
|
uses: actions/cache@v6
|
|
110
113
|
with:
|
|
111
114
|
path: ~/.npm
|
|
@@ -113,7 +116,7 @@ jobs:
|
|
|
113
116
|
restore-keys: |
|
|
114
117
|
${{ runner.os }}-npm-
|
|
115
118
|
|
|
116
|
-
- name: node_modules
|
|
119
|
+
- name: Cache node_modules
|
|
117
120
|
id: node-modules-cache
|
|
118
121
|
uses: actions/cache@v6
|
|
119
122
|
with:
|
|
@@ -122,11 +125,11 @@ jobs:
|
|
|
122
125
|
restore-keys: |
|
|
123
126
|
${{ runner.os }}-node-modules-
|
|
124
127
|
|
|
125
|
-
- name:
|
|
128
|
+
- name: Install dependencies
|
|
126
129
|
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
|
127
130
|
run: npm ci
|
|
128
131
|
|
|
129
|
-
- name: Next.js
|
|
132
|
+
- name: Cache Next.js build
|
|
130
133
|
uses: actions/cache@v6
|
|
131
134
|
with:
|
|
132
135
|
path: ${{ env.PROJECT_PATH }}/.next/cache
|
|
@@ -138,10 +141,10 @@ jobs:
|
|
|
138
141
|
- name: Build with npm
|
|
139
142
|
run: npm run build
|
|
140
143
|
|
|
141
|
-
- name: Docker
|
|
144
|
+
- name: Set up Docker Buildx
|
|
142
145
|
uses: docker/setup-buildx-action@v3
|
|
143
146
|
|
|
144
|
-
- name: DockerHub
|
|
147
|
+
- name: Log in to DockerHub
|
|
145
148
|
uses: docker/login-action@v3
|
|
146
149
|
with:
|
|
147
150
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
@@ -155,7 +158,7 @@ jobs:
|
|
|
155
158
|
restore-keys: |
|
|
156
159
|
${{ runner.os }}-buildx-
|
|
157
160
|
|
|
158
|
-
- name:
|
|
161
|
+
- name: Build and push Docker image
|
|
159
162
|
uses: docker/build-push-action@v5
|
|
160
163
|
with:
|
|
161
164
|
context: ${{ env.PROJECT_PATH }}
|
|
@@ -171,11 +174,25 @@ jobs:
|
|
|
171
174
|
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
172
175
|
|
|
173
176
|
deploy:
|
|
174
|
-
name:
|
|
177
|
+
name: Deploy to remote server
|
|
175
178
|
needs: build
|
|
176
179
|
runs-on: ubuntu-latest
|
|
177
180
|
|
|
178
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
|
+
|
|
179
196
|
- name: Deploy
|
|
180
197
|
uses: appleboy/ssh-action@v1.0.3
|
|
181
198
|
env:
|
|
@@ -192,65 +209,68 @@ jobs:
|
|
|
192
209
|
script: |
|
|
193
210
|
set -e
|
|
194
211
|
|
|
195
|
-
#
|
|
196
|
-
|
|
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 }}"
|
|
197
217
|
export PATH=$PATH:/usr/local/bin
|
|
198
218
|
export PW=${{ secrets.SERVER_PASSWORD }}
|
|
199
219
|
|
|
200
|
-
#
|
|
201
|
-
# password
|
|
202
|
-
# key
|
|
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)
|
|
203
223
|
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
204
224
|
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
205
225
|
SUDO() { sudo "$@"; }
|
|
206
226
|
else
|
|
207
227
|
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
208
228
|
fi
|
|
209
|
-
|
|
229
|
+
m "${{ env.PAW_M_cicd_ssh_auth }}" method="${SSH_AUTH_METHOD}"
|
|
210
230
|
|
|
211
231
|
BRANCH=${{ github.ref_name }}
|
|
212
232
|
PROJECT_NAME="${{ env.PROJECT_NAME }}"
|
|
213
233
|
|
|
214
|
-
#
|
|
234
|
+
# Per-branch port and container name
|
|
215
235
|
PORT=3000
|
|
216
236
|
CONTAINER_NAME="${PROJECT_NAME}-nextjs"
|
|
217
237
|
|
|
218
238
|
if [ "$BRANCH" == "{{MAIN_BRANCH}}" ]; then
|
|
219
239
|
PORT=${{ secrets.PROJECT_DEPLOY_PORT || '3000' }}
|
|
220
240
|
CONTAINER_NAME="${PROJECT_NAME}-nextjs-deploy"
|
|
221
|
-
|
|
241
|
+
m "${{ env.PAW_M_cicd_deploy_start }}" branch="$BRANCH"
|
|
222
242
|
else
|
|
223
|
-
|
|
243
|
+
m "${{ env.PAW_M_cicd_branch_unsupported }}" branch="$BRANCH"
|
|
224
244
|
exit 1
|
|
225
245
|
fi
|
|
226
246
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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}"
|
|
233
253
|
|
|
234
|
-
#
|
|
235
|
-
|
|
254
|
+
# Pull the Docker image
|
|
255
|
+
m "${{ env.PAW_M_cicd_pull_image_short }}"
|
|
236
256
|
SUDO docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}-${{ env.DOCKER_IMAGE_PREFIX }}:${BRANCH}
|
|
237
257
|
|
|
238
|
-
#
|
|
239
|
-
|
|
258
|
+
# Clean up the existing container
|
|
259
|
+
m "${{ env.PAW_M_cicd_container_check }}" container="$CONTAINER_NAME"
|
|
240
260
|
|
|
241
261
|
if SUDO docker ps -a --format '{{.Names}}' | grep -Eq "^${CONTAINER_NAME}\$"; then
|
|
242
|
-
|
|
262
|
+
m "${{ env.PAW_M_cicd_container_exists }}" container="$CONTAINER_NAME"
|
|
243
263
|
SUDO docker rm -f $CONTAINER_NAME
|
|
244
|
-
|
|
264
|
+
m "${{ env.PAW_M_cicd_container_removed }}" container="$CONTAINER_NAME"
|
|
245
265
|
else
|
|
246
|
-
|
|
266
|
+
m "${{ env.PAW_M_cicd_container_none }}" container="$CONTAINER_NAME"
|
|
247
267
|
fi
|
|
248
268
|
|
|
249
|
-
#
|
|
250
|
-
|
|
269
|
+
# Start the new container
|
|
270
|
+
m "${{ env.PAW_M_cicd_container_run }}" container="$CONTAINER_NAME"
|
|
251
271
|
|
|
252
|
-
# Next.js
|
|
253
|
-
# standalone
|
|
272
|
+
# Next.js needs a Node.js server for SSR
|
|
273
|
+
# With standalone mode, run it with node server.js
|
|
254
274
|
SUDO docker run -d \
|
|
255
275
|
-p ${PORT}:3000 \
|
|
256
276
|
--name $CONTAINER_NAME \
|
|
@@ -260,19 +280,19 @@ jobs:
|
|
|
260
280
|
--restart unless-stopped \
|
|
261
281
|
${{ secrets.DOCKERHUB_USERNAME }}/${PROJECT_NAME}-${{ env.DOCKER_IMAGE_PREFIX }}:${BRANCH}
|
|
262
282
|
|
|
263
|
-
#
|
|
264
|
-
|
|
283
|
+
# Confirm the deployment
|
|
284
|
+
m "${{ env.PAW_M_cicd_deploy_done }}"
|
|
265
285
|
echo ""
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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')"
|
|
272
292
|
echo ""
|
|
273
|
-
|
|
293
|
+
m "${{ env.PAW_M_cicd_summary_url }}" url="http://${{ secrets.SERVER_HOST }}:${PORT}"
|
|
274
294
|
|
|
275
|
-
# Next.js Dockerfile
|
|
295
|
+
# Next.js Dockerfile example (standalone mode)
|
|
276
296
|
#
|
|
277
297
|
# # Stage 1: Dependencies
|
|
278
298
|
# FROM node:20-alpine AS deps
|
|
@@ -304,7 +324,7 @@ jobs:
|
|
|
304
324
|
# ENV PORT 3000
|
|
305
325
|
# CMD ["node", "server.js"]
|
|
306
326
|
#
|
|
307
|
-
# next.config.js
|
|
327
|
+
# Add the standalone setting to next.config.js:
|
|
308
328
|
# module.exports = {
|
|
309
329
|
# output: 'standalone',
|
|
310
330
|
# }
|