project-auto-wizard 0.14.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 (64) hide show
  1. package/README.ja.md +3 -1
  2. package/README.ko.md +3 -1
  3. package/README.md +4 -2
  4. package/README.zh-CN.md +3 -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/changelog_manager.py +8 -8
  9. package/payload/scripts/messages.py +460 -24
  10. package/payload/scripts/version_manager.py +102 -73
  11. package/payload/version.yml.template +2 -2
  12. package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +9 -4
  13. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +62 -30
  14. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +96 -44
  15. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +79 -35
  16. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +71 -35
  17. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +24 -8
  18. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +29 -10
  19. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +57 -23
  20. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +155 -72
  21. package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +49 -20
  22. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +162 -62
  23. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +184 -87
  24. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +38 -15
  25. package/payload/workflows/go/PROJECT-GO-CI.yaml +14 -5
  26. package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +375 -120
  27. package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +9 -4
  28. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +12 -4
  29. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +375 -120
  30. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +9 -4
  31. package/payload/workflows/react/PROJECT-REACT-CI.yaml +38 -9
  32. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +19 -5
  33. package/payload/workflows/spring/PROJECT-SPRING-CI.yml +71 -35
  34. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +9 -6
  35. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +7 -4
  36. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +425 -144
  37. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +7 -4
  38. package/src/cli/args.js +17 -7
  39. package/src/commands/doctor.js +16 -0
  40. package/src/commands/dry-run.js +10 -1
  41. package/src/commands/full.js +5 -2
  42. package/src/commands/interactive.js +4 -1
  43. package/src/commands/status.js +14 -1
  44. package/src/core/copy/readme.js +58 -2
  45. package/src/core/copy/simple.js +31 -0
  46. package/src/core/copy/workflows.js +2 -2
  47. package/src/core/deploy-style.js +1 -1
  48. package/src/core/detect.js +3 -2
  49. package/src/core/types.js +15 -8
  50. package/src/core/version-yml.js +52 -6
  51. package/src/i18n/catalog/en/cli.js +1 -0
  52. package/src/i18n/catalog/en/commands.js +9 -0
  53. package/src/i18n/catalog/en/copy.js +1 -0
  54. package/src/i18n/catalog/en/core2.js +3 -0
  55. package/src/i18n/catalog/ko/cli.js +1 -0
  56. package/src/i18n/catalog/ko/commands.js +9 -0
  57. package/src/i18n/catalog/ko/copy.js +1 -0
  58. package/src/i18n/catalog/ko/core2.js +3 -0
  59. package/src/index.js +8 -4
  60. package/src/ui/env-plan.js +1 -1
  61. package/src/ui/prompts.js +7 -3
  62. package/src/ui/summary.js +1 -1
  63. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +0 -241
  64. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +0 -330
@@ -104,6 +104,8 @@ jobs:
104
104
  uses: actions/checkout@v7
105
105
  with:
106
106
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
107
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
108
+ path: .paw-msg
107
109
  sparse-checkout: |
108
110
  .github/scripts/messages.py
109
111
  version.yml
@@ -111,11 +113,16 @@ jobs:
111
113
 
112
114
  - name: Load messages
113
115
  run: |
114
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
116
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
115
117
  mkdir -p "$RUNNER_TEMP/paw"
116
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
118
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
119
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
120
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
121
+ rm -rf .paw-msg
122
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
123
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
117
124
  {
118
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
125
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
119
126
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
120
127
  } >> "$GITHUB_ENV"
121
128
 
@@ -206,18 +213,26 @@ jobs:
206
213
  uses: actions/checkout@v7
207
214
  with:
208
215
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
216
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
217
+ path: .paw-msg
209
218
  sparse-checkout: |
210
219
  .github/scripts/messages.py
211
220
  version.yml
212
221
  sparse-checkout-cone-mode: false
213
222
 
214
223
  - name: Load messages
224
+ id: load_messages
215
225
  run: |
216
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
226
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
217
227
  mkdir -p "$RUNNER_TEMP/paw"
218
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
228
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
229
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
230
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
231
+ rm -rf .paw-msg
232
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
233
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
219
234
  {
220
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
235
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
221
236
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
222
237
  } >> "$GITHUB_ENV"
223
238
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -349,16 +364,16 @@ jobs:
349
364
  # [End of section 2] Create the environment file
350
365
 
351
366
  - name: Log in to Docker Hub
352
- uses: docker/login-action@v3
367
+ uses: docker/login-action@v4
353
368
  with:
354
369
  username: ${{ secrets.DOCKERHUB_USERNAME }}
355
370
  password: ${{ secrets.DOCKERHUB_TOKEN }}
356
371
 
357
372
  - name: Set up Docker Buildx
358
- uses: docker/setup-buildx-action@v3
373
+ uses: docker/setup-buildx-action@v4
359
374
 
360
375
  - name: Build & push Docker image
361
- uses: docker/build-push-action@v5
376
+ uses: docker/build-push-action@v7
362
377
  with:
363
378
  context: ${{ env.PROJECT_PATH }}
364
379
  file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
@@ -370,13 +385,16 @@ jobs:
370
385
  - name: Progress - Docker done
371
386
  id: docker_progress
372
387
  uses: actions/github-script@v9
388
+ env:
389
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
390
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START: ${{ steps.progress.outputs.docker_start }}
373
391
  with:
374
392
  script: |
375
393
  const M = JSON.parse(process.env.PAW_MSG);
376
394
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
377
395
 
378
- const commentId = ${{ steps.progress.outputs.comment_id }};
379
- const dockerStart = ${{ steps.progress.outputs.docker_start }};
396
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
397
+ const dockerStart = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START, 10) || Date.now();
380
398
  const now = Date.now();
381
399
  const dockerElapsed = now - dockerStart;
382
400
  const minutes = Math.floor(dockerElapsed / 60000);
@@ -553,28 +571,34 @@ jobs:
553
571
 
554
572
  - name: Deploy complete comment
555
573
  uses: actions/github-script@v9
574
+ env:
575
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
576
+ PAW_IN_STEPS_PR_OUTPUTS_REF: ${{ steps.pr.outputs.ref }}
577
+ PAW_IN_STEPS_PR_OUTPUTS_SHA: ${{ steps.pr.outputs.sha }}
578
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
579
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START: ${{ steps.docker_progress.outputs.deploy_start }}
556
580
  with:
557
581
  script: |
558
582
  const M = JSON.parse(process.env.PAW_MSG);
559
583
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
560
584
 
561
- const commentId = ${{ steps.progress.outputs.comment_id }};
562
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
563
- const deployStart = ${{ steps.docker_progress.outputs.deploy_start }};
585
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
586
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
587
+ const deployStart = parseInt(process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START, 10) || Date.now();
564
588
  const now = Date.now();
565
589
  const deployElapsed = now - deployStart;
566
590
  const minutes = Math.floor(deployElapsed / 60000);
567
591
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
568
592
  const deployDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
569
593
 
570
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
571
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
572
- const previewPort = '${{ env.PREVIEW_PORT }}';
573
- const apiDocsPath = '${{ env.API_DOCS_PATH }}';
594
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
595
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
596
+ const previewPort = process.env.PREVIEW_PORT;
597
+ const apiDocsPath = process.env.API_DOCS_PATH;
574
598
  const prNumber = context.issue.number;
575
599
  const domain = `${projectName}-pr-${prNumber}.${domainSuffix}`;
576
600
  const previewUrl = `http://${domain}:${previewPort}`;
577
- const sha = '${{ steps.pr.outputs.sha }}';
601
+ const sha = process.env.PAW_IN_STEPS_PR_OUTPUTS_SHA;
578
602
 
579
603
  // Build the preview environment table (the API Docs row is added only when API_DOCS_PATH is set)
580
604
  const envRows = [
@@ -586,7 +610,7 @@ jobs:
586
610
  envRows.push(m('wf_preview.env_container_pr', {projectName, prNumber}));
587
611
  envRows.push(m('wf_preview.env_commit', {sha}));
588
612
 
589
- const branchName = '${{ steps.pr.outputs.ref }}';
613
+ const branchName = process.env.PAW_IN_STEPS_PR_OUTPUTS_REF;
590
614
 
591
615
  const body = [
592
616
  m('wf_preview.title_pr_deployed'),
@@ -630,20 +654,23 @@ jobs:
630
654
  });
631
655
 
632
656
  - name: Build/deploy failure comment
633
- if: failure()
657
+ if: failure() && steps.load_messages.outcome == 'success'
634
658
  uses: actions/github-script@v9
659
+ env:
660
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
661
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
635
662
  with:
636
663
  script: |
637
664
  const M = JSON.parse(process.env.PAW_MSG);
638
665
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
639
666
 
640
- const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
641
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
667
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
668
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
642
669
  const prNumber = context.issue.number;
643
670
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
644
671
 
645
672
  // Show where it failed based on progress
646
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
673
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
647
674
 
648
675
  // Work out where it failed - early-stage failures are distinguished too
649
676
  let dockerStatus, deployStatus;
@@ -726,6 +753,8 @@ jobs:
726
753
  uses: actions/checkout@v7
727
754
  with:
728
755
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
756
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
757
+ path: .paw-msg
729
758
  sparse-checkout: |
730
759
  .github/scripts/messages.py
731
760
  version.yml
@@ -733,11 +762,16 @@ jobs:
733
762
 
734
763
  - name: Load messages
735
764
  run: |
736
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
765
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
737
766
  mkdir -p "$RUNNER_TEMP/paw"
738
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
767
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
768
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
769
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
770
+ rm -rf .paw-msg
771
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
772
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
739
773
  {
740
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
774
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
741
775
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
742
776
  } >> "$GITHUB_ENV"
743
777
 
@@ -750,7 +784,7 @@ jobs:
750
784
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
751
785
 
752
786
  const issueNumber = context.issue.number;
753
- const marker = '${{ env.ISSUE_HELPER_MARKER }}';
787
+ const marker = process.env.ISSUE_HELPER_MARKER;
754
788
 
755
789
  console.log(m('wf_preview.log_search_branch', {issueNumber}));
756
790
  console.log(m('wf_preview.log_marker', {marker}));
@@ -795,7 +829,7 @@ jobs:
795
829
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
796
830
 
797
831
  const issueNumber = context.issue.number;
798
- const marker = '${{ env.ISSUE_HELPER_MARKER }}';
832
+ const marker = process.env.ISSUE_HELPER_MARKER;
799
833
 
800
834
  const body = [
801
835
  m('wf_preview.title_branch_missing_warn'),
@@ -839,18 +873,26 @@ jobs:
839
873
  uses: actions/checkout@v7
840
874
  with:
841
875
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
876
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
877
+ path: .paw-msg
842
878
  sparse-checkout: |
843
879
  .github/scripts/messages.py
844
880
  version.yml
845
881
  sparse-checkout-cone-mode: false
846
882
 
847
883
  - name: Load messages
884
+ id: load_messages
848
885
  run: |
849
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
886
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
850
887
  mkdir -p "$RUNNER_TEMP/paw"
851
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
888
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
889
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
890
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
891
+ rm -rf .paw-msg
892
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
893
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
852
894
  {
853
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
895
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
854
896
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
855
897
  } >> "$GITHUB_ENV"
856
898
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -880,13 +922,16 @@ jobs:
880
922
  - name: Check branch exists
881
923
  id: check_branch
882
924
  uses: actions/github-script@v9
925
+ env:
926
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
927
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER: ${{ needs.get-branch-from-issue.outputs.issue_number }}
883
928
  with:
884
929
  script: |
885
930
  const M = JSON.parse(process.env.PAW_MSG);
886
931
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
887
932
 
888
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
889
- const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
933
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
934
+ const issueNumber = parseInt(process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER, 10) || 0;
890
935
 
891
936
  console.log(m('wf_preview.log_check_exists', {branchName}));
892
937
 
@@ -943,24 +988,29 @@ jobs:
943
988
  - name: Set issue info
944
989
  if: steps.check_branch.outputs.exists == 'true'
945
990
  id: issue
991
+ env:
992
+ PAW_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
946
993
  run: |
947
- echo "ref=${{ needs.get-branch-from-issue.outputs.branch_name }}" >> $GITHUB_OUTPUT
994
+ echo "ref=$PAW_BRANCH_NAME" >> $GITHUB_OUTPUT
948
995
  echo "issue_number=${{ needs.get-branch-from-issue.outputs.issue_number }}" >> $GITHUB_OUTPUT
949
996
  # Get the latest commit SHA of the branch
950
- echo "sha=$(git ls-remote https://github.com/${{ github.repository }}.git refs/heads/${{ needs.get-branch-from-issue.outputs.branch_name }} | cut -c1-7)" >> $GITHUB_OUTPUT
997
+ echo "sha=$(git ls-remote https://github.com/${{ github.repository }}.git "refs/heads/$PAW_BRANCH_NAME" | cut -c1-7)" >> $GITHUB_OUTPUT
951
998
 
952
999
  # Progress comment system
953
1000
  - name: Create progress comment
954
1001
  if: steps.check_branch.outputs.exists == 'true'
955
1002
  id: progress
956
1003
  uses: actions/github-script@v9
1004
+ env:
1005
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
1006
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER: ${{ needs.get-branch-from-issue.outputs.issue_number }}
957
1007
  with:
958
1008
  script: |
959
1009
  const M = JSON.parse(process.env.PAW_MSG);
960
1010
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
961
1011
 
962
- const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
963
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1012
+ const issueNumber = parseInt(process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER, 10) || 0;
1013
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
964
1014
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
965
1015
  const startTime = Date.now();
966
1016
 
@@ -1048,18 +1098,18 @@ jobs:
1048
1098
 
1049
1099
  - name: Log in to Docker Hub
1050
1100
  if: steps.check_branch.outputs.exists == 'true'
1051
- uses: docker/login-action@v3
1101
+ uses: docker/login-action@v4
1052
1102
  with:
1053
1103
  username: ${{ secrets.DOCKERHUB_USERNAME }}
1054
1104
  password: ${{ secrets.DOCKERHUB_TOKEN }}
1055
1105
 
1056
1106
  - name: Set up Docker Buildx
1057
1107
  if: steps.check_branch.outputs.exists == 'true'
1058
- uses: docker/setup-buildx-action@v3
1108
+ uses: docker/setup-buildx-action@v4
1059
1109
 
1060
1110
  - name: Build & push Docker image
1061
1111
  if: steps.check_branch.outputs.exists == 'true'
1062
- uses: docker/build-push-action@v5
1112
+ uses: docker/build-push-action@v7
1063
1113
  with:
1064
1114
  context: ${{ env.PROJECT_PATH }}
1065
1115
  file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
@@ -1072,14 +1122,18 @@ jobs:
1072
1122
  if: steps.check_branch.outputs.exists == 'true'
1073
1123
  id: docker_progress
1074
1124
  uses: actions/github-script@v9
1125
+ env:
1126
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
1127
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
1128
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START: ${{ steps.progress.outputs.docker_start }}
1075
1129
  with:
1076
1130
  script: |
1077
1131
  const M = JSON.parse(process.env.PAW_MSG);
1078
1132
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1079
1133
 
1080
- const commentId = ${{ steps.progress.outputs.comment_id }};
1081
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1082
- const dockerStart = ${{ steps.progress.outputs.docker_start }};
1134
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
1135
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
1136
+ const dockerStart = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START, 10) || Date.now();
1083
1137
  const now = Date.now();
1084
1138
  const dockerElapsed = now - dockerStart;
1085
1139
  const minutes = Math.floor(dockerElapsed / 60000);
@@ -1260,29 +1314,36 @@ jobs:
1260
1314
  - name: Deploy complete comment
1261
1315
  if: steps.check_branch.outputs.exists == 'true'
1262
1316
  uses: actions/github-script@v9
1317
+ env:
1318
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
1319
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
1320
+ PAW_IN_STEPS_ISSUE_OUTPUTS_SHA: ${{ steps.issue.outputs.sha }}
1321
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
1322
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START: ${{ steps.docker_progress.outputs.deploy_start }}
1323
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER: ${{ needs.get-branch-from-issue.outputs.issue_number }}
1263
1324
  with:
1264
1325
  script: |
1265
1326
  const M = JSON.parse(process.env.PAW_MSG);
1266
1327
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1267
1328
 
1268
- const commentId = ${{ steps.progress.outputs.comment_id }};
1269
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1270
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
1271
- const deployStart = ${{ steps.docker_progress.outputs.deploy_start }};
1329
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
1330
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
1331
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
1332
+ const deployStart = parseInt(process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START, 10) || Date.now();
1272
1333
  const now = Date.now();
1273
1334
  const deployElapsed = now - deployStart;
1274
1335
  const minutes = Math.floor(deployElapsed / 60000);
1275
1336
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
1276
1337
  const deployDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
1277
1338
 
1278
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1279
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
1280
- const previewPort = '${{ env.PREVIEW_PORT }}';
1281
- const apiDocsPath = '${{ env.API_DOCS_PATH }}';
1282
- const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
1339
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
1340
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
1341
+ const previewPort = process.env.PREVIEW_PORT;
1342
+ const apiDocsPath = process.env.API_DOCS_PATH;
1343
+ const issueNumber = parseInt(process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER, 10) || 0;
1283
1344
  const domain = `${projectName}-pr-${issueNumber}.${domainSuffix}`;
1284
1345
  const previewUrl = `http://${domain}:${previewPort}`;
1285
- const sha = '${{ steps.issue.outputs.sha }}';
1346
+ const sha = process.env.PAW_IN_STEPS_ISSUE_OUTPUTS_SHA;
1286
1347
 
1287
1348
  // Build the preview environment table (the API Docs row is added only when API_DOCS_PATH is set)
1288
1349
  const envRows = [
@@ -1339,20 +1400,25 @@ jobs:
1339
1400
  });
1340
1401
 
1341
1402
  - name: Build/deploy failure comment
1342
- if: failure() && steps.check_branch.outputs.exists == 'true'
1403
+ if: failure() && steps.load_messages.outcome == 'success' && steps.check_branch.outputs.exists == 'true'
1343
1404
  uses: actions/github-script@v9
1405
+ env:
1406
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
1407
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
1408
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
1409
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER: ${{ needs.get-branch-from-issue.outputs.issue_number }}
1344
1410
  with:
1345
1411
  script: |
1346
1412
  const M = JSON.parse(process.env.PAW_MSG);
1347
1413
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1348
1414
 
1349
- const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
1350
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1351
- const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
1352
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1415
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
1416
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
1417
+ const issueNumber = parseInt(process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER, 10) || 0;
1418
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
1353
1419
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
1354
1420
 
1355
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
1421
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
1356
1422
 
1357
1423
  let dockerStatus, deployStatus;
1358
1424
  let dockerTime = '-';
@@ -1428,6 +1494,8 @@ jobs:
1428
1494
  uses: actions/checkout@v7
1429
1495
  with:
1430
1496
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
1497
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
1498
+ path: .paw-msg
1431
1499
  sparse-checkout: |
1432
1500
  .github/scripts/messages.py
1433
1501
  version.yml
@@ -1435,11 +1503,16 @@ jobs:
1435
1503
 
1436
1504
  - name: Load messages
1437
1505
  run: |
1438
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
1506
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
1439
1507
  mkdir -p "$RUNNER_TEMP/paw"
1440
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1508
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
1509
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1510
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
1511
+ rm -rf .paw-msg
1512
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
1513
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
1441
1514
  {
1442
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
1515
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
1443
1516
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
1444
1517
  } >> "$GITHUB_ENV"
1445
1518
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -1466,6 +1539,41 @@ jobs:
1466
1539
  print("PAW_EOF")
1467
1540
  PY
1468
1541
 
1542
+ # Without the deploy secrets no preview could have been deployed: on close events skip the cleanup quietly,
1543
+ # and on an explicit command report what is missing instead of surfacing a raw SSH action error
1544
+ - name: Deploy pre-check (Secrets)
1545
+ id: precheck
1546
+ env:
1547
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
1548
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
1549
+ SERVER_USER: ${{ secrets.SERVER_USER }}
1550
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
1551
+ SSH_KEY: ${{ secrets.SSH_KEY }}
1552
+ EVENT_NAME: ${{ github.event_name }}
1553
+ run: |
1554
+ m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
1555
+
1556
+ MISSING=""
1557
+ for NAME in DOCKERHUB_USERNAME SERVER_HOST SERVER_USER; do
1558
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
1559
+ done
1560
+ # SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
1561
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
1562
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
1563
+ else
1564
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
1565
+ fi
1566
+ if [ -n "$MISSING" ]; then
1567
+ if [ "$EVENT_NAME" = "issue_comment" ]; then
1568
+ echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
1569
+ exit 1
1570
+ fi
1571
+ echo "::warning title=$(m wf_preview.precheck_skip_title)::$(m wf_preview.precheck_skip_body missing="$MISSING")"
1572
+ echo "ready=false" >> "$GITHUB_OUTPUT"
1573
+ exit 0
1574
+ fi
1575
+ echo "ready=true" >> "$GITHUB_OUTPUT"
1576
+
1469
1577
  - name: Get PR/issue number
1470
1578
  id: pr_number
1471
1579
  run: |
@@ -1481,6 +1589,7 @@ jobs:
1481
1589
  fi
1482
1590
 
1483
1591
  - name: Remove container & image
1592
+ if: steps.precheck.outputs.ready == 'true'
1484
1593
  uses: appleboy/ssh-action@v1.0.3
1485
1594
  env:
1486
1595
  SSH_AUTH_METHOD: ${{ env.SSH_AUTH_METHOD }}
@@ -1528,17 +1637,20 @@ jobs:
1528
1637
 
1529
1638
  - name: Destroy complete comment
1530
1639
  if: |
1531
- github.event_name == 'issue_comment' ||
1532
- (github.event_name == 'pull_request' && github.event.action == 'closed') ||
1533
- (github.event_name == 'issues' && github.event.action == 'closed')
1640
+ steps.precheck.outputs.ready == 'true' && (
1641
+ github.event_name == 'issue_comment' ||
1642
+ (github.event_name == 'pull_request' && github.event.action == 'closed') ||
1643
+ (github.event_name == 'issues' && github.event.action == 'closed'))
1534
1644
  uses: actions/github-script@v9
1645
+ env:
1646
+ PAW_IN_STEPS_PR_NUMBER_OUTPUTS_NUMBER: ${{ steps.pr_number.outputs.number }}
1535
1647
  with:
1536
1648
  script: |
1537
1649
  const M = JSON.parse(process.env.PAW_MSG);
1538
1650
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1539
1651
 
1540
- const number = ${{ steps.pr_number.outputs.number }};
1541
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1652
+ const number = parseInt(process.env.PAW_IN_STEPS_PR_NUMBER_OUTPUTS_NUMBER, 10) || 0;
1653
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
1542
1654
 
1543
1655
  const body = [
1544
1656
  m('wf_preview.title_destroyed'),
@@ -1574,6 +1686,8 @@ jobs:
1574
1686
  uses: actions/checkout@v7
1575
1687
  with:
1576
1688
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
1689
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
1690
+ path: .paw-msg
1577
1691
  sparse-checkout: |
1578
1692
  .github/scripts/messages.py
1579
1693
  version.yml
@@ -1581,11 +1695,16 @@ jobs:
1581
1695
 
1582
1696
  - name: Load messages
1583
1697
  run: |
1584
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
1698
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
1585
1699
  mkdir -p "$RUNNER_TEMP/paw"
1586
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1700
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
1701
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1702
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
1703
+ rm -rf .paw-msg
1704
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
1705
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
1587
1706
  {
1588
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
1707
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
1589
1708
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
1590
1709
  } >> "$GITHUB_ENV"
1591
1710
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -1612,6 +1731,32 @@ jobs:
1612
1731
  print("PAW_EOF")
1613
1732
  PY
1614
1733
 
1734
+ # Report the missing secrets by name instead of surfacing a raw SSH action error
1735
+ - name: Deploy pre-check (Secrets)
1736
+ id: precheck
1737
+ env:
1738
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
1739
+ SERVER_USER: ${{ secrets.SERVER_USER }}
1740
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
1741
+ SSH_KEY: ${{ secrets.SSH_KEY }}
1742
+ run: |
1743
+ m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
1744
+
1745
+ MISSING=""
1746
+ for NAME in SERVER_HOST SERVER_USER; do
1747
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
1748
+ done
1749
+ # SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
1750
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
1751
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
1752
+ else
1753
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
1754
+ fi
1755
+ if [ -n "$MISSING" ]; then
1756
+ echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
1757
+ exit 1
1758
+ fi
1759
+
1615
1760
  - name: Check container status
1616
1761
  id: status
1617
1762
  uses: appleboy/ssh-action@v1.0.3
@@ -1663,19 +1808,22 @@ jobs:
1663
1808
 
1664
1809
  - name: Post status comment
1665
1810
  uses: actions/github-script@v9
1811
+ env:
1812
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_IS_PR: ${{ needs.check-command.outputs.is_pr }}
1813
+ PAW_IN_STEPS_CHECK_RUNNING_OUTCOME: ${{ steps.check_running.outcome }}
1666
1814
  with:
1667
1815
  script: |
1668
1816
  const M = JSON.parse(process.env.PAW_MSG);
1669
1817
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1670
1818
 
1671
1819
  const number = context.issue.number;
1672
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1673
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
1674
- const previewPort = '${{ env.PREVIEW_PORT }}';
1820
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
1821
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
1822
+ const previewPort = process.env.PREVIEW_PORT;
1675
1823
  const domain = `${projectName}-pr-${number}.${domainSuffix}`;
1676
1824
  const previewUrl = `http://${domain}:${previewPort}`;
1677
- const isRunning = '${{ steps.check_running.outcome }}' === 'success';
1678
- const isPr = '${{ needs.check-command.outputs.is_pr }}' === 'true';
1825
+ const isRunning = process.env.PAW_IN_STEPS_CHECK_RUNNING_OUTCOME === 'success';
1826
+ const isPr = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_IS_PR === 'true';
1679
1827
  const contextType = isPr ? 'PR' : 'Issue';
1680
1828
 
1681
1829
  let body;
@@ -1737,18 +1885,26 @@ jobs:
1737
1885
  uses: actions/checkout@v7
1738
1886
  with:
1739
1887
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
1888
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
1889
+ path: .paw-msg
1740
1890
  sparse-checkout: |
1741
1891
  .github/scripts/messages.py
1742
1892
  version.yml
1743
1893
  sparse-checkout-cone-mode: false
1744
1894
 
1745
1895
  - name: Load messages
1896
+ id: load_messages
1746
1897
  run: |
1747
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
1898
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
1748
1899
  mkdir -p "$RUNNER_TEMP/paw"
1749
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1900
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
1901
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1902
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
1903
+ rm -rf .paw-msg
1904
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
1905
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
1750
1906
  {
1751
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
1907
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
1752
1908
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
1753
1909
  } >> "$GITHUB_ENV"
1754
1910
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -1778,12 +1934,14 @@ jobs:
1778
1934
  - name: Check branch exists and extract info
1779
1935
  id: branch_info
1780
1936
  uses: actions/github-script@v9
1937
+ env:
1938
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
1781
1939
  with:
1782
1940
  script: |
1783
1941
  const M = JSON.parse(process.env.PAW_MSG);
1784
1942
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1785
1943
 
1786
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
1944
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
1787
1945
  const issueNumber = context.issue.number;
1788
1946
 
1789
1947
  console.log(m('wf_preview.log_check_branch', {branchName}));
@@ -1858,21 +2016,25 @@ jobs:
1858
2016
  - name: Get latest commit SHA of the branch
1859
2017
  if: steps.branch_info.outputs.branch_exists == 'true'
1860
2018
  id: get_sha
2019
+ env:
2020
+ PAW_BRANCH_NAME: ${{ needs.check-command.outputs.custom_branch }}
1861
2021
  run: |
1862
- SHA=$(git ls-remote https://github.com/${{ github.repository }}.git refs/heads/${{ needs.check-command.outputs.custom_branch }} | cut -c1-7)
2022
+ SHA=$(git ls-remote https://github.com/${{ github.repository }}.git "refs/heads/$PAW_BRANCH_NAME" | cut -c1-7)
1863
2023
  echo "sha=$SHA" >> $GITHUB_OUTPUT
1864
2024
 
1865
2025
  - name: Create progress comment
1866
2026
  if: steps.branch_info.outputs.branch_exists == 'true'
1867
2027
  id: progress
1868
2028
  uses: actions/github-script@v9
2029
+ env:
2030
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
1869
2031
  with:
1870
2032
  script: |
1871
2033
  const M = JSON.parse(process.env.PAW_MSG);
1872
2034
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1873
2035
 
1874
2036
  const issueNumber = context.issue.number;
1875
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2037
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
1876
2038
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
1877
2039
  const startTime = Date.now();
1878
2040
 
@@ -1958,14 +2120,14 @@ jobs:
1958
2120
 
1959
2121
  - name: Log in to Docker Hub
1960
2122
  if: steps.branch_info.outputs.branch_exists == 'true'
1961
- uses: docker/login-action@v3
2123
+ uses: docker/login-action@v4
1962
2124
  with:
1963
2125
  username: ${{ secrets.DOCKERHUB_USERNAME }}
1964
2126
  password: ${{ secrets.DOCKERHUB_TOKEN }}
1965
2127
 
1966
2128
  - name: Set up Docker Buildx
1967
2129
  if: steps.branch_info.outputs.branch_exists == 'true'
1968
- uses: docker/setup-buildx-action@v3
2130
+ uses: docker/setup-buildx-action@v4
1969
2131
 
1970
2132
  - name: Resolve container name
1971
2133
  if: steps.branch_info.outputs.branch_exists == 'true'
@@ -1981,7 +2143,7 @@ jobs:
1981
2143
 
1982
2144
  - name: Build & push Docker image
1983
2145
  if: steps.branch_info.outputs.branch_exists == 'true'
1984
- uses: docker/build-push-action@v5
2146
+ uses: docker/build-push-action@v7
1985
2147
  with:
1986
2148
  context: ${{ env.PROJECT_PATH }}
1987
2149
  file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
@@ -1994,14 +2156,18 @@ jobs:
1994
2156
  if: steps.branch_info.outputs.branch_exists == 'true'
1995
2157
  id: docker_progress
1996
2158
  uses: actions/github-script@v9
2159
+ env:
2160
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2161
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
2162
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START: ${{ steps.progress.outputs.docker_start }}
1997
2163
  with:
1998
2164
  script: |
1999
2165
  const M = JSON.parse(process.env.PAW_MSG);
2000
2166
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2001
2167
 
2002
- const commentId = ${{ steps.progress.outputs.comment_id }};
2003
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2004
- const dockerStart = ${{ steps.progress.outputs.docker_start }};
2168
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
2169
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2170
+ const dockerStart = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START, 10) || Date.now();
2005
2171
  const now = Date.now();
2006
2172
  const dockerElapsed = now - dockerStart;
2007
2173
  const minutes = Math.floor(dockerElapsed / 60000);
@@ -2176,29 +2342,36 @@ jobs:
2176
2342
  - name: Deploy complete comment
2177
2343
  if: steps.branch_info.outputs.branch_exists == 'true'
2178
2344
  uses: actions/github-script@v9
2345
+ env:
2346
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2347
+ PAW_IN_STEPS_CONTAINER_OUTPUTS_NAME: ${{ steps.container.outputs.name }}
2348
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
2349
+ PAW_IN_STEPS_GET_SHA_OUTPUTS_SHA: ${{ steps.get_sha.outputs.sha }}
2350
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
2351
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START: ${{ steps.docker_progress.outputs.deploy_start }}
2179
2352
  with:
2180
2353
  script: |
2181
2354
  const M = JSON.parse(process.env.PAW_MSG);
2182
2355
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2183
2356
 
2184
- const commentId = ${{ steps.progress.outputs.comment_id }};
2185
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2186
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
2187
- const deployStart = ${{ steps.docker_progress.outputs.deploy_start }};
2357
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
2358
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2359
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
2360
+ const deployStart = parseInt(process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START, 10) || Date.now();
2188
2361
  const now = Date.now();
2189
2362
  const deployElapsed = now - deployStart;
2190
2363
  const minutes = Math.floor(deployElapsed / 60000);
2191
2364
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
2192
2365
  const deployDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
2193
2366
 
2194
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2195
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
2196
- const previewPort = '${{ env.PREVIEW_PORT }}';
2197
- const apiDocsPath = '${{ env.API_DOCS_PATH }}';
2198
- const containerName = '${{ steps.container.outputs.name }}';
2367
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
2368
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
2369
+ const previewPort = process.env.PREVIEW_PORT;
2370
+ const apiDocsPath = process.env.API_DOCS_PATH;
2371
+ const containerName = process.env.PAW_IN_STEPS_CONTAINER_OUTPUTS_NAME;
2199
2372
  const domain = `${containerName}.${domainSuffix}`;
2200
2373
  const previewUrl = `http://${domain}:${previewPort}`;
2201
- const sha = '${{ steps.get_sha.outputs.sha }}';
2374
+ const sha = process.env.PAW_IN_STEPS_GET_SHA_OUTPUTS_SHA;
2202
2375
 
2203
2376
  // Build the preview environment table
2204
2377
  const envRows = [
@@ -2255,18 +2428,22 @@ jobs:
2255
2428
  });
2256
2429
 
2257
2430
  - name: Build/deploy failure comment
2258
- if: failure() && steps.branch_info.outputs.branch_exists == 'true'
2431
+ if: failure() && steps.load_messages.outcome == 'success' && steps.branch_info.outputs.branch_exists == 'true'
2259
2432
  uses: actions/github-script@v9
2433
+ env:
2434
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2435
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
2436
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
2260
2437
  with:
2261
2438
  script: |
2262
2439
  const M = JSON.parse(process.env.PAW_MSG);
2263
2440
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2264
2441
 
2265
- const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
2266
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2442
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
2443
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2267
2444
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
2268
2445
 
2269
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
2446
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
2270
2447
 
2271
2448
  let dockerStatus, deployStatus;
2272
2449
  let dockerTime = '-';
@@ -2342,6 +2519,8 @@ jobs:
2342
2519
  uses: actions/checkout@v7
2343
2520
  with:
2344
2521
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
2522
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
2523
+ path: .paw-msg
2345
2524
  sparse-checkout: |
2346
2525
  .github/scripts/messages.py
2347
2526
  version.yml
@@ -2349,11 +2528,16 @@ jobs:
2349
2528
 
2350
2529
  - name: Load messages
2351
2530
  run: |
2352
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
2531
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
2353
2532
  mkdir -p "$RUNNER_TEMP/paw"
2354
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
2533
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
2534
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
2535
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
2536
+ rm -rf .paw-msg
2537
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
2538
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
2355
2539
  {
2356
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
2540
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
2357
2541
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
2358
2542
  } >> "$GITHUB_ENV"
2359
2543
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -2380,16 +2564,45 @@ jobs:
2380
2564
  print("PAW_EOF")
2381
2565
  PY
2382
2566
 
2567
+ # Report the missing secrets by name instead of surfacing a raw SSH action error
2568
+ - name: Deploy pre-check (Secrets)
2569
+ id: precheck
2570
+ env:
2571
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
2572
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
2573
+ SERVER_USER: ${{ secrets.SERVER_USER }}
2574
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
2575
+ SSH_KEY: ${{ secrets.SSH_KEY }}
2576
+ run: |
2577
+ m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
2578
+
2579
+ MISSING=""
2580
+ for NAME in DOCKERHUB_USERNAME SERVER_HOST SERVER_USER; do
2581
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
2582
+ done
2583
+ # SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
2584
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
2585
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
2586
+ else
2587
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
2588
+ fi
2589
+ if [ -n "$MISSING" ]; then
2590
+ echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
2591
+ exit 1
2592
+ fi
2593
+
2383
2594
  - name: Extract container info from branch name
2384
2595
  id: container_info
2385
2596
  uses: actions/github-script@v9
2597
+ env:
2598
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2386
2599
  with:
2387
2600
  script: |
2388
2601
  const M = JSON.parse(process.env.PAW_MSG);
2389
2602
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2390
2603
 
2391
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2392
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2604
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2605
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
2393
2606
 
2394
2607
  // Extract the issue number from the branch name (#number pattern)
2395
2608
  const issueMatch = branchName.match(/#(\d+)/);
@@ -2464,13 +2677,16 @@ jobs:
2464
2677
 
2465
2678
  - name: Destroy complete comment
2466
2679
  uses: actions/github-script@v9
2680
+ env:
2681
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2682
+ PAW_IN_STEPS_CONTAINER_INFO_OUTPUTS_CONTAINER_NAME: ${{ steps.container_info.outputs.container_name }}
2467
2683
  with:
2468
2684
  script: |
2469
2685
  const M = JSON.parse(process.env.PAW_MSG);
2470
2686
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2471
2687
 
2472
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2473
- const containerName = '${{ steps.container_info.outputs.container_name }}';
2688
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2689
+ const containerName = process.env.PAW_IN_STEPS_CONTAINER_INFO_OUTPUTS_CONTAINER_NAME;
2474
2690
 
2475
2691
  const body = [
2476
2692
  m('wf_preview.title_custom_destroyed'),
@@ -2510,6 +2726,8 @@ jobs:
2510
2726
  uses: actions/checkout@v7
2511
2727
  with:
2512
2728
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
2729
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
2730
+ path: .paw-msg
2513
2731
  sparse-checkout: |
2514
2732
  .github/scripts/messages.py
2515
2733
  version.yml
@@ -2517,11 +2735,16 @@ jobs:
2517
2735
 
2518
2736
  - name: Load messages
2519
2737
  run: |
2520
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
2738
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
2521
2739
  mkdir -p "$RUNNER_TEMP/paw"
2522
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
2740
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
2741
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
2742
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
2743
+ rm -rf .paw-msg
2744
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
2745
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
2523
2746
  {
2524
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
2747
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
2525
2748
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
2526
2749
  } >> "$GITHUB_ENV"
2527
2750
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -2548,13 +2771,41 @@ jobs:
2548
2771
  print("PAW_EOF")
2549
2772
  PY
2550
2773
 
2774
+ # Report the missing secrets by name instead of surfacing a raw SSH action error
2775
+ - name: Deploy pre-check (Secrets)
2776
+ id: precheck
2777
+ env:
2778
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
2779
+ SERVER_USER: ${{ secrets.SERVER_USER }}
2780
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
2781
+ SSH_KEY: ${{ secrets.SSH_KEY }}
2782
+ run: |
2783
+ m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
2784
+
2785
+ MISSING=""
2786
+ for NAME in SERVER_HOST SERVER_USER; do
2787
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
2788
+ done
2789
+ # SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
2790
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
2791
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
2792
+ else
2793
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
2794
+ fi
2795
+ if [ -n "$MISSING" ]; then
2796
+ echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
2797
+ exit 1
2798
+ fi
2799
+
2551
2800
  - name: Extract container info from branch name
2552
2801
  id: container_info
2553
2802
  uses: actions/github-script@v9
2803
+ env:
2804
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2554
2805
  with:
2555
2806
  script: |
2556
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2557
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2807
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2808
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
2558
2809
 
2559
2810
  // Extract the issue number from the branch name (#number pattern)
2560
2811
  const issueMatch = branchName.match(/#(\d+)/);
@@ -2610,19 +2861,23 @@ jobs:
2610
2861
 
2611
2862
  - name: Post status comment
2612
2863
  uses: actions/github-script@v9
2864
+ env:
2865
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2866
+ PAW_IN_STEPS_CHECK_RUNNING_OUTCOME: ${{ steps.check_running.outcome }}
2867
+ PAW_IN_STEPS_CONTAINER_INFO_OUTPUTS_CONTAINER_NAME: ${{ steps.container_info.outputs.container_name }}
2613
2868
  with:
2614
2869
  script: |
2615
2870
  const M = JSON.parse(process.env.PAW_MSG);
2616
2871
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2617
2872
 
2618
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2619
- const containerName = '${{ steps.container_info.outputs.container_name }}';
2620
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-go', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2621
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
2622
- const previewPort = '${{ env.PREVIEW_PORT }}';
2873
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2874
+ const containerName = process.env.PAW_IN_STEPS_CONTAINER_INFO_OUTPUTS_CONTAINER_NAME;
2875
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-go' : process.env.PROJECT_NAME;
2876
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
2877
+ const previewPort = process.env.PREVIEW_PORT;
2623
2878
  const domain = `${containerName}.${domainSuffix}`;
2624
2879
  const previewUrl = `http://${domain}:${previewPort}`;
2625
- const isRunning = '${{ steps.check_running.outcome }}' === 'success';
2880
+ const isRunning = process.env.PAW_IN_STEPS_CHECK_RUNNING_OUTCOME === 'success';
2626
2881
 
2627
2882
  let body;
2628
2883
  if (isRunning) {