project-auto-wizard 0.16.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/payload/scripts/messages.py +44 -6
- package/payload/scripts/version_manager.py +76 -3
- package/payload/version.yml.template +3 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +7 -7
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +111 -57
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +10 -1
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +13 -3
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +10 -1
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +2 -2
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +10 -10
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +10 -10
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +14 -12
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +35 -18
- package/payload/workflows/go/PROJECT-GO-CI.yaml +3 -1
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +81 -61
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +3 -1
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +81 -61
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +3 -2
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +4 -4
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +86 -68
- package/src/commands/doctor.js +14 -24
- package/src/commands/dry-run.js +4 -3
- package/src/commands/full.js +6 -6
- package/src/commands/interactive.js +5 -1
- package/src/commands/status.js +9 -2
- package/src/core/copy/gitignore.js +3 -3
- package/src/core/copy/readme.js +11 -10
- package/src/core/detect-fs.js +9 -4
- package/src/core/options.js +36 -0
- package/src/core/version-yml.js +37 -13
- package/src/i18n/catalog/en/cli.js +1 -0
- package/src/i18n/catalog/en/commands.js +4 -1
- package/src/i18n/catalog/en/core.js +1 -0
- package/src/i18n/catalog/en/core2.js +2 -0
- package/src/i18n/catalog/en/ui.js +3 -0
- package/src/i18n/catalog/ko/cli.js +1 -0
- package/src/i18n/catalog/ko/commands.js +4 -1
- package/src/i18n/catalog/ko/core.js +1 -0
- package/src/i18n/catalog/ko/core2.js +2 -0
- package/src/i18n/catalog/ko/ui.js +3 -0
- package/src/index.js +19 -7
- package/src/ui/summary.js +15 -6
package/README.ja.md
CHANGED
|
@@ -49,7 +49,7 @@ npx project-auto-wizard --mode full --force --type node --dry-run # preview on
|
|
|
49
49
|
|
|
50
50
|
生成されたファイルをコミットして push してください。`npx project-auto-wizard --mode doctor` は、ワークフローが依存するリポジトリ設定を確認します。
|
|
51
51
|
|
|
52
|
-
メッセージの言語は英語または韓国語(`en` | `ko`)です。`--lang`、環境変数 `PROJECT_AUTO_WIZARD_LANG`、`version.yml` の `language`
|
|
52
|
+
メッセージの言語は英語または韓国語(`en` | `ko`)です。`--lang`、環境変数 `PROJECT_AUTO_WIZARD_LANG`、`version.yml` の `language` の順に優先されます。環境変数はその実行にのみ適用され、有効な保存済み `language` は上書きしません(有効な値がない場合のみ保存)。上書きするには `--lang` を使います。
|
|
53
53
|
|
|
54
54
|
## インストールされるもの
|
|
55
55
|
|
package/README.ko.md
CHANGED
|
@@ -49,7 +49,7 @@ npx project-auto-wizard --mode full --force --type node --dry-run # preview on
|
|
|
49
49
|
|
|
50
50
|
생성된 파일을 커밋하고 push하세요. `npx project-auto-wizard --mode doctor`는 워크플로우가 의존하는 레포 설정을 점검합니다.
|
|
51
51
|
|
|
52
|
-
메시지 언어는 영어 또는 한국어(`en` | `ko`)입니다. `--lang`, 환경변수 `PROJECT_AUTO_WIZARD_LANG`, `version.yml`의 `language` 순서로 우선 적용됩니다.
|
|
52
|
+
메시지 언어는 영어 또는 한국어(`en` | `ko`)입니다. `--lang`, 환경변수 `PROJECT_AUTO_WIZARD_LANG`, `version.yml`의 `language` 순서로 우선 적용됩니다. 환경변수는 그 실행에만 적용되며 저장된 유효한 `language`를 덮어쓰지 않습니다(유효한 값이 없을 때만 저장). 덮어쓰려면 `--lang`을 쓰세요.
|
|
53
53
|
|
|
54
54
|
## 설치되는 것
|
|
55
55
|
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Everything it installs is plain GitHub Actions in your own repository. No API ke
|
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
23
|
<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
|
|
24
|
-
## Latest Version : v0.16.
|
|
24
|
+
## Latest Version : v0.16.2 (2026-10-01)
|
|
25
25
|
|
|
26
26
|
[Full release history](CHANGELOG.md)
|
|
27
27
|
|
|
@@ -50,7 +50,7 @@ npx project-auto-wizard --mode full --force --type node --dry-run # preview on
|
|
|
50
50
|
|
|
51
51
|
Commit the generated files and push. `npx project-auto-wizard --mode doctor` checks the repository settings the workflows depend on.
|
|
52
52
|
|
|
53
|
-
Messages are in English or Korean (`en` | `ko`). Pick one with `--lang`, the `PROJECT_AUTO_WIZARD_LANG` environment variable, or `language` in `version.yml`, in that order of precedence.
|
|
53
|
+
Messages are in English or Korean (`en` | `ko`). Pick one with `--lang`, the `PROJECT_AUTO_WIZARD_LANG` environment variable, or `language` in `version.yml`, in that order of precedence. The environment variable applies to that run only and does not overwrite a valid saved `language` (it is saved only when there is none); `--lang` does.
|
|
54
54
|
|
|
55
55
|
## What you get
|
|
56
56
|
|
package/README.zh-CN.md
CHANGED
|
@@ -49,7 +49,7 @@ npx project-auto-wizard --mode full --force --type node --dry-run # preview on
|
|
|
49
49
|
|
|
50
50
|
提交生成的文件并 push。`npx project-auto-wizard --mode doctor` 会检查工作流所依赖的仓库设置。
|
|
51
51
|
|
|
52
|
-
消息语言为英语或韩语(`en` | `ko`)。可通过 `--lang`、环境变量 `PROJECT_AUTO_WIZARD_LANG` 或 `version.yml` 中的 `language`
|
|
52
|
+
消息语言为英语或韩语(`en` | `ko`)。可通过 `--lang`、环境变量 `PROJECT_AUTO_WIZARD_LANG` 或 `version.yml` 中的 `language` 指定,优先级依次降低。环境变量仅对该次运行生效,不会覆盖已保存的有效 `language`(仅在没有有效值时才保存);如需覆盖请使用 `--lang`。
|
|
53
53
|
|
|
54
54
|
## 安装内容
|
|
55
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-auto-wizard",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "One command DevOps: npx wizard that installs GitHub-native Release Automation (optional GitHub Copilot AI summaries) into any project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -674,6 +674,7 @@ EN = {
|
|
|
674
674
|
"flutter_a.ci_apk_start": "📦 Starting the Flutter APK build (debug)...",
|
|
675
675
|
"flutter_a.ci_apk_done": "✅ APK build complete",
|
|
676
676
|
"flutter_a.ci_podfile_missing": "ℹ️ ios/Podfile not found, skipping pod install (no CocoaPods plugins)",
|
|
677
|
+
"flutter_a.ci_pods_installed": "✅ CocoaPods installed",
|
|
677
678
|
"flutter_a.ci_ios_start": "📦 Starting the Flutter iOS build...",
|
|
678
679
|
"flutter_a.ci_ios_done": "✅ iOS build complete",
|
|
679
680
|
"flutter_a.ci_gate_results": "needs results: {results}",
|
|
@@ -751,7 +752,8 @@ EN = {
|
|
|
751
752
|
"ios_test_tf.st_waiting": "⏸️ Waiting",
|
|
752
753
|
"ios_test_tf.st_done": "✅ Done",
|
|
753
754
|
"ios_test_tf.st_failed": "❌ Failed",
|
|
754
|
-
"ios_test_tf.
|
|
755
|
+
"ios_test_tf.st_not_run": "⏸️ Not run (an earlier step failed)",
|
|
756
|
+
"ios_test_tf.st_precheck_blocked": "⏸️ Not run (pre-check failed: {missing})",
|
|
755
757
|
"ios_test_tf.row_build_number": "| **Build number** | `{build}` |",
|
|
756
758
|
"ios_test_tf.row_app_version": "| **App version** | `{version}({build})` |",
|
|
757
759
|
"ios_test_tf.row_branch": "| **Branch** | `{branch}` |",
|
|
@@ -789,6 +791,7 @@ EN = {
|
|
|
789
791
|
"ios_test_tf.notes_generated": "📋 Generated release notes:",
|
|
790
792
|
"ios_test_tf.title_prepare_failed": "## 🍎 ❌ iOS TestFlight build failed",
|
|
791
793
|
"ios_test_tf.prepare_error": "❌ **An error occurred while preparing the environment.**",
|
|
794
|
+
"ios_test_tf.prepare_precheck_error": "❌ **The pre-check failed, so the build did not start.** Missing or invalid: {missing}",
|
|
792
795
|
"ios_test_tf.run_log_check_link": "🔗 [Check the workflow run log]({url})",
|
|
793
796
|
"ios_test_tf.log_prepare_failed": "❌ Progress update complete: prepare failed",
|
|
794
797
|
"ios_test_tf.log_prepare_done": "✅ Progress update complete: prepare done ({elapsed})",
|
|
@@ -882,6 +885,7 @@ EN = {
|
|
|
882
885
|
"truncate.limit_positive": "ERROR: limit must be a positive integer: {limit}",
|
|
883
886
|
# --- version ---
|
|
884
887
|
"version.bump_help": "bump level (default patch — same as before when not given)",
|
|
888
|
+
"version.option_default_help": "value to print when the key is not in version.yml (default false)",
|
|
885
889
|
# --- wf_aisum ---
|
|
886
890
|
"wf_aisum.no_issue_in_branch": "No issue number found in the branch name — skipping: {ref}",
|
|
887
891
|
# --- wf_changelog ---
|
|
@@ -1047,7 +1051,8 @@ EN = {
|
|
|
1047
1051
|
"wf_preview.status_done": "✅ Done",
|
|
1048
1052
|
"wf_preview.status_failed": "❌ Failed",
|
|
1049
1053
|
"wf_preview.status_init_failed": "⚠️ Initialization failed",
|
|
1050
|
-
"wf_preview.
|
|
1054
|
+
"wf_preview.status_not_run": "⏸️ Not run (an earlier step failed)",
|
|
1055
|
+
"wf_preview.status_precheck_blocked": "⏸️ Not run (pre-check failed: {missing})",
|
|
1051
1056
|
"wf_preview.tbl_commands": "| Command | Description |",
|
|
1052
1057
|
"wf_preview.tbl_items": "| Item | Value |",
|
|
1053
1058
|
"wf_preview.tbl_steps": "| Step | Status | Duration |",
|
|
@@ -1265,6 +1270,7 @@ EN = {
|
|
|
1265
1270
|
"version_manager.err_invalid_version": "ERROR: invalid version format: {version}",
|
|
1266
1271
|
"version_manager.err_invalid_version_xyz": "ERROR: invalid version format: {version} (must be x.y.z)",
|
|
1267
1272
|
"version_manager.err_generic": "ERROR: {error}",
|
|
1273
|
+
"version_manager.option_invalid": "::warning::version.yml option {option} has an unrecognized value \"{value}\"; using false. Use true or false.",
|
|
1268
1274
|
"version_manager.err_no_types_sync": "ERROR: version.yml has no readable project_types — cannot sync project files",
|
|
1269
1275
|
"version_manager.err_no_types_check": "ERROR: version.yml has no readable project_types — cannot sync versions",
|
|
1270
1276
|
"version_manager.err_write_failed": "failed to write version {version} to version.yml",
|
|
@@ -1290,11 +1296,15 @@ EN = {
|
|
|
1290
1296
|
"wf_common.rebase_ok": "rebase ok, retrying push...",
|
|
1291
1297
|
"wf_common.rebase_failed": "rebase failed, manual conflict resolution required",
|
|
1292
1298
|
"wf_common.push_gave_up": "push failed after {max_retries} attempts",
|
|
1299
|
+
"wf_common.push_protected": "Push to {branch} was rejected by a branch protection rule, not by a race, so retrying cannot help. The bot commit has to be pushed to {branch} directly: allow the pushing account (the WORKFLOW_PAT owner, or the GitHub Actions bot when no PAT is set) to bypass the rule.",
|
|
1300
|
+
"wf_release.push_protected_trunk": "trunk-based mode commits every release (version bump and CHANGELOG) straight to {branch}, so its protection rule must allow that push. If it cannot, switch to the develop -> main release flow, where the bot only commits to the release PR branch.",
|
|
1293
1301
|
"wf_common.readme_conflict_resolved": "README.md conflicted with a concurrent update - kept the remote copy and reapplied the version line",
|
|
1294
1302
|
# --- wf_aisum ---
|
|
1295
1303
|
"wf_aisum.expected_version": "expected next version: {version} (current: {current_version}, mode: {mode}, bump: {bump})",
|
|
1296
1304
|
"wf_aisum.comment_update_failed": "comment update failed — continuing (this workflow never blocks the PR)",
|
|
1297
1305
|
"wf_aisum.comment_post_failed": "comment post failed — continuing (this workflow never blocks the PR)",
|
|
1306
|
+
"wf_aisum.summary_heading": "📋 **PR Summary (project-auto-wizard)**",
|
|
1307
|
+
"wf_aisum.engine_label": "engine: {engine}",
|
|
1298
1308
|
# --- wf_changelog ---
|
|
1299
1309
|
"wf_changelog.pr_not_open": "PR #{pr_number} is already {state} — skipping the release pipeline",
|
|
1300
1310
|
"wf_changelog.pr_state": "PR #{pr_number} state: {state}",
|
|
@@ -1317,9 +1327,17 @@ EN = {
|
|
|
1317
1327
|
"wf_changelog.automerge_off_list_failed": "Could not read the comments of PR #{pr_number}; skipped the merge guidance comment to avoid a duplicate",
|
|
1318
1328
|
"wf_changelog.automerge_off_rename_hint": "The PR title could not be updated automatically. Before merging, set the merge commit message (or the PR title) to `{subject}` so the release is published.",
|
|
1319
1329
|
"wf_changelog.automerge_rename_failed": "Could not update PR #{pr_number} (title or guidance comment); the release still needs \"chore(release):\" in the merge commit message",
|
|
1320
|
-
"wf_changelog.automerge_off_comment": "**Automatic merge is off** (`release_automerge: false` in `version.yml`). Wait until this workflow run has finished (it confirms the version and renames
|
|
1330
|
+
"wf_changelog.automerge_off_comment": "**Automatic merge is off** (`release_automerge: false` in `version.yml`). Wait until this workflow run has finished (it confirms the version and renames this PR to `chore(release): ...`), then merge it yourself with **Create a merge commit** (recommended).\n\nOther merge methods may not publish the release by themselves. *Rebase and merge* leaves the version-confirm commit (`[skip ci]`) as the last commit, and *Squash and merge* with the default message (commit list included) puts that `[skip ci]` into the merge commit body; in both cases GitHub skips the push workflows and no tag or Release is created. If the repository's squash message default is the PR title only, a squash merge is published. If a release was skipped, run **PROJECT-RELEASE-PUBLISH** manually on the release branch (Actions > Run workflow) to publish. Both methods also rewrite the commit history the next release notes are built from, so earlier changes can be listed again (after a rebase merge only once develop has merged the release branch back). Merging before this run finishes has a similar effect: the safety-net workflow (PROJECT-VERSION-CONTROL) then publishes an extra patch version.",
|
|
1321
1331
|
"wf_changelog.merge_retry": "merge attempt {attempt}/{max_attempts} failed — retrying in {wait}s",
|
|
1332
|
+
"wf_changelog.head_updated": "head branch updated from base",
|
|
1322
1333
|
"wf_changelog.merge_gave_up": "PR merge failed after {max_attempts} attempts (allow_merge_commit={allow_merge_commit}) — check branch protection rules, required checks, and token permissions",
|
|
1334
|
+
"wf_changelog.merge_blocked_review": "The release PR cannot be merged automatically: branch protection on {prod} requires an approving review. Approve it and merge it yourself with a merge commit, or change the rule so the merging account can merge without the review. To stop this attempt, set release_automerge: false in version.yml.",
|
|
1335
|
+
"wf_changelog.merge_blocked_policy": "The release PR cannot be merged automatically: branch protection on {prod} blocks the merge (required checks, reviews or another rule). Merge it yourself with a merge commit once the requirements are met, or change the rule so the merging account is allowed. To stop this attempt, set release_automerge: false in version.yml.",
|
|
1336
|
+
"wf_changelog.automerge_blocked_renamed": "automerge was blocked — PR renamed to \"{subject}\"; merge it yourself once the requirements are met",
|
|
1337
|
+
"wf_changelog.merge_blocked_reason_review": "the release branch requires an approving review.",
|
|
1338
|
+
"wf_changelog.merge_blocked_reason_policy": "branch protection on the release branch blocks the merge (required checks, reviews or another rule).",
|
|
1339
|
+
"wf_changelog.merge_blocked_reason_other": "GitHub rejected the merge.",
|
|
1340
|
+
"wf_changelog.merge_blocked_comment": "**The release PR was not merged automatically** — {reason}\n\nThis PR was renamed to `chore(release): ...`, so a merge you make yourself still publishes the release. Once the requirements are met, merge it with **Create a merge commit** (recommended).\n\n- To let the workflow merge it next time, change the protection rule on the release branch so the account that merges (the `WORKFLOW_PAT` owner, or the GitHub Actions bot when no PAT is set) is allowed to merge.\n- To always merge by hand without this run failing, set `release_automerge: false` in `version.yml`.\n\nSquash or rebase merges may not publish the release by themselves. If a release was skipped, run **PROJECT-RELEASE-PUBLISH** manually on the release branch (Actions > Run workflow).",
|
|
1323
1341
|
# --- wf_readme ---
|
|
1324
1342
|
"wf_readme.not_found": "README.md not found — nothing to update, skipping",
|
|
1325
1343
|
"wf_readme.read_version_failed": "failed to read the current version",
|
|
@@ -1440,6 +1458,7 @@ EN = {
|
|
|
1440
1458
|
"wf_flutter.release_notes_heading": "📝 Release Notes:",
|
|
1441
1459
|
"wf_flutter.working_dir": "🔧 Working directory: {dir}",
|
|
1442
1460
|
"wf_flutter.run_number": "🔍 GitHub Run Number: {number}",
|
|
1461
|
+
"wf_flutter.github_sha": "🔍 GitHub SHA: {sha}",
|
|
1443
1462
|
"wf_flutter.ci_analyze_only": " Analyze Only: {value}",
|
|
1444
1463
|
}
|
|
1445
1464
|
|
|
@@ -1949,6 +1968,7 @@ KO = {
|
|
|
1949
1968
|
"flutter_a.ci_apk_start": "📦 Flutter APK 빌드 시작 (debug)...",
|
|
1950
1969
|
"flutter_a.ci_apk_done": "✅ APK 빌드 완료",
|
|
1951
1970
|
"flutter_a.ci_podfile_missing": "ℹ️ ios/Podfile이 없어 pod install을 건너뜁니다 (CocoaPods 플러그인 없음)",
|
|
1971
|
+
"flutter_a.ci_pods_installed": "✅ CocoaPods 설치 완료",
|
|
1952
1972
|
"flutter_a.ci_ios_start": "📦 Flutter iOS 빌드 시작...",
|
|
1953
1973
|
"flutter_a.ci_ios_done": "✅ iOS 빌드 완료",
|
|
1954
1974
|
"flutter_a.ci_gate_results": "needs 결과: {results}",
|
|
@@ -2026,7 +2046,8 @@ KO = {
|
|
|
2026
2046
|
"ios_test_tf.st_waiting": "⏸️ 대기",
|
|
2027
2047
|
"ios_test_tf.st_done": "✅ 완료",
|
|
2028
2048
|
"ios_test_tf.st_failed": "❌ 실패",
|
|
2029
|
-
"ios_test_tf.
|
|
2049
|
+
"ios_test_tf.st_not_run": "⏸️ 실행 안 됨 (이전 단계 실패)",
|
|
2050
|
+
"ios_test_tf.st_precheck_blocked": "⏸️ 실행 안 됨 (사전 점검 실패: {missing})",
|
|
2030
2051
|
"ios_test_tf.row_build_number": "| **빌드 번호** | `{build}` |",
|
|
2031
2052
|
"ios_test_tf.row_app_version": "| **앱 버전** | `{version}({build})` |",
|
|
2032
2053
|
"ios_test_tf.row_branch": "| **브랜치** | `{branch}` |",
|
|
@@ -2064,6 +2085,7 @@ KO = {
|
|
|
2064
2085
|
"ios_test_tf.notes_generated": "📋 생성된 릴리즈 노트:",
|
|
2065
2086
|
"ios_test_tf.title_prepare_failed": "## 🍎 ❌ iOS TestFlight 빌드 실패",
|
|
2066
2087
|
"ios_test_tf.prepare_error": "❌ **환경 준비 중 오류가 발생했습니다.**",
|
|
2088
|
+
"ios_test_tf.prepare_precheck_error": "❌ **사전 점검에 실패해 빌드를 시작하지 않았습니다.** 누락되었거나 올바르지 않은 항목: {missing}",
|
|
2067
2089
|
"ios_test_tf.run_log_check_link": "🔗 [워크플로우 실행 로그 확인]({url})",
|
|
2068
2090
|
"ios_test_tf.log_prepare_failed": "❌ 진행 상황 업데이트 완료: 준비 실패",
|
|
2069
2091
|
"ios_test_tf.log_prepare_done": "✅ 진행 상황 업데이트 완료: 준비 완료 ({elapsed})",
|
|
@@ -2157,6 +2179,7 @@ KO = {
|
|
|
2157
2179
|
"truncate.limit_positive": "ERROR: limit은 양수여야 함: {limit}",
|
|
2158
2180
|
# --- version ---
|
|
2159
2181
|
"version.bump_help": "승격 폭 (기본 patch — 지정 안 하면 기존 동작과 동일)",
|
|
2182
|
+
"version.option_default_help": "version.yml에 키가 없을 때 출력할 값 (기본 false)",
|
|
2160
2183
|
# --- wf_aisum ---
|
|
2161
2184
|
"wf_aisum.no_issue_in_branch": "브랜치명에서 이슈 번호를 찾지 못함 — 건너뜀: {ref}",
|
|
2162
2185
|
# --- wf_changelog ---
|
|
@@ -2322,7 +2345,8 @@ KO = {
|
|
|
2322
2345
|
"wf_preview.status_done": "✅ 완료",
|
|
2323
2346
|
"wf_preview.status_failed": "❌ 실패",
|
|
2324
2347
|
"wf_preview.status_init_failed": "⚠️ 초기화 실패",
|
|
2325
|
-
"wf_preview.
|
|
2348
|
+
"wf_preview.status_not_run": "⏸️ 실행 안 됨 (이전 단계 실패)",
|
|
2349
|
+
"wf_preview.status_precheck_blocked": "⏸️ 실행 안 됨 (사전 점검 실패: {missing})",
|
|
2326
2350
|
"wf_preview.tbl_commands": "| 명령어 | 설명 |",
|
|
2327
2351
|
"wf_preview.tbl_items": "| 항목 | 값 |",
|
|
2328
2352
|
"wf_preview.tbl_steps": "| 단계 | 상태 | 소요 시간 |",
|
|
@@ -2540,6 +2564,7 @@ KO = {
|
|
|
2540
2564
|
"version_manager.err_invalid_version": "오류: 올바르지 않은 버전 형식: {version}",
|
|
2541
2565
|
"version_manager.err_invalid_version_xyz": "오류: 올바르지 않은 버전 형식: {version} (x.y.z 형식이어야 합니다)",
|
|
2542
2566
|
"version_manager.err_generic": "오류: {error}",
|
|
2567
|
+
"version_manager.option_invalid": "::warning::version.yml의 {option} 값 \"{value}\"을(를) 인식하지 못해 false로 처리합니다. true 또는 false를 쓰세요.",
|
|
2543
2568
|
"version_manager.err_no_types_sync": "오류: version.yml에서 project_types를 읽을 수 없어 프로젝트 파일을 동기화할 수 없습니다",
|
|
2544
2569
|
"version_manager.err_no_types_check": "오류: version.yml에서 project_types를 읽을 수 없어 버전을 동기화할 수 없습니다",
|
|
2545
2570
|
"version_manager.err_write_failed": "version.yml에 버전 {version}을(를) 기록하지 못했습니다",
|
|
@@ -2565,11 +2590,15 @@ KO = {
|
|
|
2565
2590
|
"wf_common.rebase_ok": "rebase 성공, push를 다시 시도합니다...",
|
|
2566
2591
|
"wf_common.rebase_failed": "rebase 실패, 충돌을 직접 해결해야 합니다",
|
|
2567
2592
|
"wf_common.push_gave_up": "push가 {max_retries}회 시도 후에도 실패했습니다",
|
|
2593
|
+
"wf_common.push_protected": "{branch} 브랜치 보호 규칙이 push를 거부했습니다. 경합이 아니므로 재시도해도 소용없습니다. 봇 커밋은 {branch}에 직접 push되어야 합니다: push하는 계정(WORKFLOW_PAT 소유자, PAT가 없으면 GitHub Actions 봇)이 규칙을 우회할 수 있게 허용하세요.",
|
|
2594
|
+
"wf_release.push_protected_trunk": "trunk-based 모드는 모든 릴리스(버전 증가와 CHANGELOG)를 {branch}에 직접 커밋하므로 보호 규칙이 그 push를 허용해야 합니다. 허용할 수 없다면 봇이 릴리스 PR 브랜치에만 커밋하는 develop -> main 흐름으로 바꾸세요.",
|
|
2568
2595
|
"wf_common.readme_conflict_resolved": "동시에 수정된 README.md 충돌을 원격 내용을 유지하고 버전 줄을 다시 반영해 해결했습니다",
|
|
2569
2596
|
# --- wf_aisum ---
|
|
2570
2597
|
"wf_aisum.expected_version": "예상 다음 버전: {version} (현재: {current_version}, 모드: {mode}, 승격: {bump})",
|
|
2571
2598
|
"wf_aisum.comment_update_failed": "댓글 수정 실패 — 계속 진행합니다 (이 워크플로우는 PR을 막지 않습니다)",
|
|
2572
2599
|
"wf_aisum.comment_post_failed": "댓글 등록 실패 — 계속 진행합니다 (이 워크플로우는 PR을 막지 않습니다)",
|
|
2600
|
+
"wf_aisum.summary_heading": "📋 **PR 요약 (project-auto-wizard)**",
|
|
2601
|
+
"wf_aisum.engine_label": "엔진: {engine}",
|
|
2573
2602
|
# --- wf_changelog ---
|
|
2574
2603
|
"wf_changelog.pr_not_open": "PR #{pr_number}은(는) 이미 {state} 상태입니다 — 릴리스 파이프라인을 건너뜁니다",
|
|
2575
2604
|
"wf_changelog.pr_state": "PR #{pr_number} 상태: {state}",
|
|
@@ -2592,9 +2621,17 @@ KO = {
|
|
|
2592
2621
|
"wf_changelog.automerge_off_list_failed": "PR #{pr_number}의 댓글을 읽지 못해 중복을 피하려고 머지 안내 댓글을 건너뛰었습니다",
|
|
2593
2622
|
"wf_changelog.automerge_off_rename_hint": "PR 제목을 자동으로 바꾸지 못했습니다. 릴리스가 발행되도록 머지하기 전에 머지 커밋 메시지(또는 PR 제목)를 `{subject}`로 지정하세요.",
|
|
2594
2623
|
"wf_changelog.automerge_rename_failed": "PR #{pr_number}의 제목 또는 안내 댓글을 갱신하지 못했습니다. 릴리스가 되려면 머지 커밋 메시지에 \"chore(release):\"가 있어야 합니다",
|
|
2595
|
-
"wf_changelog.automerge_off_comment": "**자동 머지가 꺼져 있습니다** (`version.yml`의 `release_automerge: false`). 이 워크플로우 실행이 끝난 뒤(버전 확정과 PR
|
|
2624
|
+
"wf_changelog.automerge_off_comment": "**자동 머지가 꺼져 있습니다** (`version.yml`의 `release_automerge: false`). 이 워크플로우 실행이 끝난 뒤(버전 확정과 PR 제목이 `chore(release): ...`로 바뀐 뒤) 릴리스할 준비가 되면 **Create a merge commit**으로 직접 머지하는 것을 권장합니다.\n\n다른 머지 방식은 릴리스가 저절로 발행되지 않을 수 있습니다. *Rebase and merge*는 버전 확정 커밋(`[skip ci]`)이 마지막 커밋으로 남고, 기본 메시지(커밋 목록 포함)의 *Squash and merge*는 그 `[skip ci]`가 머지 커밋 본문에 들어가므로, 두 경우 모두 GitHub가 push 워크플로우를 건너뛰어 태그와 Release가 만들어지지 않습니다. 레포의 squash 메시지 기본값을 PR 제목으로 바꿨다면 squash는 발행됩니다. 건너뛰었다면 릴리스 브랜치에서 **PROJECT-RELEASE-PUBLISH**를 수동으로 실행(Actions > Run workflow)하면 발행됩니다. 두 방식은 다음 릴리스 노트가 기대는 커밋 이력도 바꾸어 이전 변경이 다시 기재될 수 있습니다(rebase는 develop이 릴리스 브랜치를 역머지하기 전까지는 제외됩니다). 이 실행이 끝나기 전에 머지해도 비슷한 문제가 생깁니다. 안전망 워크플로우(PROJECT-VERSION-CONTROL)가 패치 버전을 하나 더 발행합니다.",
|
|
2596
2625
|
"wf_changelog.merge_retry": "병합 시도 {attempt}/{max_attempts} 실패 — {wait}초 후 다시 시도합니다",
|
|
2626
|
+
"wf_changelog.head_updated": "기준 브랜치의 변경을 head 브랜치에 반영했습니다",
|
|
2597
2627
|
"wf_changelog.merge_gave_up": "PR 병합이 {max_attempts}회 시도 후에도 실패했습니다 (allow_merge_commit={allow_merge_commit}) — 브랜치 보호 규칙, 필수 체크, 토큰 권한을 확인하세요",
|
|
2628
|
+
"wf_changelog.merge_blocked_review": "릴리스 PR을 자동으로 머지할 수 없습니다: {prod}의 브랜치 보호 규칙이 승인(approving review)을 요구합니다. 승인한 뒤 merge commit으로 직접 머지하거나, 머지하는 계정이 승인 없이 머지할 수 있도록 규칙을 바꾸세요. 이 시도를 끄려면 version.yml에 release_automerge: false를 설정하세요.",
|
|
2629
|
+
"wf_changelog.merge_blocked_policy": "릴리스 PR을 자동으로 머지할 수 없습니다: {prod}의 브랜치 보호 규칙이 머지를 막고 있습니다 (필수 체크, 리뷰 또는 다른 규칙). 조건이 충족되면 merge commit으로 직접 머지하거나, 머지하는 계정이 허용되도록 규칙을 바꾸세요. 이 시도를 끄려면 version.yml에 release_automerge: false를 설정하세요.",
|
|
2630
|
+
"wf_changelog.automerge_blocked_renamed": "automerge가 막혀 PR 제목을 \"{subject}\"로 바꿨습니다. 조건이 충족되면 직접 머지하세요",
|
|
2631
|
+
"wf_changelog.merge_blocked_reason_review": "릴리스 브랜치가 승인(approving review)을 요구합니다.",
|
|
2632
|
+
"wf_changelog.merge_blocked_reason_policy": "릴리스 브랜치의 보호 규칙이 머지를 막고 있습니다 (필수 체크, 리뷰 또는 다른 규칙).",
|
|
2633
|
+
"wf_changelog.merge_blocked_reason_other": "GitHub이 머지를 거부했습니다.",
|
|
2634
|
+
"wf_changelog.merge_blocked_comment": "**릴리스 PR이 자동으로 머지되지 않았습니다** — {reason}\n\n이 PR의 제목을 `chore(release): ...`로 바꿔 두었으므로, 직접 머지해도 릴리스가 발행됩니다. 조건이 충족되면 **Create a merge commit**(권장)으로 머지하세요.\n\n- 다음부터 워크플로우가 머지하게 하려면, 릴리스 브랜치의 보호 규칙에서 머지하는 계정(`WORKFLOW_PAT` 소유자, PAT가 없으면 GitHub Actions 봇)이 머지할 수 있도록 바꾸세요.\n- 항상 직접 머지하고 이 실행이 실패로 끝나지 않게 하려면 `version.yml`에 `release_automerge: false`를 설정하세요.\n\nSquash나 rebase 머지는 릴리스가 자동으로 발행되지 않을 수 있습니다. 릴리스가 건너뛰어졌다면 릴리스 브랜치에서 **PROJECT-RELEASE-PUBLISH**를 수동 실행하세요 (Actions > Run workflow).",
|
|
2598
2635
|
# --- wf_readme ---
|
|
2599
2636
|
"wf_readme.not_found": "README.md가 없습니다 — 수정할 것이 없어 건너뜁니다",
|
|
2600
2637
|
"wf_readme.read_version_failed": "현재 버전을 읽지 못했습니다",
|
|
@@ -2715,6 +2752,7 @@ KO = {
|
|
|
2715
2752
|
"wf_flutter.release_notes_heading": "📝 릴리스 노트:",
|
|
2716
2753
|
"wf_flutter.working_dir": "🔧 작업 디렉터리: {dir}",
|
|
2717
2754
|
"wf_flutter.run_number": "🔍 GitHub 실행 번호: {number}",
|
|
2755
|
+
"wf_flutter.github_sha": "🔍 커밋 SHA: {sha}",
|
|
2718
2756
|
"wf_flutter.ci_analyze_only": " 분석 전용: {value}",
|
|
2719
2757
|
}
|
|
2720
2758
|
|
|
@@ -22,6 +22,7 @@ Usage:
|
|
|
22
22
|
version_manager.py increment-code # version_code+1 only
|
|
23
23
|
version_manager.py set X.Y.Z # set version explicitly, sync
|
|
24
24
|
version_manager.py sync # sync version.yml <-> project files
|
|
25
|
+
version_manager.py option KEY [--default true|false] # boolean option from metadata.template.options
|
|
25
26
|
|
|
26
27
|
Contract:
|
|
27
28
|
- The LAST line printed to stdout is always the value (callers do `| tail -n 1`).
|
|
@@ -189,6 +190,58 @@ def key_exists(key):
|
|
|
189
190
|
return re.search(r'^' + re.escape(key) + r':', text, re.MULTILINE) is not None
|
|
190
191
|
|
|
191
192
|
|
|
193
|
+
def parse_option_value(raw):
|
|
194
|
+
"""One boolean option value -> True / False / None (None = not recognized).
|
|
195
|
+
|
|
196
|
+
Same rule as parseOptionValue() in src/core/options.js (tests/fixtures/option-value-cases.json runs both):
|
|
197
|
+
an inline comment (whitespace + #) is dropped, then one pair of matching quotes, then case is ignored.
|
|
198
|
+
Only true / false are recognized; yes, off, 0, maybe and an empty value are not."""
|
|
199
|
+
v = re.sub(r'(^|\s)#.*$', '', str(raw if raw is not None else '')).strip()
|
|
200
|
+
q = re.match(r'^(["\'])(.*)\1$', v)
|
|
201
|
+
if q:
|
|
202
|
+
v = q.group(2).strip()
|
|
203
|
+
v = v.lower()
|
|
204
|
+
if v == 'true':
|
|
205
|
+
return True
|
|
206
|
+
if v == 'false':
|
|
207
|
+
return False
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def read_option(key, default, text=None):
|
|
212
|
+
"""(value, raw) for a boolean under metadata.template.options in version.yml.
|
|
213
|
+
|
|
214
|
+
- key missing: (default, None)
|
|
215
|
+
- key written with a recognized value: (that value, None)
|
|
216
|
+
- key written with an unrecognized value: (False, raw) - the conservative side, reported by the caller;
|
|
217
|
+
a present-but-unreadable value is not a missing key, so the default must not be guessed for it
|
|
218
|
+
Mirrors the section walk of parseTemplateOptions() in src/core/version-yml.js; the first occurrence wins."""
|
|
219
|
+
if text is None:
|
|
220
|
+
text = read_text()
|
|
221
|
+
line_re = re.compile(r'^\s+' + re.escape(key) + r':\s*(.*)$')
|
|
222
|
+
in_template = in_options = False
|
|
223
|
+
for line in text.split("\n"):
|
|
224
|
+
if re.match(r'^\s*template:', line):
|
|
225
|
+
in_template = True
|
|
226
|
+
continue
|
|
227
|
+
if in_template and re.match(r'^\s+options:', line):
|
|
228
|
+
in_options = True
|
|
229
|
+
continue
|
|
230
|
+
if in_template and in_options:
|
|
231
|
+
m = line_re.match(line)
|
|
232
|
+
if m:
|
|
233
|
+
raw = m.group(1).strip()
|
|
234
|
+
parsed = parse_option_value(raw)
|
|
235
|
+
return (False, raw) if parsed is None else (parsed, None)
|
|
236
|
+
# another key indented 0-4 spaces ends the options section
|
|
237
|
+
if re.match(r'^\s{0,4}[a-z_]+:', line):
|
|
238
|
+
in_options = in_template = False
|
|
239
|
+
# a top-level key ends the template section
|
|
240
|
+
if in_template and re.match(r'^[a-z_]+:', line):
|
|
241
|
+
in_template = in_options = False
|
|
242
|
+
return default, None
|
|
243
|
+
|
|
244
|
+
|
|
192
245
|
def get_current_version():
|
|
193
246
|
return read_scalar_key("version", "0.0.0")
|
|
194
247
|
|
|
@@ -278,13 +331,13 @@ def get_version_code():
|
|
|
278
331
|
if re.search(r'^version:', text, re.MULTILINE):
|
|
279
332
|
new_text = re.sub(
|
|
280
333
|
r'^(version:[^\n]*\n)',
|
|
281
|
-
r'\1version_code: 1
|
|
334
|
+
r'\1version_code: 1 # app build number\n',
|
|
282
335
|
text,
|
|
283
336
|
count=1,
|
|
284
337
|
flags=re.MULTILINE,
|
|
285
338
|
)
|
|
286
339
|
else:
|
|
287
|
-
new_text = text.rstrip("\n") + '\nversion_code: 1
|
|
340
|
+
new_text = text.rstrip("\n") + '\nversion_code: 1 # app build number\n'
|
|
288
341
|
write_text(new_text)
|
|
289
342
|
return "1"
|
|
290
343
|
return code.strip()
|
|
@@ -300,7 +353,7 @@ def set_version_code(new_code):
|
|
|
300
353
|
pass
|
|
301
354
|
text = read_text()
|
|
302
355
|
pattern = re.compile(r'^version_code:[ \t]*.*$', re.MULTILINE)
|
|
303
|
-
replacement = f'version_code: {new_code}
|
|
356
|
+
replacement = f'version_code: {new_code} # app build number'
|
|
304
357
|
if pattern.search(text):
|
|
305
358
|
new_text = pattern.sub(replacement, text, count=1)
|
|
306
359
|
else:
|
|
@@ -1011,6 +1064,20 @@ def cmd_sync(args):
|
|
|
1011
1064
|
return 0
|
|
1012
1065
|
|
|
1013
1066
|
|
|
1067
|
+
def cmd_option(args):
|
|
1068
|
+
"""Print `true` / `false` for one boolean option; a missing version.yml or key prints the default.
|
|
1069
|
+
An unrecognized value prints false and a warning on stderr (stdout stays the value contract)."""
|
|
1070
|
+
default = args.default == "true"
|
|
1071
|
+
if not _version_yml_path().is_file():
|
|
1072
|
+
print("true" if default else "false")
|
|
1073
|
+
return 0
|
|
1074
|
+
value, raw = read_option(args.key, default)
|
|
1075
|
+
if raw is not None:
|
|
1076
|
+
log(t("version_manager.option_invalid", option=args.key, value=raw))
|
|
1077
|
+
print("true" if value else "false")
|
|
1078
|
+
return 0
|
|
1079
|
+
|
|
1080
|
+
|
|
1014
1081
|
def build_parser():
|
|
1015
1082
|
parser = argparse.ArgumentParser(prog="version_manager.py")
|
|
1016
1083
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
@@ -1023,6 +1090,11 @@ def build_parser():
|
|
|
1023
1090
|
sub.add_parser("increment-code")
|
|
1024
1091
|
sub.add_parser("sync")
|
|
1025
1092
|
|
|
1093
|
+
p_option = sub.add_parser("option")
|
|
1094
|
+
p_option.add_argument("key")
|
|
1095
|
+
p_option.add_argument("--default", choices=["true", "false"], default="false",
|
|
1096
|
+
help=t("version.option_default_help"))
|
|
1097
|
+
|
|
1026
1098
|
p_set = sub.add_parser("set")
|
|
1027
1099
|
p_set.add_argument("version")
|
|
1028
1100
|
|
|
@@ -1041,6 +1113,7 @@ def main(argv=None):
|
|
|
1041
1113
|
"increment-code": cmd_increment_code,
|
|
1042
1114
|
"set": cmd_set,
|
|
1043
1115
|
"sync": cmd_sync,
|
|
1116
|
+
"option": cmd_option,
|
|
1044
1117
|
}
|
|
1045
1118
|
handler = handlers[args.command]
|
|
1046
1119
|
try:
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
# Falls back to rule-based summaries whenever Copilot is off or unavailable.
|
|
12
12
|
# - release_automerge: default true — false stops the release PR (develop -> main)
|
|
13
13
|
# from being merged automatically. Merge it yourself with a merge commit
|
|
14
|
-
# (
|
|
14
|
+
# (recommended); squash and rebase merges skip the release workflow, so run
|
|
15
|
+
# PROJECT-RELEASE-PUBLISH manually if you used one.
|
|
15
16
|
# ===
|
|
16
17
|
# Supported project types: spring, flutter, react, react-native,
|
|
17
18
|
# react-native-expo, node, python, basic, go
|
|
@@ -41,7 +42,7 @@ language: "{{LANGUAGE}}" # message language: en | ko
|
|
|
41
42
|
{{PROJECT_PATHS}}
|
|
42
43
|
metadata:
|
|
43
44
|
last_updated: "{{NOW}}"
|
|
44
|
-
last_updated_by: "
|
|
45
|
+
last_updated_by: "{{LAST_UPDATED_BY}}"
|
|
45
46
|
default_branch: "{{DEFAULT_BRANCH}}"
|
|
46
47
|
integration_date: "{{TODAY}}"
|
|
47
48
|
template:
|
|
@@ -57,7 +57,7 @@ jobs:
|
|
|
57
57
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
58
58
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
59
59
|
# unrelated "copilot_ai:" key can never hijack the value
|
|
60
|
-
COPILOT_AI=$(python3
|
|
60
|
+
COPILOT_AI=$(python3 "$GITHUB_WORKSPACE/.github/scripts/version_manager.py" option copilot_ai --default false || echo "false")
|
|
61
61
|
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
62
62
|
echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
|
|
63
63
|
|
|
@@ -81,11 +81,11 @@ jobs:
|
|
|
81
81
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
82
82
|
|
|
83
83
|
git fetch origin {{MAIN_BRANCH}}
|
|
84
|
-
git log --pretty=%s "origin/{{MAIN_BRANCH}}
|
|
84
|
+
git log --cherry-pick --right-only --pretty=%s "origin/{{MAIN_BRANCH}}...HEAD" > commits.txt
|
|
85
85
|
# Body-footer BREAKING CHANGE: lines are appended after the subject list, one per
|
|
86
86
|
# line. The script reads each line as one entry, so passing the whole body (%B)
|
|
87
87
|
# would turn description lines into change entries.
|
|
88
|
-
git log --pretty=%b "origin/{{MAIN_BRANCH}}
|
|
88
|
+
git log --cherry-pick --right-only --pretty=%b "origin/{{MAIN_BRANCH}}...HEAD" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
|
|
89
89
|
git diff --stat "origin/{{MAIN_BRANCH}}...HEAD" > _full_diff_stat.txt
|
|
90
90
|
if [ "$(wc -l < _full_diff_stat.txt)" -gt 49 ]; then
|
|
91
91
|
{ head -49 _full_diff_stat.txt; tail -1 _full_diff_stat.txt; } > diff_stat.txt
|
|
@@ -102,7 +102,7 @@ jobs:
|
|
|
102
102
|
# so rules alone are used, without any AI-assisted classification.
|
|
103
103
|
CURRENT_VERSION=$(python3 .github/scripts/version_manager.py get 2>/dev/null | tail -n 1)
|
|
104
104
|
MODE=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?branches:.*?mode:\s*\"?([A-Za-z-]+)", t, re.S); print(m.group(1) if m else "pr-flow")' 2>/dev/null || echo "pr-flow")
|
|
105
|
-
SEMVER_AUTO=$(python3
|
|
105
|
+
SEMVER_AUTO=$(python3 .github/scripts/version_manager.py option semver_auto --default false || echo "false")
|
|
106
106
|
BUMP="patch"
|
|
107
107
|
if [ "$MODE" = "trunk-based" ] && [ "$SEMVER_AUTO" = "true" ]; then
|
|
108
108
|
BUMP=$(AI_API_KEY="" COPILOT_AI="false" python3 .github/scripts/changelog_manager.py classify-bump --commits-file commits.txt 2>/dev/null | tail -n 1)
|
|
@@ -121,17 +121,17 @@ jobs:
|
|
|
121
121
|
# On fallback, append the reason (the script also emits it as ::warning::) to the engine line so the cause is visible without opening the job log
|
|
122
122
|
FALLBACK_REASON=$(python3 -c 'import html,json,sys; print(html.escape((json.loads(sys.argv[1]).get("fallback_reason") or "")[:200]))' "$SUMMARY_RESULT" 2>/dev/null || echo "")
|
|
123
123
|
ENGINE_LINE="$ENGINE${FALLBACK_REASON:+ ($FALLBACK_REASON)}"
|
|
124
|
-
|
|
124
|
+
m wf_aisum.engine_label engine="$ENGINE_LINE" >> "$GITHUB_STEP_SUMMARY"
|
|
125
125
|
|
|
126
126
|
# Find this bot's earlier summary comment by its marker and replace it with the latest content so pushes do not pile up comments
|
|
127
127
|
MARKER="<!-- project-auto-wizard:pr-summary -->"
|
|
128
128
|
{
|
|
129
129
|
echo "$MARKER"
|
|
130
|
-
|
|
130
|
+
m wf_aisum.summary_heading
|
|
131
131
|
echo ""
|
|
132
132
|
cat summary.md
|
|
133
133
|
echo ""
|
|
134
|
-
echo "<sub
|
|
134
|
+
echo "<sub>$(m wf_aisum.engine_label engine="$ENGINE_LINE")</sub>"
|
|
135
135
|
} > comment_body.md
|
|
136
136
|
|
|
137
137
|
python3 -c 'import json; print(json.dumps({"body": open("comment_body.md", encoding="utf-8").read()}))' > comment_payload.json
|