project-auto-wizard 0.15.0 → 0.16.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.
Files changed (65) 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 +162 -26
  9. package/payload/scripts/version_manager.py +69 -40
  10. package/payload/version.yml.template +6 -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 +64 -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 +32 -22
  38. package/src/commands/doctor.js +20 -0
  39. package/src/commands/dry-run.js +9 -1
  40. package/src/commands/full.js +9 -4
  41. package/src/commands/interactive.js +29 -30
  42. package/src/commands/status.js +19 -5
  43. package/src/context.js +4 -2
  44. package/src/core/copy/simple.js +31 -0
  45. package/src/core/copy/workflows.js +2 -2
  46. package/src/core/deploy-style.js +1 -1
  47. package/src/core/detect.js +3 -2
  48. package/src/core/options.js +88 -0
  49. package/src/core/release-options.js +6 -23
  50. package/src/core/types.js +15 -8
  51. package/src/core/version-yml.js +68 -26
  52. package/src/i18n/catalog/en/cli.js +3 -1
  53. package/src/i18n/catalog/en/commands.js +15 -0
  54. package/src/i18n/catalog/en/core2.js +3 -0
  55. package/src/i18n/catalog/en/ui.js +1 -0
  56. package/src/i18n/catalog/ko/cli.js +3 -1
  57. package/src/i18n/catalog/ko/commands.js +15 -0
  58. package/src/i18n/catalog/ko/core2.js +3 -0
  59. package/src/i18n/catalog/ko/ui.js +1 -0
  60. package/src/index.js +7 -3
  61. package/src/ui/env-plan.js +1 -1
  62. package/src/ui/prompts.js +9 -5
  63. package/src/ui/status-cards.js +2 -2
  64. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +0 -241
  65. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +0 -330
@@ -106,6 +106,8 @@ jobs:
106
106
  uses: actions/checkout@v7
107
107
  with:
108
108
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
109
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
110
+ path: .paw-msg
109
111
  sparse-checkout: |
110
112
  .github/scripts/messages.py
111
113
  version.yml
@@ -113,11 +115,16 @@ jobs:
113
115
 
114
116
  - name: Load messages
115
117
  run: |
116
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
118
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
117
119
  mkdir -p "$RUNNER_TEMP/paw"
118
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
120
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
121
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
122
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
123
+ rm -rf .paw-msg
124
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
125
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
119
126
  {
120
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
127
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
121
128
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
122
129
  } >> "$GITHUB_ENV"
123
130
 
@@ -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:
@@ -348,16 +363,16 @@ jobs:
348
363
  EOF
349
364
 
350
365
  - name: Log in to Docker Hub
351
- uses: docker/login-action@v3
366
+ uses: docker/login-action@v4
352
367
  with:
353
368
  username: ${{ secrets.DOCKERHUB_USERNAME }}
354
369
  password: ${{ secrets.DOCKERHUB_TOKEN }}
355
370
 
356
371
  - name: Set up Docker Buildx
357
- uses: docker/setup-buildx-action@v3
372
+ uses: docker/setup-buildx-action@v4
358
373
 
359
374
  - name: Build & push Docker image
360
- uses: docker/build-push-action@v5
375
+ uses: docker/build-push-action@v7
361
376
  with:
362
377
  context: ${{ env.PROJECT_PATH }}
363
378
  file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
@@ -369,13 +384,16 @@ jobs:
369
384
  - name: Progress - Docker done
370
385
  id: docker_progress
371
386
  uses: actions/github-script@v9
387
+ env:
388
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
389
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START: ${{ steps.progress.outputs.docker_start }}
372
390
  with:
373
391
  script: |
374
392
  const M = JSON.parse(process.env.PAW_MSG);
375
393
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
376
394
 
377
- const commentId = ${{ steps.progress.outputs.comment_id }};
378
- const dockerStart = ${{ steps.progress.outputs.docker_start }};
395
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
396
+ const dockerStart = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START, 10) || Date.now();
379
397
  const now = Date.now();
380
398
  const dockerElapsed = now - dockerStart;
381
399
  const minutes = Math.floor(dockerElapsed / 60000);
@@ -552,28 +570,34 @@ jobs:
552
570
 
553
571
  - name: Deploy complete comment
554
572
  uses: actions/github-script@v9
573
+ env:
574
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
575
+ PAW_IN_STEPS_PR_OUTPUTS_REF: ${{ steps.pr.outputs.ref }}
576
+ PAW_IN_STEPS_PR_OUTPUTS_SHA: ${{ steps.pr.outputs.sha }}
577
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
578
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START: ${{ steps.docker_progress.outputs.deploy_start }}
555
579
  with:
556
580
  script: |
557
581
  const M = JSON.parse(process.env.PAW_MSG);
558
582
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
559
583
 
560
- const commentId = ${{ steps.progress.outputs.comment_id }};
561
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
562
- const deployStart = ${{ steps.docker_progress.outputs.deploy_start }};
584
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
585
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
586
+ const deployStart = parseInt(process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START, 10) || Date.now();
563
587
  const now = Date.now();
564
588
  const deployElapsed = now - deployStart;
565
589
  const minutes = Math.floor(deployElapsed / 60000);
566
590
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
567
591
  const deployDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
568
592
 
569
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
570
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
571
- const previewPort = '${{ env.PREVIEW_PORT }}';
572
- const apiDocsPath = '${{ env.API_DOCS_PATH }}';
593
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
594
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
595
+ const previewPort = process.env.PREVIEW_PORT;
596
+ const apiDocsPath = process.env.API_DOCS_PATH;
573
597
  const prNumber = context.issue.number;
574
598
  const domain = `${projectName}-pr-${prNumber}.${domainSuffix}`;
575
599
  const previewUrl = `http://${domain}:${previewPort}`;
576
- const sha = '${{ steps.pr.outputs.sha }}';
600
+ const sha = process.env.PAW_IN_STEPS_PR_OUTPUTS_SHA;
577
601
 
578
602
  // Build the preview environment table (the API Docs row is added only when API_DOCS_PATH is set)
579
603
  const envRows = [
@@ -585,7 +609,7 @@ jobs:
585
609
  envRows.push(m('wf_preview.env_container_pr', {projectName, prNumber}));
586
610
  envRows.push(m('wf_preview.env_commit', {sha}));
587
611
 
588
- const branchName = '${{ steps.pr.outputs.ref }}';
612
+ const branchName = process.env.PAW_IN_STEPS_PR_OUTPUTS_REF;
589
613
 
590
614
  const body = [
591
615
  m('wf_preview.title_pr_deployed'),
@@ -629,20 +653,23 @@ jobs:
629
653
  });
630
654
 
631
655
  - name: Build/deploy failure comment
632
- if: failure()
656
+ if: failure() && steps.load_messages.outcome == 'success'
633
657
  uses: actions/github-script@v9
658
+ env:
659
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
660
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
634
661
  with:
635
662
  script: |
636
663
  const M = JSON.parse(process.env.PAW_MSG);
637
664
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
638
665
 
639
- const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
640
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
666
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
667
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
641
668
  const prNumber = context.issue.number;
642
669
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
643
670
 
644
671
  // Show where it failed based on progress
645
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
672
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
646
673
 
647
674
  // Work out where it failed - early-stage failures are distinguished too
648
675
  let dockerStatus, deployStatus;
@@ -725,6 +752,8 @@ jobs:
725
752
  uses: actions/checkout@v7
726
753
  with:
727
754
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
755
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
756
+ path: .paw-msg
728
757
  sparse-checkout: |
729
758
  .github/scripts/messages.py
730
759
  version.yml
@@ -732,11 +761,16 @@ jobs:
732
761
 
733
762
  - name: Load messages
734
763
  run: |
735
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
764
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
736
765
  mkdir -p "$RUNNER_TEMP/paw"
737
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
766
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
767
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
768
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
769
+ rm -rf .paw-msg
770
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
771
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
738
772
  {
739
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
773
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
740
774
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
741
775
  } >> "$GITHUB_ENV"
742
776
 
@@ -749,7 +783,7 @@ jobs:
749
783
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
750
784
 
751
785
  const issueNumber = context.issue.number;
752
- const marker = '${{ env.ISSUE_HELPER_MARKER }}';
786
+ const marker = process.env.ISSUE_HELPER_MARKER;
753
787
 
754
788
  console.log(m('wf_preview.log_search_branch', {issueNumber}));
755
789
  console.log(m('wf_preview.log_marker', {marker}));
@@ -794,7 +828,7 @@ jobs:
794
828
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
795
829
 
796
830
  const issueNumber = context.issue.number;
797
- const marker = '${{ env.ISSUE_HELPER_MARKER }}';
831
+ const marker = process.env.ISSUE_HELPER_MARKER;
798
832
 
799
833
  const body = [
800
834
  m('wf_preview.title_branch_missing_warn'),
@@ -838,18 +872,26 @@ jobs:
838
872
  uses: actions/checkout@v7
839
873
  with:
840
874
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
875
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
876
+ path: .paw-msg
841
877
  sparse-checkout: |
842
878
  .github/scripts/messages.py
843
879
  version.yml
844
880
  sparse-checkout-cone-mode: false
845
881
 
846
882
  - name: Load messages
883
+ id: load_messages
847
884
  run: |
848
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
885
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
849
886
  mkdir -p "$RUNNER_TEMP/paw"
850
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
887
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
888
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
889
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
890
+ rm -rf .paw-msg
891
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
892
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
851
893
  {
852
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
894
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
853
895
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
854
896
  } >> "$GITHUB_ENV"
855
897
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -879,13 +921,16 @@ jobs:
879
921
  - name: Check branch exists
880
922
  id: check_branch
881
923
  uses: actions/github-script@v9
924
+ env:
925
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
926
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER: ${{ needs.get-branch-from-issue.outputs.issue_number }}
882
927
  with:
883
928
  script: |
884
929
  const M = JSON.parse(process.env.PAW_MSG);
885
930
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
886
931
 
887
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
888
- const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
932
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
933
+ const issueNumber = parseInt(process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER, 10) || 0;
889
934
 
890
935
  console.log(m('wf_preview.log_check_exists', {branchName}));
891
936
 
@@ -942,23 +987,28 @@ jobs:
942
987
  - name: Set issue info
943
988
  if: steps.check_branch.outputs.exists == 'true'
944
989
  id: issue
990
+ env:
991
+ PAW_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
945
992
  run: |
946
- echo "ref=${{ needs.get-branch-from-issue.outputs.branch_name }}" >> $GITHUB_OUTPUT
993
+ echo "ref=$PAW_BRANCH_NAME" >> $GITHUB_OUTPUT
947
994
  echo "issue_number=${{ needs.get-branch-from-issue.outputs.issue_number }}" >> $GITHUB_OUTPUT
948
- 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
995
+ echo "sha=$(git ls-remote https://github.com/${{ github.repository }}.git "refs/heads/$PAW_BRANCH_NAME" | cut -c1-7)" >> $GITHUB_OUTPUT
949
996
 
950
997
  # Progress comment system
951
998
  - name: Create progress comment
952
999
  if: steps.check_branch.outputs.exists == 'true'
953
1000
  id: progress
954
1001
  uses: actions/github-script@v9
1002
+ env:
1003
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
1004
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER: ${{ needs.get-branch-from-issue.outputs.issue_number }}
955
1005
  with:
956
1006
  script: |
957
1007
  const M = JSON.parse(process.env.PAW_MSG);
958
1008
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
959
1009
 
960
- const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
961
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1010
+ const issueNumber = parseInt(process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER, 10) || 0;
1011
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
962
1012
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
963
1013
  const startTime = Date.now();
964
1014
 
@@ -1045,18 +1095,18 @@ jobs:
1045
1095
 
1046
1096
  - name: Log in to Docker Hub
1047
1097
  if: steps.check_branch.outputs.exists == 'true'
1048
- uses: docker/login-action@v3
1098
+ uses: docker/login-action@v4
1049
1099
  with:
1050
1100
  username: ${{ secrets.DOCKERHUB_USERNAME }}
1051
1101
  password: ${{ secrets.DOCKERHUB_TOKEN }}
1052
1102
 
1053
1103
  - name: Set up Docker Buildx
1054
1104
  if: steps.check_branch.outputs.exists == 'true'
1055
- uses: docker/setup-buildx-action@v3
1105
+ uses: docker/setup-buildx-action@v4
1056
1106
 
1057
1107
  - name: Build & push Docker image
1058
1108
  if: steps.check_branch.outputs.exists == 'true'
1059
- uses: docker/build-push-action@v5
1109
+ uses: docker/build-push-action@v7
1060
1110
  with:
1061
1111
  context: ${{ env.PROJECT_PATH }}
1062
1112
  file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
@@ -1069,14 +1119,18 @@ jobs:
1069
1119
  if: steps.check_branch.outputs.exists == 'true'
1070
1120
  id: docker_progress
1071
1121
  uses: actions/github-script@v9
1122
+ env:
1123
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
1124
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
1125
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START: ${{ steps.progress.outputs.docker_start }}
1072
1126
  with:
1073
1127
  script: |
1074
1128
  const M = JSON.parse(process.env.PAW_MSG);
1075
1129
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1076
1130
 
1077
- const commentId = ${{ steps.progress.outputs.comment_id }};
1078
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1079
- const dockerStart = ${{ steps.progress.outputs.docker_start }};
1131
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
1132
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
1133
+ const dockerStart = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START, 10) || Date.now();
1080
1134
  const now = Date.now();
1081
1135
  const dockerElapsed = now - dockerStart;
1082
1136
  const minutes = Math.floor(dockerElapsed / 60000);
@@ -1257,29 +1311,36 @@ jobs:
1257
1311
  - name: Deploy complete comment
1258
1312
  if: steps.check_branch.outputs.exists == 'true'
1259
1313
  uses: actions/github-script@v9
1314
+ env:
1315
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
1316
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
1317
+ PAW_IN_STEPS_ISSUE_OUTPUTS_SHA: ${{ steps.issue.outputs.sha }}
1318
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
1319
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START: ${{ steps.docker_progress.outputs.deploy_start }}
1320
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER: ${{ needs.get-branch-from-issue.outputs.issue_number }}
1260
1321
  with:
1261
1322
  script: |
1262
1323
  const M = JSON.parse(process.env.PAW_MSG);
1263
1324
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1264
1325
 
1265
- const commentId = ${{ steps.progress.outputs.comment_id }};
1266
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1267
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
1268
- const deployStart = ${{ steps.docker_progress.outputs.deploy_start }};
1326
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
1327
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
1328
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
1329
+ const deployStart = parseInt(process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START, 10) || Date.now();
1269
1330
  const now = Date.now();
1270
1331
  const deployElapsed = now - deployStart;
1271
1332
  const minutes = Math.floor(deployElapsed / 60000);
1272
1333
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
1273
1334
  const deployDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
1274
1335
 
1275
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1276
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
1277
- const previewPort = '${{ env.PREVIEW_PORT }}';
1278
- const apiDocsPath = '${{ env.API_DOCS_PATH }}';
1279
- const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
1336
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
1337
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
1338
+ const previewPort = process.env.PREVIEW_PORT;
1339
+ const apiDocsPath = process.env.API_DOCS_PATH;
1340
+ const issueNumber = parseInt(process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER, 10) || 0;
1280
1341
  const domain = `${projectName}-pr-${issueNumber}.${domainSuffix}`;
1281
1342
  const previewUrl = `http://${domain}:${previewPort}`;
1282
- const sha = '${{ steps.issue.outputs.sha }}';
1343
+ const sha = process.env.PAW_IN_STEPS_ISSUE_OUTPUTS_SHA;
1283
1344
 
1284
1345
  // Build the preview environment table (the API Docs row is added only when API_DOCS_PATH is set)
1285
1346
  const envRows = [
@@ -1336,20 +1397,25 @@ jobs:
1336
1397
  });
1337
1398
 
1338
1399
  - name: Build/deploy failure comment
1339
- if: failure() && steps.check_branch.outputs.exists == 'true'
1400
+ if: failure() && steps.load_messages.outcome == 'success' && steps.check_branch.outputs.exists == 'true'
1340
1401
  uses: actions/github-script@v9
1402
+ env:
1403
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME: ${{ needs.get-branch-from-issue.outputs.branch_name }}
1404
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
1405
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
1406
+ PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER: ${{ needs.get-branch-from-issue.outputs.issue_number }}
1341
1407
  with:
1342
1408
  script: |
1343
1409
  const M = JSON.parse(process.env.PAW_MSG);
1344
1410
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1345
1411
 
1346
- const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
1347
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1348
- const issueNumber = ${{ needs.get-branch-from-issue.outputs.issue_number }};
1349
- const branchName = '${{ needs.get-branch-from-issue.outputs.branch_name }}';
1412
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
1413
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
1414
+ const issueNumber = parseInt(process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_ISSUE_NUMBER, 10) || 0;
1415
+ const branchName = process.env.PAW_IN_NEEDS_GET_BRANCH_FROM_ISSUE_OUTPUTS_BRANCH_NAME;
1350
1416
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
1351
1417
 
1352
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
1418
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
1353
1419
 
1354
1420
  let dockerStatus, deployStatus;
1355
1421
  let dockerTime = '-';
@@ -1425,6 +1491,8 @@ jobs:
1425
1491
  uses: actions/checkout@v7
1426
1492
  with:
1427
1493
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
1494
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
1495
+ path: .paw-msg
1428
1496
  sparse-checkout: |
1429
1497
  .github/scripts/messages.py
1430
1498
  version.yml
@@ -1432,11 +1500,16 @@ jobs:
1432
1500
 
1433
1501
  - name: Load messages
1434
1502
  run: |
1435
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
1503
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
1436
1504
  mkdir -p "$RUNNER_TEMP/paw"
1437
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1505
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
1506
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1507
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
1508
+ rm -rf .paw-msg
1509
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
1510
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
1438
1511
  {
1439
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
1512
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
1440
1513
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
1441
1514
  } >> "$GITHUB_ENV"
1442
1515
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -1463,6 +1536,41 @@ jobs:
1463
1536
  print("PAW_EOF")
1464
1537
  PY
1465
1538
 
1539
+ # Without the deploy secrets no preview could have been deployed: on close events skip the cleanup quietly,
1540
+ # and on an explicit command report what is missing instead of surfacing a raw SSH action error
1541
+ - name: Deploy pre-check (Secrets)
1542
+ id: precheck
1543
+ env:
1544
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
1545
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
1546
+ SERVER_USER: ${{ secrets.SERVER_USER }}
1547
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
1548
+ SSH_KEY: ${{ secrets.SSH_KEY }}
1549
+ EVENT_NAME: ${{ github.event_name }}
1550
+ run: |
1551
+ m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
1552
+
1553
+ MISSING=""
1554
+ for NAME in DOCKERHUB_USERNAME SERVER_HOST SERVER_USER; do
1555
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
1556
+ done
1557
+ # SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
1558
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
1559
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
1560
+ else
1561
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
1562
+ fi
1563
+ if [ -n "$MISSING" ]; then
1564
+ if [ "$EVENT_NAME" = "issue_comment" ]; then
1565
+ echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
1566
+ exit 1
1567
+ fi
1568
+ echo "::warning title=$(m wf_preview.precheck_skip_title)::$(m wf_preview.precheck_skip_body missing="$MISSING")"
1569
+ echo "ready=false" >> "$GITHUB_OUTPUT"
1570
+ exit 0
1571
+ fi
1572
+ echo "ready=true" >> "$GITHUB_OUTPUT"
1573
+
1466
1574
  - name: Get PR/issue number
1467
1575
  id: pr_number
1468
1576
  run: |
@@ -1478,6 +1586,7 @@ jobs:
1478
1586
  fi
1479
1587
 
1480
1588
  - name: Remove container & image
1589
+ if: steps.precheck.outputs.ready == 'true'
1481
1590
  uses: appleboy/ssh-action@v1.0.3
1482
1591
  env:
1483
1592
  SSH_AUTH_METHOD: ${{ env.SSH_AUTH_METHOD }}
@@ -1525,17 +1634,20 @@ jobs:
1525
1634
 
1526
1635
  - name: Destroy complete comment
1527
1636
  if: |
1528
- github.event_name == 'issue_comment' ||
1529
- (github.event_name == 'pull_request' && github.event.action == 'closed') ||
1530
- (github.event_name == 'issues' && github.event.action == 'closed')
1637
+ steps.precheck.outputs.ready == 'true' && (
1638
+ github.event_name == 'issue_comment' ||
1639
+ (github.event_name == 'pull_request' && github.event.action == 'closed') ||
1640
+ (github.event_name == 'issues' && github.event.action == 'closed'))
1531
1641
  uses: actions/github-script@v9
1642
+ env:
1643
+ PAW_IN_STEPS_PR_NUMBER_OUTPUTS_NUMBER: ${{ steps.pr_number.outputs.number }}
1532
1644
  with:
1533
1645
  script: |
1534
1646
  const M = JSON.parse(process.env.PAW_MSG);
1535
1647
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1536
1648
 
1537
- const number = ${{ steps.pr_number.outputs.number }};
1538
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1649
+ const number = parseInt(process.env.PAW_IN_STEPS_PR_NUMBER_OUTPUTS_NUMBER, 10) || 0;
1650
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
1539
1651
 
1540
1652
  const body = [
1541
1653
  m('wf_preview.title_destroyed'),
@@ -1571,6 +1683,8 @@ jobs:
1571
1683
  uses: actions/checkout@v7
1572
1684
  with:
1573
1685
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
1686
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
1687
+ path: .paw-msg
1574
1688
  sparse-checkout: |
1575
1689
  .github/scripts/messages.py
1576
1690
  version.yml
@@ -1578,11 +1692,16 @@ jobs:
1578
1692
 
1579
1693
  - name: Load messages
1580
1694
  run: |
1581
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
1695
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
1582
1696
  mkdir -p "$RUNNER_TEMP/paw"
1583
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1697
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
1698
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1699
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
1700
+ rm -rf .paw-msg
1701
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
1702
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
1584
1703
  {
1585
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
1704
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
1586
1705
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
1587
1706
  } >> "$GITHUB_ENV"
1588
1707
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -1609,6 +1728,32 @@ jobs:
1609
1728
  print("PAW_EOF")
1610
1729
  PY
1611
1730
 
1731
+ # Report the missing secrets by name instead of surfacing a raw SSH action error
1732
+ - name: Deploy pre-check (Secrets)
1733
+ id: precheck
1734
+ env:
1735
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
1736
+ SERVER_USER: ${{ secrets.SERVER_USER }}
1737
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
1738
+ SSH_KEY: ${{ secrets.SSH_KEY }}
1739
+ run: |
1740
+ m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
1741
+
1742
+ MISSING=""
1743
+ for NAME in SERVER_HOST SERVER_USER; do
1744
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
1745
+ done
1746
+ # SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
1747
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
1748
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
1749
+ else
1750
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
1751
+ fi
1752
+ if [ -n "$MISSING" ]; then
1753
+ echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
1754
+ exit 1
1755
+ fi
1756
+
1612
1757
  - name: Check container status
1613
1758
  id: status
1614
1759
  uses: appleboy/ssh-action@v1.0.3
@@ -1660,19 +1805,22 @@ jobs:
1660
1805
 
1661
1806
  - name: Post status comment
1662
1807
  uses: actions/github-script@v9
1808
+ env:
1809
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_IS_PR: ${{ needs.check-command.outputs.is_pr }}
1810
+ PAW_IN_STEPS_CHECK_RUNNING_OUTCOME: ${{ steps.check_running.outcome }}
1663
1811
  with:
1664
1812
  script: |
1665
1813
  const M = JSON.parse(process.env.PAW_MSG);
1666
1814
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1667
1815
 
1668
1816
  const number = context.issue.number;
1669
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
1670
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
1671
- const previewPort = '${{ env.PREVIEW_PORT }}';
1817
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
1818
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
1819
+ const previewPort = process.env.PREVIEW_PORT;
1672
1820
  const domain = `${projectName}-pr-${number}.${domainSuffix}`;
1673
1821
  const previewUrl = `http://${domain}:${previewPort}`;
1674
- const isRunning = '${{ steps.check_running.outcome }}' === 'success';
1675
- const isPr = '${{ needs.check-command.outputs.is_pr }}' === 'true';
1822
+ const isRunning = process.env.PAW_IN_STEPS_CHECK_RUNNING_OUTCOME === 'success';
1823
+ const isPr = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_IS_PR === 'true';
1676
1824
  const contextType = isPr ? 'PR' : 'Issue';
1677
1825
 
1678
1826
  let body;
@@ -1734,18 +1882,26 @@ jobs:
1734
1882
  uses: actions/checkout@v7
1735
1883
  with:
1736
1884
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
1885
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
1886
+ path: .paw-msg
1737
1887
  sparse-checkout: |
1738
1888
  .github/scripts/messages.py
1739
1889
  version.yml
1740
1890
  sparse-checkout-cone-mode: false
1741
1891
 
1742
1892
  - name: Load messages
1893
+ id: load_messages
1743
1894
  run: |
1744
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
1895
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
1745
1896
  mkdir -p "$RUNNER_TEMP/paw"
1746
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1897
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
1898
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
1899
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
1900
+ rm -rf .paw-msg
1901
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
1902
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
1747
1903
  {
1748
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
1904
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
1749
1905
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
1750
1906
  } >> "$GITHUB_ENV"
1751
1907
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -1775,12 +1931,14 @@ jobs:
1775
1931
  - name: Check branch exists and extract info
1776
1932
  id: branch_info
1777
1933
  uses: actions/github-script@v9
1934
+ env:
1935
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
1778
1936
  with:
1779
1937
  script: |
1780
1938
  const M = JSON.parse(process.env.PAW_MSG);
1781
1939
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1782
1940
 
1783
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
1941
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
1784
1942
  const issueNumber = context.issue.number;
1785
1943
 
1786
1944
  console.log(m('wf_preview.log_check_branch', {branchName}));
@@ -1855,21 +2013,25 @@ jobs:
1855
2013
  - name: Get latest commit SHA of the branch
1856
2014
  if: steps.branch_info.outputs.branch_exists == 'true'
1857
2015
  id: get_sha
2016
+ env:
2017
+ PAW_BRANCH_NAME: ${{ needs.check-command.outputs.custom_branch }}
1858
2018
  run: |
1859
- SHA=$(git ls-remote https://github.com/${{ github.repository }}.git refs/heads/${{ needs.check-command.outputs.custom_branch }} | cut -c1-7)
2019
+ SHA=$(git ls-remote https://github.com/${{ github.repository }}.git "refs/heads/$PAW_BRANCH_NAME" | cut -c1-7)
1860
2020
  echo "sha=$SHA" >> $GITHUB_OUTPUT
1861
2021
 
1862
2022
  - name: Create progress comment
1863
2023
  if: steps.branch_info.outputs.branch_exists == 'true'
1864
2024
  id: progress
1865
2025
  uses: actions/github-script@v9
2026
+ env:
2027
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
1866
2028
  with:
1867
2029
  script: |
1868
2030
  const M = JSON.parse(process.env.PAW_MSG);
1869
2031
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1870
2032
 
1871
2033
  const issueNumber = context.issue.number;
1872
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2034
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
1873
2035
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
1874
2036
  const startTime = Date.now();
1875
2037
 
@@ -1955,14 +2117,14 @@ jobs:
1955
2117
 
1956
2118
  - name: Log in to Docker Hub
1957
2119
  if: steps.branch_info.outputs.branch_exists == 'true'
1958
- uses: docker/login-action@v3
2120
+ uses: docker/login-action@v4
1959
2121
  with:
1960
2122
  username: ${{ secrets.DOCKERHUB_USERNAME }}
1961
2123
  password: ${{ secrets.DOCKERHUB_TOKEN }}
1962
2124
 
1963
2125
  - name: Set up Docker Buildx
1964
2126
  if: steps.branch_info.outputs.branch_exists == 'true'
1965
- uses: docker/setup-buildx-action@v3
2127
+ uses: docker/setup-buildx-action@v4
1966
2128
 
1967
2129
  - name: Resolve container name
1968
2130
  if: steps.branch_info.outputs.branch_exists == 'true'
@@ -1978,7 +2140,7 @@ jobs:
1978
2140
 
1979
2141
  - name: Build & push Docker image
1980
2142
  if: steps.branch_info.outputs.branch_exists == 'true'
1981
- uses: docker/build-push-action@v5
2143
+ uses: docker/build-push-action@v7
1982
2144
  with:
1983
2145
  context: ${{ env.PROJECT_PATH }}
1984
2146
  file: ${{ env.PROJECT_PATH }}/${{ env.DOCKERFILE_PATH }}
@@ -1991,14 +2153,18 @@ jobs:
1991
2153
  if: steps.branch_info.outputs.branch_exists == 'true'
1992
2154
  id: docker_progress
1993
2155
  uses: actions/github-script@v9
2156
+ env:
2157
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2158
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
2159
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START: ${{ steps.progress.outputs.docker_start }}
1994
2160
  with:
1995
2161
  script: |
1996
2162
  const M = JSON.parse(process.env.PAW_MSG);
1997
2163
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
1998
2164
 
1999
- const commentId = ${{ steps.progress.outputs.comment_id }};
2000
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2001
- const dockerStart = ${{ steps.progress.outputs.docker_start }};
2165
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
2166
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2167
+ const dockerStart = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_DOCKER_START, 10) || Date.now();
2002
2168
  const now = Date.now();
2003
2169
  const dockerElapsed = now - dockerStart;
2004
2170
  const minutes = Math.floor(dockerElapsed / 60000);
@@ -2172,29 +2338,36 @@ jobs:
2172
2338
  - name: Deploy complete comment
2173
2339
  if: steps.branch_info.outputs.branch_exists == 'true'
2174
2340
  uses: actions/github-script@v9
2341
+ env:
2342
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2343
+ PAW_IN_STEPS_CONTAINER_OUTPUTS_NAME: ${{ steps.container.outputs.name }}
2344
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
2345
+ PAW_IN_STEPS_GET_SHA_OUTPUTS_SHA: ${{ steps.get_sha.outputs.sha }}
2346
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
2347
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START: ${{ steps.docker_progress.outputs.deploy_start }}
2175
2348
  with:
2176
2349
  script: |
2177
2350
  const M = JSON.parse(process.env.PAW_MSG);
2178
2351
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2179
2352
 
2180
- const commentId = ${{ steps.progress.outputs.comment_id }};
2181
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2182
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
2183
- const deployStart = ${{ steps.docker_progress.outputs.deploy_start }};
2353
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
2354
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2355
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
2356
+ const deployStart = parseInt(process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DEPLOY_START, 10) || Date.now();
2184
2357
  const now = Date.now();
2185
2358
  const deployElapsed = now - deployStart;
2186
2359
  const minutes = Math.floor(deployElapsed / 60000);
2187
2360
  const seconds = Math.floor((deployElapsed % 60000) / 1000);
2188
2361
  const deployDuration = minutes > 0 ? m('wf_preview.dur_min_sec', {minutes, seconds}) : m('wf_preview.dur_sec', {seconds});
2189
2362
 
2190
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2191
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
2192
- const previewPort = '${{ env.PREVIEW_PORT }}';
2193
- const apiDocsPath = '${{ env.API_DOCS_PATH }}';
2194
- const containerName = '${{ steps.container.outputs.name }}';
2363
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
2364
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
2365
+ const previewPort = process.env.PREVIEW_PORT;
2366
+ const apiDocsPath = process.env.API_DOCS_PATH;
2367
+ const containerName = process.env.PAW_IN_STEPS_CONTAINER_OUTPUTS_NAME;
2195
2368
  const domain = `${containerName}.${domainSuffix}`;
2196
2369
  const previewUrl = `http://${domain}:${previewPort}`;
2197
- const sha = '${{ steps.get_sha.outputs.sha }}';
2370
+ const sha = process.env.PAW_IN_STEPS_GET_SHA_OUTPUTS_SHA;
2198
2371
 
2199
2372
  // Build the preview environment table
2200
2373
  const envRows = [
@@ -2251,18 +2424,22 @@ jobs:
2251
2424
  });
2252
2425
 
2253
2426
  - name: Build/deploy failure comment
2254
- if: failure() && steps.branch_info.outputs.branch_exists == 'true'
2427
+ if: failure() && steps.load_messages.outcome == 'success' && steps.branch_info.outputs.branch_exists == 'true'
2255
2428
  uses: actions/github-script@v9
2429
+ env:
2430
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2431
+ PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION: ${{ steps.docker_progress.outputs.docker_duration }}
2432
+ PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID: ${{ steps.progress.outputs.comment_id }}
2256
2433
  with:
2257
2434
  script: |
2258
2435
  const M = JSON.parse(process.env.PAW_MSG);
2259
2436
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2260
2437
 
2261
- const commentId = ${{ steps.progress.outputs.comment_id || 0 }};
2262
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2438
+ const commentId = parseInt(process.env.PAW_IN_STEPS_PROGRESS_OUTPUTS_COMMENT_ID, 10) || 0;
2439
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2263
2440
  const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
2264
2441
 
2265
- const dockerDuration = '${{ steps.docker_progress.outputs.docker_duration }}';
2442
+ const dockerDuration = process.env.PAW_IN_STEPS_DOCKER_PROGRESS_OUTPUTS_DOCKER_DURATION;
2266
2443
 
2267
2444
  let dockerStatus, deployStatus;
2268
2445
  let dockerTime = '-';
@@ -2338,6 +2515,8 @@ jobs:
2338
2515
  uses: actions/checkout@v7
2339
2516
  with:
2340
2517
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
2518
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
2519
+ path: .paw-msg
2341
2520
  sparse-checkout: |
2342
2521
  .github/scripts/messages.py
2343
2522
  version.yml
@@ -2345,11 +2524,16 @@ jobs:
2345
2524
 
2346
2525
  - name: Load messages
2347
2526
  run: |
2348
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
2527
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
2349
2528
  mkdir -p "$RUNNER_TEMP/paw"
2350
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
2529
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
2530
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
2531
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
2532
+ rm -rf .paw-msg
2533
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
2534
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
2351
2535
  {
2352
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
2536
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
2353
2537
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
2354
2538
  } >> "$GITHUB_ENV"
2355
2539
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -2376,16 +2560,45 @@ jobs:
2376
2560
  print("PAW_EOF")
2377
2561
  PY
2378
2562
 
2563
+ # Report the missing secrets by name instead of surfacing a raw SSH action error
2564
+ - name: Deploy pre-check (Secrets)
2565
+ id: precheck
2566
+ env:
2567
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
2568
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
2569
+ SERVER_USER: ${{ secrets.SERVER_USER }}
2570
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
2571
+ SSH_KEY: ${{ secrets.SSH_KEY }}
2572
+ run: |
2573
+ m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
2574
+
2575
+ MISSING=""
2576
+ for NAME in DOCKERHUB_USERNAME SERVER_HOST SERVER_USER; do
2577
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
2578
+ done
2579
+ # SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
2580
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
2581
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
2582
+ else
2583
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
2584
+ fi
2585
+ if [ -n "$MISSING" ]; then
2586
+ echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
2587
+ exit 1
2588
+ fi
2589
+
2379
2590
  - name: Extract container info from branch name
2380
2591
  id: container_info
2381
2592
  uses: actions/github-script@v9
2593
+ env:
2594
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2382
2595
  with:
2383
2596
  script: |
2384
2597
  const M = JSON.parse(process.env.PAW_MSG);
2385
2598
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2386
2599
 
2387
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2388
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2600
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2601
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
2389
2602
 
2390
2603
  // Extract the issue number from the branch name (#number pattern)
2391
2604
  const issueMatch = branchName.match(/#(\d+)/);
@@ -2460,13 +2673,16 @@ jobs:
2460
2673
 
2461
2674
  - name: Destroy complete comment
2462
2675
  uses: actions/github-script@v9
2676
+ env:
2677
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2678
+ PAW_IN_STEPS_CONTAINER_INFO_OUTPUTS_CONTAINER_NAME: ${{ steps.container_info.outputs.container_name }}
2463
2679
  with:
2464
2680
  script: |
2465
2681
  const M = JSON.parse(process.env.PAW_MSG);
2466
2682
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2467
2683
 
2468
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2469
- const containerName = '${{ steps.container_info.outputs.container_name }}';
2684
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2685
+ const containerName = process.env.PAW_IN_STEPS_CONTAINER_INFO_OUTPUTS_CONTAINER_NAME;
2470
2686
 
2471
2687
  const body = [
2472
2688
  m('wf_preview.title_custom_destroyed'),
@@ -2506,6 +2722,8 @@ jobs:
2506
2722
  uses: actions/checkout@v7
2507
2723
  with:
2508
2724
  ref: ${{ github.event_name == 'pull_request' && github.sha || github.event.repository.default_branch }}
2725
+ # Separate path: a sparse checkout at the workspace root would leave the later full checkout sparse too
2726
+ path: .paw-msg
2509
2727
  sparse-checkout: |
2510
2728
  .github/scripts/messages.py
2511
2729
  version.yml
@@ -2513,11 +2731,16 @@ jobs:
2513
2731
 
2514
2732
  - name: Load messages
2515
2733
  run: |
2516
- # Keep a copy outside the workspace: checking out the PR branch later replaces its files
2734
+ # Keep a copy outside the workspace and drop the sparse clone so the later checkout sees a clean tree
2517
2735
  mkdir -p "$RUNNER_TEMP/paw"
2518
- cp .github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
2736
+ [ -f .paw-msg/.github/scripts/messages.py ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
2737
+ cp .paw-msg/.github/scripts/messages.py "$RUNNER_TEMP/paw/messages.py"
2738
+ LANG_CODE=$(cd .paw-msg && python3 "$RUNNER_TEMP/paw/messages.py" lang)
2739
+ rm -rf .paw-msg
2740
+ # The workspace root has no version.yml; pass the language resolved above to every later messages.py call
2741
+ export PROJECT_AUTO_WIZARD_LANG="$LANG_CODE"
2519
2742
  {
2520
- echo "PROJECT_AUTO_WIZARD_LANG=$(python3 .github/scripts/messages.py lang)"
2743
+ echo "PROJECT_AUTO_WIZARD_LANG=$LANG_CODE"
2521
2744
  echo "PAW_MSG=$(python3 "$RUNNER_TEMP/paw/messages.py" dump wf_preview.)"
2522
2745
  } >> "$GITHUB_ENV"
2523
2746
  # The deploy script runs on the remote server, where messages.py does not exist:
@@ -2544,13 +2767,41 @@ jobs:
2544
2767
  print("PAW_EOF")
2545
2768
  PY
2546
2769
 
2770
+ # Report the missing secrets by name instead of surfacing a raw SSH action error
2771
+ - name: Deploy pre-check (Secrets)
2772
+ id: precheck
2773
+ env:
2774
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
2775
+ SERVER_USER: ${{ secrets.SERVER_USER }}
2776
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
2777
+ SSH_KEY: ${{ secrets.SSH_KEY }}
2778
+ run: |
2779
+ m() { python3 "$RUNNER_TEMP/paw/messages.py" get "$@"; }
2780
+
2781
+ MISSING=""
2782
+ for NAME in SERVER_HOST SERVER_USER; do
2783
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
2784
+ done
2785
+ # SSH_KEY is required when SSH_AUTH_METHOD=key, otherwise SERVER_PASSWORD
2786
+ if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
2787
+ [ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
2788
+ else
2789
+ [ -n "$SERVER_PASSWORD" ] || MISSING="$MISSING SERVER_PASSWORD"
2790
+ fi
2791
+ if [ -n "$MISSING" ]; then
2792
+ echo "::error title=$(m wf_preview.precheck_secret_title)::$(m wf_preview.precheck_secret_body missing="$MISSING")"
2793
+ exit 1
2794
+ fi
2795
+
2547
2796
  - name: Extract container info from branch name
2548
2797
  id: container_info
2549
2798
  uses: actions/github-script@v9
2799
+ env:
2800
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2550
2801
  with:
2551
2802
  script: |
2552
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2553
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2803
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2804
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
2554
2805
 
2555
2806
  // Extract the issue number from the branch name (#number pattern)
2556
2807
  const issueMatch = branchName.match(/#(\d+)/);
@@ -2606,19 +2857,23 @@ jobs:
2606
2857
 
2607
2858
  - name: Post status comment
2608
2859
  uses: actions/github-script@v9
2860
+ env:
2861
+ PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH: ${{ needs.check-command.outputs.custom_branch }}
2862
+ PAW_IN_STEPS_CHECK_RUNNING_OUTCOME: ${{ steps.check_running.outcome }}
2863
+ PAW_IN_STEPS_CONTAINER_INFO_OUTPUTS_CONTAINER_NAME: ${{ steps.container_info.outputs.container_name }}
2609
2864
  with:
2610
2865
  script: |
2611
2866
  const M = JSON.parse(process.env.PAW_MSG);
2612
2867
  const m = (k, p = {}) => M[k].replace(/\{(\w+)\}/g, (s, n) => (n in p ? p[n] : s));
2613
2868
 
2614
- const branchName = '${{ needs.check-command.outputs.custom_branch }}';
2615
- const containerName = '${{ steps.container_info.outputs.container_name }}';
2616
- const projectName = '${{ env.PROJECT_PATH != '.' && format('{0}-python', env.PROJECT_NAME) || env.PROJECT_NAME }}';
2617
- const domainSuffix = '${{ env.PREVIEW_DOMAIN_SUFFIX }}';
2618
- const previewPort = '${{ env.PREVIEW_PORT }}';
2869
+ const branchName = process.env.PAW_IN_NEEDS_CHECK_COMMAND_OUTPUTS_CUSTOM_BRANCH;
2870
+ const containerName = process.env.PAW_IN_STEPS_CONTAINER_INFO_OUTPUTS_CONTAINER_NAME;
2871
+ const projectName = process.env.PROJECT_PATH !== '.' ? process.env.PROJECT_NAME + '-python' : process.env.PROJECT_NAME;
2872
+ const domainSuffix = process.env.PREVIEW_DOMAIN_SUFFIX;
2873
+ const previewPort = process.env.PREVIEW_PORT;
2619
2874
  const domain = `${containerName}.${domainSuffix}`;
2620
2875
  const previewUrl = `http://${domain}:${previewPort}`;
2621
- const isRunning = '${{ steps.check_running.outcome }}' === 'success';
2876
+ const isRunning = process.env.PAW_IN_STEPS_CHECK_RUNNING_OUTCOME === 'success';
2622
2877
 
2623
2878
  let body;
2624
2879
  if (isRunning) {