project-auto-wizard 0.13.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/project-auto-wizard.js +18 -4
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +28 -14
- package/payload/config/wizard-prompts.yml +102 -46
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
- package/payload/flutter-app/ios/ExportOptions.plist +8 -8
- package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
- package/payload/scripts/changelog_manager.py +296 -257
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2279 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +64 -61
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +17 -12
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +59 -34
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +17 -10
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +54 -44
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +24 -17
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
- package/src/cli/args.js +98 -52
- package/src/cli/help.js +8 -41
- package/src/commands/doctor.js +85 -89
- package/src/commands/dry-run.js +63 -43
- package/src/commands/full.js +79 -77
- package/src/commands/install-settings.js +16 -15
- package/src/commands/interactive-flutter.js +20 -19
- package/src/commands/interactive.js +135 -132
- package/src/commands/purge.js +47 -42
- package/src/commands/status.js +36 -36
- package/src/commands/uninstall.js +43 -41
- package/src/context.js +12 -11
- package/src/core/assets.js +12 -11
- package/src/core/baseline.js +27 -27
- package/src/core/branches.js +29 -27
- package/src/core/branding.js +7 -6
- package/src/core/breaking-check.js +22 -21
- package/src/core/breaking.js +15 -8
- package/src/core/copy/app-files.js +5 -4
- package/src/core/copy/flutter-app.js +8 -8
- package/src/core/copy/gitignore.js +58 -50
- package/src/core/copy/readme.js +45 -35
- package/src/core/copy/simple.js +16 -15
- package/src/core/copy/workflows.js +131 -124
- package/src/core/deploy-style.js +51 -48
- package/src/core/detect-fs.js +57 -57
- package/src/core/detect.js +67 -65
- package/src/core/errors.js +2 -2
- package/src/core/flutter-doctor.js +17 -16
- package/src/core/flutter-hooks.js +33 -32
- package/src/core/flutter-options.js +34 -33
- package/src/core/fsutil.js +12 -12
- package/src/core/installed-stores.js +4 -4
- package/src/core/logger.js +42 -40
- package/src/core/paths-resolve.js +88 -86
- package/src/core/paths.js +10 -10
- package/src/core/release-options.js +8 -8
- package/src/core/removal-exec.js +14 -13
- package/src/core/removal-plan.js +37 -37
- package/src/core/types.js +47 -47
- package/src/core/verify.js +44 -38
- package/src/core/version-yml.js +76 -68
- package/src/core/wizard-env.js +46 -46
- package/src/core/wizard-labels.js +63 -41
- package/src/i18n/catalog/en/cli.js +50 -0
- package/src/i18n/catalog/en/commands.js +187 -0
- package/src/i18n/catalog/en/copy.js +19 -0
- package/src/i18n/catalog/en/core.js +74 -0
- package/src/i18n/catalog/en/core2.js +15 -0
- package/src/i18n/catalog/en/ui.js +221 -0
- package/src/i18n/catalog/en.js +12 -0
- package/src/i18n/catalog/index.js +5 -0
- package/src/i18n/catalog/ko/cli.js +50 -0
- package/src/i18n/catalog/ko/commands.js +187 -0
- package/src/i18n/catalog/ko/copy.js +19 -0
- package/src/i18n/catalog/ko/core.js +74 -0
- package/src/i18n/catalog/ko/core2.js +15 -0
- package/src/i18n/catalog/ko/ui.js +221 -0
- package/src/i18n/catalog/ko.js +10 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/languages.js +9 -0
- package/src/index.js +128 -94
- package/src/ui/ansi.js +7 -7
- package/src/ui/banner.js +7 -6
- package/src/ui/env-plan.js +82 -81
- package/src/ui/prompts.js +97 -122
- package/src/ui/readline-engine.js +59 -58
- package/src/ui/status-cards.js +37 -31
- package/src/ui/summary.js +89 -88
|
@@ -42,6 +42,8 @@ import urllib.error
|
|
|
42
42
|
import urllib.request
|
|
43
43
|
from datetime import datetime, timedelta, timezone
|
|
44
44
|
|
|
45
|
+
from messages import t, use_utf8_output
|
|
46
|
+
|
|
45
47
|
KST = timezone(timedelta(hours=9))
|
|
46
48
|
COMMENT_MARKER_DEFAULT = "<!-- project-auto-wizard issue helper -->"
|
|
47
49
|
API_BASE = "https://api.github.com"
|
|
@@ -52,7 +54,7 @@ def log(message):
|
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
# ===================================================================
|
|
55
|
-
#
|
|
57
|
+
# Normalization (pure functions — no GitHub API calls)
|
|
56
58
|
# ===================================================================
|
|
57
59
|
|
|
58
60
|
def extract_issue_number(issue_url):
|
|
@@ -79,10 +81,10 @@ _ZWJ = ""
|
|
|
79
81
|
|
|
80
82
|
|
|
81
83
|
def _is_removable_char(ch):
|
|
82
|
-
#
|
|
83
|
-
# Python re
|
|
84
|
-
# "So"(Symbol, other)
|
|
85
|
-
#
|
|
84
|
+
# Port of the original (TS) regex \p{So}|\p{C}|️|.
|
|
85
|
+
# Python's re has no \p{...}, so unicodedata.category() is used instead:
|
|
86
|
+
# "So" (Symbol, other) or any category starting with "C" (Cc/Cf/Co/Cs/Cn) is removed.
|
|
87
|
+
# The variation selector (U+FE0F, category Mn) matches neither, so it is listed explicitly.
|
|
86
88
|
if ch in (_VARIATION_SELECTOR, _ZWJ):
|
|
87
89
|
return True
|
|
88
90
|
category = unicodedata.category(ch)
|
|
@@ -99,11 +101,12 @@ def format_date_yyyymmdd(dt):
|
|
|
99
101
|
return dt.strftime("%Y%m%d")
|
|
100
102
|
|
|
101
103
|
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
+
# Keep every Unicode letter and digit. Keeping only Hangul and Latin made titles in
|
|
105
|
+
# other languages (e.g. Japanese) vanish entirely, leaving a branch name ending in `_`
|
|
106
|
+
# and an empty commit title.
|
|
104
107
|
_NON_WORD_RE = re.compile(r"[\W_]+")
|
|
105
108
|
_MULTI_UNDERSCORE_RE = re.compile(r"_+")
|
|
106
|
-
#
|
|
109
|
+
# Name used when normalization leaves nothing (e.g. a title made only of emoji or symbols)
|
|
107
110
|
FALLBACK_TITLE = "issue"
|
|
108
111
|
|
|
109
112
|
|
|
@@ -116,7 +119,7 @@ def normalize_title(title):
|
|
|
116
119
|
def create_branch_name(issue_title, issue_number, date_yyyymmdd, branch_prefix, max_branch_length):
|
|
117
120
|
normalized_title = normalize_title(issue_title) or FALLBACK_TITLE
|
|
118
121
|
base = f"{date_yyyymmdd}_#{issue_number}_{normalized_title}"
|
|
119
|
-
#
|
|
122
|
+
# If the cut lands on a separator, do not leave the name ending in `_`.
|
|
120
123
|
limited_base = base[:max_branch_length].rstrip("_") if max_branch_length > 0 else base
|
|
121
124
|
return f"{branch_prefix}{limited_base}"
|
|
122
125
|
|
|
@@ -141,7 +144,7 @@ def normalize_all(title, issue_url, issue_number, date_yyyymmdd, branch_prefix,
|
|
|
141
144
|
|
|
142
145
|
|
|
143
146
|
# ===================================================================
|
|
144
|
-
#
|
|
147
|
+
# Linking issues in a PR body (Closes #N) — insert/replace a marker block
|
|
145
148
|
# ===================================================================
|
|
146
149
|
|
|
147
150
|
LINK_MARKER_START = "<!-- auto-issue-link:start -->"
|
|
@@ -158,11 +161,10 @@ def upsert_issue_links_in_body(body, issue_numbers, replace_existing):
|
|
|
158
161
|
if not issue_numbers:
|
|
159
162
|
return body, False
|
|
160
163
|
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
# "성공"처럼 보이는 버그가 생긴다.
|
|
164
|
+
# Treat the marker as present only for a complete START...END block. Mistaking a
|
|
165
|
+
# damaged state (START without END) for a marker makes _LINK_BLOCK_RE.sub() replace
|
|
166
|
+
# nothing yet return changed=True, so on the replace_existing=True path (release PR)
|
|
167
|
+
# the new issue list is never applied while the run silently looks successful.
|
|
166
168
|
has_full_block = _LINK_BLOCK_RE.search(body) is not None
|
|
167
169
|
if has_full_block and not replace_existing:
|
|
168
170
|
return body, False
|
|
@@ -177,7 +179,7 @@ def upsert_issue_links_in_body(body, issue_numbers, replace_existing):
|
|
|
177
179
|
|
|
178
180
|
|
|
179
181
|
# ===================================================================
|
|
180
|
-
# GitHub REST API (urllib.request + GITHUB_TOKEN,
|
|
182
|
+
# GitHub REST API (urllib.request + GITHUB_TOKEN, no third-party dependencies)
|
|
181
183
|
# ===================================================================
|
|
182
184
|
|
|
183
185
|
def _headers(token, has_body):
|
|
@@ -224,7 +226,7 @@ def list_comments(owner, repo, issue_number, token):
|
|
|
224
226
|
while url:
|
|
225
227
|
status, page, link = _api_request("GET", url, token)
|
|
226
228
|
if status >= 400:
|
|
227
|
-
raise RuntimeError(
|
|
229
|
+
raise RuntimeError(t("issue_helper.err_list_comments", status=status, issue_number=issue_number))
|
|
228
230
|
comments.extend(page or [])
|
|
229
231
|
url = _find_next_link(link)
|
|
230
232
|
return comments
|
|
@@ -238,15 +240,15 @@ def upsert_comment(owner, repo, issue_number, token, marker, body):
|
|
|
238
240
|
"PATCH", f"{API_BASE}/repos/{owner}/{repo}/issues/comments/{existing['id']}", token, {"body": body},
|
|
239
241
|
)
|
|
240
242
|
if status >= 400:
|
|
241
|
-
raise RuntimeError(
|
|
242
|
-
log("
|
|
243
|
+
raise RuntimeError(t("issue_helper.err_update_comment", status=status))
|
|
244
|
+
log(t("issue_helper.comment_updated"))
|
|
243
245
|
else:
|
|
244
246
|
status, _, _ = _api_request(
|
|
245
247
|
"POST", f"{API_BASE}/repos/{owner}/{repo}/issues/{issue_number}/comments", token, {"body": body},
|
|
246
248
|
)
|
|
247
249
|
if status >= 400:
|
|
248
|
-
raise RuntimeError(
|
|
249
|
-
log("
|
|
250
|
+
raise RuntimeError(t("issue_helper.err_create_comment", status=status))
|
|
251
|
+
log(t("issue_helper.comment_created"))
|
|
250
252
|
|
|
251
253
|
|
|
252
254
|
def create_branch_if_needed(owner, repo, branch_name, base_branch, create_branch, token):
|
|
@@ -257,12 +259,12 @@ def create_branch_if_needed(owner, repo, branch_name, base_branch, create_branch
|
|
|
257
259
|
if not base:
|
|
258
260
|
status, repo_data, _ = _api_request("GET", f"{API_BASE}/repos/{owner}/{repo}", token)
|
|
259
261
|
if status >= 400:
|
|
260
|
-
raise RuntimeError(
|
|
262
|
+
raise RuntimeError(t("issue_helper.err_repo_info", status=status))
|
|
261
263
|
base = repo_data["default_branch"]
|
|
262
264
|
|
|
263
265
|
status, ref_data, _ = _api_request("GET", f"{API_BASE}/repos/{owner}/{repo}/git/ref/heads/{base}", token)
|
|
264
266
|
if status >= 400:
|
|
265
|
-
raise RuntimeError(
|
|
267
|
+
raise RuntimeError(t("issue_helper.err_base_ref", base=base, status=status))
|
|
266
268
|
sha = ref_data["object"]["sha"]
|
|
267
269
|
|
|
268
270
|
status, _, _ = _api_request(
|
|
@@ -270,28 +272,29 @@ def create_branch_if_needed(owner, repo, branch_name, base_branch, create_branch
|
|
|
270
272
|
{"ref": f"refs/heads/{branch_name}", "sha": sha},
|
|
271
273
|
)
|
|
272
274
|
if status == 422:
|
|
273
|
-
log(
|
|
275
|
+
log(t("issue_helper.branch_exists", branch_name=branch_name))
|
|
274
276
|
return
|
|
275
277
|
if status >= 400:
|
|
276
|
-
raise RuntimeError(
|
|
277
|
-
log(
|
|
278
|
+
raise RuntimeError(t("issue_helper.err_create_branch", branch_name=branch_name, status=status))
|
|
279
|
+
log(t("issue_helper.branch_created", branch_name=branch_name))
|
|
278
280
|
|
|
279
281
|
|
|
280
282
|
def filter_existing_issues(owner, repo, issue_numbers, token):
|
|
281
|
-
"""
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
"""Keep only issue numbers that really exist in the repo. If the number in a branch
|
|
284
|
+
name is a typo or refers to another repo, we would attach Closes to a missing issue
|
|
285
|
+
and record it as "linked". If the lookup itself fails (permissions, transient
|
|
286
|
+
error) we cannot verify, so the number is kept as is."""
|
|
284
287
|
kept = []
|
|
285
288
|
for n in issue_numbers:
|
|
286
289
|
status, data, _ = _api_request("GET", f"{API_BASE}/repos/{owner}/{repo}/issues/{n}", token)
|
|
287
290
|
if status in (404, 410):
|
|
288
|
-
print(
|
|
291
|
+
print(t("issue_helper.warn_issue_missing", n=n), file=sys.stderr)
|
|
289
292
|
continue
|
|
290
293
|
if status < 400 and isinstance(data, dict) and data.get("pull_request"):
|
|
291
|
-
print(
|
|
294
|
+
print(t("issue_helper.warn_is_pr", n=n), file=sys.stderr)
|
|
292
295
|
continue
|
|
293
296
|
if status >= 400:
|
|
294
|
-
log(
|
|
297
|
+
log(t("issue_helper.issue_check_failed", n=n, status=status))
|
|
295
298
|
kept.append(n)
|
|
296
299
|
return kept
|
|
297
300
|
|
|
@@ -299,29 +302,29 @@ def filter_existing_issues(owner, repo, issue_numbers, token):
|
|
|
299
302
|
def link_pr_issues(owner, repo, pr_number, issue_numbers, token, replace_existing):
|
|
300
303
|
issue_numbers = filter_existing_issues(owner, repo, issue_numbers, token)
|
|
301
304
|
if not issue_numbers:
|
|
302
|
-
log(
|
|
305
|
+
log(t("issue_helper.no_issue_to_link", pr_number=pr_number))
|
|
303
306
|
return
|
|
304
307
|
|
|
305
308
|
status, pr_data, _ = _api_request("GET", f"{API_BASE}/repos/{owner}/{repo}/pulls/{pr_number}", token)
|
|
306
309
|
if status >= 400:
|
|
307
|
-
raise RuntimeError(
|
|
310
|
+
raise RuntimeError(t("issue_helper.err_get_pr", status=status, pr_number=pr_number))
|
|
308
311
|
body = pr_data.get("body") or ""
|
|
309
312
|
|
|
310
313
|
new_body, changed = upsert_issue_links_in_body(body, issue_numbers, replace_existing)
|
|
311
314
|
if not changed:
|
|
312
|
-
log(
|
|
315
|
+
log(t("issue_helper.link_unchanged", pr_number=pr_number))
|
|
313
316
|
return
|
|
314
317
|
|
|
315
318
|
status, _, _ = _api_request(
|
|
316
319
|
"PATCH", f"{API_BASE}/repos/{owner}/{repo}/pulls/{pr_number}", token, {"body": new_body},
|
|
317
320
|
)
|
|
318
321
|
if status >= 400:
|
|
319
|
-
raise RuntimeError(
|
|
320
|
-
log(
|
|
322
|
+
raise RuntimeError(t("issue_helper.err_update_pr", status=status, pr_number=pr_number))
|
|
323
|
+
log(t("issue_helper.link_done", numbers=', '.join(f'#{n}' for n in issue_numbers), pr_number=pr_number))
|
|
321
324
|
|
|
322
325
|
|
|
323
326
|
# ===================================================================
|
|
324
|
-
#
|
|
327
|
+
# Event handling / CLI
|
|
325
328
|
# ===================================================================
|
|
326
329
|
|
|
327
330
|
def _bool_env(name, default):
|
|
@@ -334,13 +337,13 @@ def _bool_env(name, default):
|
|
|
334
337
|
def cmd_run():
|
|
335
338
|
event_path = os.environ.get("GITHUB_EVENT_PATH")
|
|
336
339
|
if not event_path:
|
|
337
|
-
log("
|
|
340
|
+
log(t("issue_helper.err_no_event_path"))
|
|
338
341
|
return 1
|
|
339
342
|
try:
|
|
340
343
|
with open(event_path, "r", encoding="utf-8") as f:
|
|
341
344
|
payload = json.load(f)
|
|
342
345
|
except (OSError, json.JSONDecodeError) as e:
|
|
343
|
-
log(
|
|
346
|
+
log(t("issue_helper.err_event_payload", error=e))
|
|
344
347
|
return 1
|
|
345
348
|
|
|
346
349
|
action = payload.get("action")
|
|
@@ -350,18 +353,18 @@ def cmd_run():
|
|
|
350
353
|
is_edited_with_title = action == "edited" and "title" in changes
|
|
351
354
|
|
|
352
355
|
if not is_opened and not is_edited_with_title:
|
|
353
|
-
log("
|
|
356
|
+
log(t("issue_helper.not_relevant_event"))
|
|
354
357
|
return 0
|
|
355
358
|
|
|
356
359
|
repo_full = os.environ.get("GITHUB_REPOSITORY", "")
|
|
357
360
|
if "/" not in repo_full or repo_full.count("/") != 1 or not all(repo_full.split("/")):
|
|
358
|
-
log(
|
|
361
|
+
log(t("issue_helper.err_repo_format", repo=repr(repo_full)))
|
|
359
362
|
return 1
|
|
360
363
|
owner, repo = repo_full.split("/", 1)
|
|
361
364
|
|
|
362
365
|
token = os.environ.get("GITHUB_TOKEN", "").strip()
|
|
363
366
|
if not token:
|
|
364
|
-
log("
|
|
367
|
+
log(t("issue_helper.err_no_token"))
|
|
365
368
|
return 1
|
|
366
369
|
|
|
367
370
|
raw_title = issue.get("title", "")
|
|
@@ -375,7 +378,7 @@ def cmd_run():
|
|
|
375
378
|
max_branch_length = int(os.environ.get("ISSUE_HELPER_MAX_BRANCH_LENGTH") or "120")
|
|
376
379
|
commit_template = (
|
|
377
380
|
os.environ.get("ISSUE_HELPER_COMMIT_TEMPLATE")
|
|
378
|
-
or "
|
|
381
|
+
or t("issue_helper.commit_template_default")
|
|
379
382
|
)
|
|
380
383
|
create_branch = _bool_env("ISSUE_HELPER_CREATE_BRANCH", False)
|
|
381
384
|
base_branch = os.environ.get("ISSUE_HELPER_BASE_BRANCH", "").strip()
|
|
@@ -388,11 +391,11 @@ def cmd_run():
|
|
|
388
391
|
body = (
|
|
389
392
|
f"{comment_marker}\n"
|
|
390
393
|
"## Issue Helper\n"
|
|
391
|
-
"###
|
|
394
|
+
f"### {t('issue_helper.branch_title')}\n"
|
|
392
395
|
"```\n"
|
|
393
396
|
f"{branch_name}\n"
|
|
394
397
|
"```\n\n"
|
|
395
|
-
"###
|
|
398
|
+
f"### {t('issue_helper.commit_title')}\n"
|
|
396
399
|
"```\n"
|
|
397
400
|
f"{commit_message}\n"
|
|
398
401
|
"```"
|
|
@@ -413,18 +416,18 @@ def cmd_run():
|
|
|
413
416
|
def cmd_link_pr_issues(pr_number, issue_numbers_csv, replace_existing):
|
|
414
417
|
repo_full = os.environ.get("GITHUB_REPOSITORY", "")
|
|
415
418
|
if "/" not in repo_full or repo_full.count("/") != 1 or not all(repo_full.split("/")):
|
|
416
|
-
log(
|
|
419
|
+
log(t("issue_helper.err_repo_format", repo=repr(repo_full)))
|
|
417
420
|
return 1
|
|
418
421
|
owner, repo = repo_full.split("/", 1)
|
|
419
422
|
|
|
420
423
|
token = os.environ.get("GITHUB_TOKEN", "").strip()
|
|
421
424
|
if not token:
|
|
422
|
-
log("
|
|
425
|
+
log(t("issue_helper.err_no_token"))
|
|
423
426
|
return 1
|
|
424
427
|
|
|
425
428
|
issue_numbers = [n.strip() for n in issue_numbers_csv.split(",") if n.strip()]
|
|
426
429
|
if not issue_numbers:
|
|
427
|
-
log("
|
|
430
|
+
log(t("issue_helper.no_issue_numbers"))
|
|
428
431
|
return 0
|
|
429
432
|
|
|
430
433
|
link_pr_issues(owner, repo, pr_number, issue_numbers, token, replace_existing)
|
|
@@ -449,6 +452,7 @@ def build_parser():
|
|
|
449
452
|
|
|
450
453
|
|
|
451
454
|
def main(argv=None):
|
|
455
|
+
use_utf8_output()
|
|
452
456
|
parser = build_parser()
|
|
453
457
|
args = parser.parse_args(argv)
|
|
454
458
|
try:
|
|
@@ -462,7 +466,7 @@ def main(argv=None):
|
|
|
462
466
|
if args.command == "link-pr-issues":
|
|
463
467
|
return cmd_link_pr_issues(args.pr, args.issue_numbers, args.replace)
|
|
464
468
|
except Exception as e:
|
|
465
|
-
log(
|
|
469
|
+
log(t("issue_helper.run_failed", error=e))
|
|
466
470
|
return 1
|
|
467
471
|
return 2
|
|
468
472
|
|