@akiojin/gwt 6.30.3 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cargo/config.toml +2 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/.coderabbit.yaml +8 -0
- package/.codex/skills/gwt-fix-issue/scripts/inspect_issue.py +833 -0
- package/.dockerignore +63 -0
- package/.gitattributes +27 -0
- package/.husky/commit-msg +2 -0
- package/.husky/pre-commit +9 -0
- package/.husky/pre-push +12 -0
- package/.markdownlint.json +18 -0
- package/.markdownlintignore +2 -0
- package/Dockerfile +58 -0
- package/README.ja.md +161 -484
- package/README.md +164 -444
- package/cliff.toml +56 -0
- package/clippy.toml +2 -0
- package/cmake/ci-disable-native.cmake +16 -0
- package/codecov.yml +16 -0
- package/commitlint.config.cjs +107 -0
- package/deny.toml +35 -0
- package/docker-compose.yml +59 -0
- package/messages/errors.toml +52 -0
- package/package.json +12 -22
- package/rustfmt.toml +8 -0
- package/scripts/check-e2e-coverage-threshold.mjs +238 -0
- package/scripts/entrypoint.sh +36 -25
- package/scripts/install-linux-deps.sh +46 -0
- package/scripts/postinstall.js +79 -227
- package/scripts/release_issue_refs.py +317 -0
- package/scripts/run-local-backend-tests-on-commit.sh +15 -0
- package/scripts/run-local-e2e-coverage-on-commit.sh +69 -0
- package/scripts/run-local-e2e-on-commit.sh +60 -0
- package/scripts/test-all.sh +13 -0
- package/scripts/test_release_issue_refs.py +257 -0
- package/scripts/validate-skill-frontmatter.sh +108 -0
- package/scripts/verify-ci-node-toolchain.sh +76 -0
- package/scripts/verify-husky-hooks.sh +6 -0
- package/scripts/voice-eval.sh +48 -0
- package/tests/voice_eval/README.md +53 -0
- package/tests/voice_eval/manifest.template.json +55 -0
- package/tests/voice_eval/samples/.gitkeep +1 -0
- package/tests/voice_eval/script-ja.txt +10 -0
- package/vendor/ratatui-core/src/backend/test.rs +1077 -0
- package/vendor/ratatui-core/src/backend.rs +405 -0
- package/vendor/ratatui-core/src/buffer/assert.rs +71 -0
- package/vendor/ratatui-core/src/buffer/buffer.rs +1388 -0
- package/vendor/ratatui-core/src/buffer/cell.rs +377 -0
- package/vendor/ratatui-core/src/buffer.rs +9 -0
- package/vendor/ratatui-core/src/layout/alignment.rs +89 -0
- package/vendor/ratatui-core/src/layout/constraint.rs +526 -0
- package/vendor/ratatui-core/src/layout/direction.rs +63 -0
- package/vendor/ratatui-core/src/layout/flex.rs +212 -0
- package/vendor/ratatui-core/src/layout/layout.rs +2838 -0
- package/vendor/ratatui-core/src/layout/margin.rs +79 -0
- package/vendor/ratatui-core/src/layout/offset.rs +66 -0
- package/vendor/ratatui-core/src/layout/position.rs +253 -0
- package/vendor/ratatui-core/src/layout/rect/iter.rs +356 -0
- package/vendor/ratatui-core/src/layout/rect/ops.rs +136 -0
- package/vendor/ratatui-core/src/layout/rect.rs +1114 -0
- package/vendor/ratatui-core/src/layout/size.rs +147 -0
- package/vendor/ratatui-core/src/layout.rs +333 -0
- package/vendor/ratatui-core/src/lib.rs +82 -0
- package/vendor/ratatui-core/src/style/anstyle.rs +348 -0
- package/vendor/ratatui-core/src/style/color.rs +788 -0
- package/vendor/ratatui-core/src/style/palette/material.rs +608 -0
- package/vendor/ratatui-core/src/style/palette/tailwind.rs +653 -0
- package/vendor/ratatui-core/src/style/palette.rs +6 -0
- package/vendor/ratatui-core/src/style/palette_conversion.rs +82 -0
- package/vendor/ratatui-core/src/style/stylize.rs +668 -0
- package/vendor/ratatui-core/src/style.rs +1069 -0
- package/vendor/ratatui-core/src/symbols/bar.rs +51 -0
- package/vendor/ratatui-core/src/symbols/block.rs +51 -0
- package/vendor/ratatui-core/src/symbols/border.rs +709 -0
- package/vendor/ratatui-core/src/symbols/braille.rs +21 -0
- package/vendor/ratatui-core/src/symbols/half_block.rs +3 -0
- package/vendor/ratatui-core/src/symbols/line.rs +259 -0
- package/vendor/ratatui-core/src/symbols/marker.rs +82 -0
- package/vendor/ratatui-core/src/symbols/merge.rs +748 -0
- package/vendor/ratatui-core/src/symbols/pixel.rs +30 -0
- package/vendor/ratatui-core/src/symbols/scrollbar.rs +46 -0
- package/vendor/ratatui-core/src/symbols/shade.rs +5 -0
- package/vendor/ratatui-core/src/symbols.rs +15 -0
- package/vendor/ratatui-core/src/terminal/frame.rs +192 -0
- package/vendor/ratatui-core/src/terminal/terminal.rs +926 -0
- package/vendor/ratatui-core/src/terminal/viewport.rs +58 -0
- package/vendor/ratatui-core/src/terminal.rs +40 -0
- package/vendor/ratatui-core/src/text/grapheme.rs +84 -0
- package/vendor/ratatui-core/src/text/line.rs +1678 -0
- package/vendor/ratatui-core/src/text/masked.rs +149 -0
- package/vendor/ratatui-core/src/text/span.rs +904 -0
- package/vendor/ratatui-core/src/text/text.rs +1434 -0
- package/vendor/ratatui-core/src/text.rs +64 -0
- package/vendor/ratatui-core/src/widgets/stateful_widget.rs +193 -0
- package/vendor/ratatui-core/src/widgets/widget.rs +174 -0
- package/vendor/ratatui-core/src/widgets.rs +9 -0
- package/bin/gwt.js +0 -131
- package/scripts/postinstall.test.js +0 -71
- package/scripts/release-download.js +0 -66
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Collect auto-close and reference-only issue refs for a release range."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from dataclasses import asdict, dataclass, field
|
|
12
|
+
from typing import Callable, Sequence
|
|
13
|
+
|
|
14
|
+
CommandRunner = Callable[[Sequence[str]], str]
|
|
15
|
+
|
|
16
|
+
CLOSING_KEYWORD_RE = re.compile(
|
|
17
|
+
r"\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b",
|
|
18
|
+
re.IGNORECASE,
|
|
19
|
+
)
|
|
20
|
+
ISSUE_REF_RE = re.compile(r"#(\d+)")
|
|
21
|
+
SQUASH_REF_RE = re.compile(r"\(#(\d+)\)$")
|
|
22
|
+
MERGE_PR_RE = re.compile(r"^Merge pull request #(\d+)\b")
|
|
23
|
+
HEADING_RE = re.compile(r"^##\s+(?P<title>.+?)\s*$")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class BodyIssueRefs:
|
|
28
|
+
auto_close_issues: list[int] = field(default_factory=list)
|
|
29
|
+
reference_only_issues: list[int] = field(default_factory=list)
|
|
30
|
+
warnings: list[str] = field(default_factory=list)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class CommitRef:
|
|
35
|
+
number: int
|
|
36
|
+
kind: str
|
|
37
|
+
source: str
|
|
38
|
+
auto_close_issues: list[int] = field(default_factory=list)
|
|
39
|
+
reference_only_issues: list[int] = field(default_factory=list)
|
|
40
|
+
warnings: list[str] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ReleaseIssueRefs:
|
|
45
|
+
repo: str
|
|
46
|
+
range: str
|
|
47
|
+
refs: list[CommitRef] = field(default_factory=list)
|
|
48
|
+
auto_close_issues: list[int] = field(default_factory=list)
|
|
49
|
+
reference_only_issues: list[int] = field(default_factory=list)
|
|
50
|
+
warnings: list[str] = field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run_command(args: Sequence[str]) -> str:
|
|
54
|
+
result = subprocess.run(args, check=True, capture_output=True, text=True)
|
|
55
|
+
return result.stdout
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def unique_sorted(numbers: Sequence[int | str]) -> list[int]:
|
|
59
|
+
return sorted({int(value) for value in numbers})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def dedupe_preserve_order(items: Sequence[str]) -> list[str]:
|
|
63
|
+
seen: set[str] = set()
|
|
64
|
+
ordered: list[str] = []
|
|
65
|
+
for item in items:
|
|
66
|
+
if item not in seen:
|
|
67
|
+
seen.add(item)
|
|
68
|
+
ordered.append(item)
|
|
69
|
+
return ordered
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def format_issue_refs(numbers: Sequence[int]) -> str:
|
|
73
|
+
return ", ".join(f"#{number}" for number in unique_sorted(numbers))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def extract_section(body: str, section_title: str) -> str:
|
|
77
|
+
lines = body.splitlines()
|
|
78
|
+
in_section = False
|
|
79
|
+
collected: list[str] = []
|
|
80
|
+
|
|
81
|
+
for line in lines:
|
|
82
|
+
heading = HEADING_RE.match(line.strip())
|
|
83
|
+
if heading:
|
|
84
|
+
title = heading.group("title").strip()
|
|
85
|
+
if in_section:
|
|
86
|
+
break
|
|
87
|
+
in_section = title == section_title
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
if in_section:
|
|
91
|
+
collected.append(line)
|
|
92
|
+
|
|
93
|
+
return "\n".join(collected)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def extract_issue_numbers(text: str) -> list[int]:
|
|
97
|
+
return unique_sorted(match.group(1) for match in ISSUE_REF_RE.finditer(text or ""))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def parse_pr_body_refs(body: str, pr_number: int | None = None) -> BodyIssueRefs:
|
|
101
|
+
closing_section = extract_section(body, "Closing Issues")
|
|
102
|
+
related_section = extract_section(body, "Related Issues / Links")
|
|
103
|
+
|
|
104
|
+
auto_close = set(extract_issue_numbers(closing_section))
|
|
105
|
+
auto_close.update(int(match.group(1)) for match in CLOSING_KEYWORD_RE.finditer(body or ""))
|
|
106
|
+
|
|
107
|
+
reference_only = set(extract_issue_numbers(related_section)) - auto_close
|
|
108
|
+
warnings: list[str] = []
|
|
109
|
+
|
|
110
|
+
if reference_only:
|
|
111
|
+
prefix = f"PR #{pr_number}" if pr_number is not None else "PR body"
|
|
112
|
+
warnings.append(
|
|
113
|
+
f"{prefix} references {format_issue_refs(sorted(reference_only))} only in "
|
|
114
|
+
"`Related Issues / Links`; they will not auto-close on release."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return BodyIssueRefs(
|
|
118
|
+
auto_close_issues=sorted(auto_close),
|
|
119
|
+
reference_only_issues=sorted(reference_only),
|
|
120
|
+
warnings=warnings,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def extract_release_commit_refs(
|
|
125
|
+
no_merge_subjects: str,
|
|
126
|
+
merge_subjects: str,
|
|
127
|
+
) -> list[tuple[int, str]]:
|
|
128
|
+
refs: dict[int, str] = {}
|
|
129
|
+
|
|
130
|
+
for subject in no_merge_subjects.splitlines():
|
|
131
|
+
match = SQUASH_REF_RE.search(subject.strip())
|
|
132
|
+
if match:
|
|
133
|
+
refs.setdefault(int(match.group(1)), "squash")
|
|
134
|
+
|
|
135
|
+
for subject in merge_subjects.splitlines():
|
|
136
|
+
match = MERGE_PR_RE.search(subject.strip())
|
|
137
|
+
if match:
|
|
138
|
+
refs.setdefault(int(match.group(1)), "merge")
|
|
139
|
+
|
|
140
|
+
return [(number, refs[number]) for number in sorted(refs)]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def resolve_repo_slug(runner: CommandRunner) -> str:
|
|
144
|
+
return runner(
|
|
145
|
+
["gh", "repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"]
|
|
146
|
+
).strip()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def fetch_issue_labels(number: int, repo_slug: str, runner: CommandRunner) -> list[str]:
|
|
150
|
+
"""Return label names for a GitHub issue."""
|
|
151
|
+
payload = json.loads(runner(["gh", "api", f"repos/{repo_slug}/issues/{number}"]) or "{}")
|
|
152
|
+
return [label["name"] for label in payload.get("labels", [])]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
SPEC_LABEL = "gwt-spec"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def classify_release_ref(
|
|
159
|
+
number: int,
|
|
160
|
+
source: str,
|
|
161
|
+
repo_slug: str,
|
|
162
|
+
runner: CommandRunner,
|
|
163
|
+
) -> CommitRef:
|
|
164
|
+
issue_payload = json.loads(runner(["gh", "api", f"repos/{repo_slug}/issues/{number}"]) or "{}")
|
|
165
|
+
if issue_payload.get("pull_request"):
|
|
166
|
+
pr_payload = json.loads(
|
|
167
|
+
runner(["gh", "pr", "view", str(number), "--repo", repo_slug, "--json", "body"]) or "{}"
|
|
168
|
+
)
|
|
169
|
+
pr_refs = parse_pr_body_refs(pr_payload.get("body") or "", pr_number=number)
|
|
170
|
+
return CommitRef(
|
|
171
|
+
number=number,
|
|
172
|
+
kind="pr",
|
|
173
|
+
source=source,
|
|
174
|
+
auto_close_issues=pr_refs.auto_close_issues,
|
|
175
|
+
reference_only_issues=pr_refs.reference_only_issues,
|
|
176
|
+
warnings=pr_refs.warnings,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return CommitRef(
|
|
180
|
+
number=number,
|
|
181
|
+
kind="issue",
|
|
182
|
+
source=source,
|
|
183
|
+
auto_close_issues=[number],
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def collect_release_issue_refs(
|
|
188
|
+
range_expr: str,
|
|
189
|
+
repo_slug: str | None = None,
|
|
190
|
+
runner: CommandRunner = run_command,
|
|
191
|
+
) -> ReleaseIssueRefs:
|
|
192
|
+
repo = repo_slug or resolve_repo_slug(runner)
|
|
193
|
+
no_merge_subjects = runner(["git", "log", "--pretty=%s", "--no-merges", range_expr])
|
|
194
|
+
merge_subjects = runner(["git", "log", "--merges", "--pretty=%s", range_expr])
|
|
195
|
+
|
|
196
|
+
refs: list[CommitRef] = []
|
|
197
|
+
auto_close: set[int] = set()
|
|
198
|
+
reference_only: set[int] = set()
|
|
199
|
+
warnings: list[str] = []
|
|
200
|
+
|
|
201
|
+
for number, source in extract_release_commit_refs(no_merge_subjects, merge_subjects):
|
|
202
|
+
ref = classify_release_ref(number, source, repo, runner)
|
|
203
|
+
refs.append(ref)
|
|
204
|
+
auto_close.update(ref.auto_close_issues)
|
|
205
|
+
reference_only.update(ref.reference_only_issues)
|
|
206
|
+
warnings.extend(ref.warnings)
|
|
207
|
+
|
|
208
|
+
# Post-filter: move gwt-spec issues from auto-close to reference-only
|
|
209
|
+
spec_protected: list[int] = []
|
|
210
|
+
for issue_number in sorted(auto_close):
|
|
211
|
+
labels = fetch_issue_labels(issue_number, repo, runner)
|
|
212
|
+
if SPEC_LABEL in labels:
|
|
213
|
+
spec_protected.append(issue_number)
|
|
214
|
+
|
|
215
|
+
if spec_protected:
|
|
216
|
+
auto_close.difference_update(spec_protected)
|
|
217
|
+
reference_only.update(spec_protected)
|
|
218
|
+
warnings.append(
|
|
219
|
+
f"gwt-spec issues moved to reference-only: "
|
|
220
|
+
f"{format_issue_refs(spec_protected)}. "
|
|
221
|
+
"gwt-spec issues are never auto-closed by releases."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
reference_only.difference_update(auto_close)
|
|
225
|
+
if reference_only:
|
|
226
|
+
warnings.insert(
|
|
227
|
+
0,
|
|
228
|
+
"Reference-only issues detected: "
|
|
229
|
+
f"{format_issue_refs(sorted(reference_only))}. "
|
|
230
|
+
"Add them to `Closing Issues` if they should auto-close.",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return ReleaseIssueRefs(
|
|
234
|
+
repo=repo,
|
|
235
|
+
range=range_expr,
|
|
236
|
+
refs=refs,
|
|
237
|
+
auto_close_issues=sorted(auto_close),
|
|
238
|
+
reference_only_issues=sorted(reference_only),
|
|
239
|
+
warnings=dedupe_preserve_order(warnings),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def render_text(report: ReleaseIssueRefs) -> str:
|
|
244
|
+
lines = [
|
|
245
|
+
f"Repo: {report.repo}",
|
|
246
|
+
f"Range: {report.range}",
|
|
247
|
+
"",
|
|
248
|
+
"Auto-close issues:",
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
if report.auto_close_issues:
|
|
252
|
+
lines.extend(f"- Closes #{number}" for number in report.auto_close_issues)
|
|
253
|
+
else:
|
|
254
|
+
lines.append("- None")
|
|
255
|
+
|
|
256
|
+
lines.extend(["", "Reference-only issues:"])
|
|
257
|
+
if report.reference_only_issues:
|
|
258
|
+
lines.extend(f"- #{number}" for number in report.reference_only_issues)
|
|
259
|
+
else:
|
|
260
|
+
lines.append("- None")
|
|
261
|
+
|
|
262
|
+
if report.warnings:
|
|
263
|
+
lines.extend(["", "Warnings:"])
|
|
264
|
+
lines.extend(f"- {warning}" for warning in report.warnings)
|
|
265
|
+
|
|
266
|
+
return "\n".join(lines)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
270
|
+
parser = argparse.ArgumentParser(
|
|
271
|
+
description="Collect release auto-close and reference-only issue refs."
|
|
272
|
+
)
|
|
273
|
+
parser.add_argument(
|
|
274
|
+
"--range",
|
|
275
|
+
dest="range_expr",
|
|
276
|
+
required=True,
|
|
277
|
+
help="Git revision range to inspect, for example v1.2.3..HEAD or HEAD.",
|
|
278
|
+
)
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"--repo",
|
|
281
|
+
dest="repo_slug",
|
|
282
|
+
default=None,
|
|
283
|
+
help="GitHub repo slug (owner/name). Defaults to `gh repo view`.",
|
|
284
|
+
)
|
|
285
|
+
parser.add_argument(
|
|
286
|
+
"--format",
|
|
287
|
+
choices=("text", "json"),
|
|
288
|
+
default="text",
|
|
289
|
+
help="Output format.",
|
|
290
|
+
)
|
|
291
|
+
return parser
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def main() -> int:
|
|
295
|
+
parser = build_parser()
|
|
296
|
+
args = parser.parse_args()
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
report = collect_release_issue_refs(args.range_expr, repo_slug=args.repo_slug)
|
|
300
|
+
except subprocess.CalledProcessError as error:
|
|
301
|
+
stderr = (error.stderr or "").strip()
|
|
302
|
+
if stderr:
|
|
303
|
+
print(stderr, file=sys.stderr)
|
|
304
|
+
else:
|
|
305
|
+
print(str(error), file=sys.stderr)
|
|
306
|
+
return error.returncode or 1
|
|
307
|
+
|
|
308
|
+
if args.format == "json":
|
|
309
|
+
print(json.dumps(asdict(report), ensure_ascii=False, indent=2))
|
|
310
|
+
else:
|
|
311
|
+
print(render_text(report))
|
|
312
|
+
|
|
313
|
+
return 0
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
if __name__ == "__main__":
|
|
317
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
|
|
7
|
+
echo "Running commit-time backend tests..."
|
|
8
|
+
|
|
9
|
+
(
|
|
10
|
+
cd "$ROOT_DIR"
|
|
11
|
+
cargo test -p gwt-tui commands::branches::tests:: -- --nocapture
|
|
12
|
+
cargo test -p gwt-tui commands::project::tests:: -- --nocapture
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
echo "Commit-time backend tests passed."
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
GUI_DIR="$ROOT_DIR/gwt-gui"
|
|
7
|
+
|
|
8
|
+
SUITES=(
|
|
9
|
+
"e2e/agent-canvas-browser.spec.ts"
|
|
10
|
+
"e2e/agent-terminal.spec.ts"
|
|
11
|
+
"e2e/branch-worktree.spec.ts"
|
|
12
|
+
"e2e/cleanup-migration.spec.ts"
|
|
13
|
+
"e2e/dialogs-common.spec.ts"
|
|
14
|
+
"e2e/issue-cache-sync.spec.ts"
|
|
15
|
+
"e2e/open-project-smoke.spec.ts"
|
|
16
|
+
"e2e/pr-management.spec.ts"
|
|
17
|
+
"e2e/project-management.spec.ts"
|
|
18
|
+
"e2e/responsive-performance.spec.ts"
|
|
19
|
+
"e2e/settings-config.spec.ts"
|
|
20
|
+
"e2e/status-bar.spec.ts"
|
|
21
|
+
"e2e/top-level-tools.spec.ts"
|
|
22
|
+
"e2e/voice-input-settings.spec.ts"
|
|
23
|
+
"e2e/web-preview-fallback.spec.ts"
|
|
24
|
+
"e2e/windows-shell-selection.spec.ts"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
run_playwright_coverage() {
|
|
28
|
+
local mode="$1"
|
|
29
|
+
|
|
30
|
+
echo "Running commit-time E2E coverage in ${mode} mode..."
|
|
31
|
+
(
|
|
32
|
+
cd "$GUI_DIR"
|
|
33
|
+
rm -rf .nyc_output e2e/.nyc_output coverage-e2e
|
|
34
|
+
mkdir -p .nyc_output
|
|
35
|
+
if [ "$mode" = "headed" ]; then
|
|
36
|
+
E2E_COVERAGE=1 pnpm exec playwright test "${SUITES[@]}" --project=chromium --headed --workers=1
|
|
37
|
+
else
|
|
38
|
+
E2E_COVERAGE=1 pnpm exec playwright test "${SUITES[@]}" --project=chromium
|
|
39
|
+
fi
|
|
40
|
+
if ! python3 - <<'EOF'
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
raise SystemExit(0 if any(Path(".nyc_output").glob("*.json")) else 1)
|
|
43
|
+
EOF
|
|
44
|
+
then
|
|
45
|
+
echo "E2E coverage run did not produce any .nyc_output JSON files." >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
pnpm exec nyc report --nycrc-path .nycrc.e2e.json
|
|
49
|
+
node ../scripts/check-e2e-coverage-threshold.mjs
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
headed_possible=1
|
|
54
|
+
|
|
55
|
+
if [ -n "${CI:-}" ]; then
|
|
56
|
+
headed_possible=0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [ "$(uname -s)" = "Linux" ] && [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then
|
|
60
|
+
headed_possible=0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if [ "$headed_possible" -eq 1 ]; then
|
|
64
|
+
run_playwright_coverage headed
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
echo "Headed Playwright coverage unavailable in this environment; using headless mode."
|
|
69
|
+
run_playwright_coverage headless
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
GUI_DIR="$ROOT_DIR/gwt-gui"
|
|
7
|
+
|
|
8
|
+
# Keep commit-time E2E focused on the current shell/UX-critical flows.
|
|
9
|
+
SUITES=(
|
|
10
|
+
"e2e/agent-canvas-browser.spec.ts"
|
|
11
|
+
"e2e/agent-terminal.spec.ts"
|
|
12
|
+
"e2e/branch-worktree.spec.ts"
|
|
13
|
+
"e2e/cleanup-migration.spec.ts"
|
|
14
|
+
"e2e/dialogs-common.spec.ts"
|
|
15
|
+
"e2e/issue-cache-sync.spec.ts"
|
|
16
|
+
"e2e/open-project-smoke.spec.ts"
|
|
17
|
+
"e2e/pr-management.spec.ts"
|
|
18
|
+
"e2e/project-management.spec.ts"
|
|
19
|
+
"e2e/responsive-performance.spec.ts"
|
|
20
|
+
"e2e/settings-config.spec.ts"
|
|
21
|
+
"e2e/status-bar.spec.ts"
|
|
22
|
+
"e2e/top-level-tools.spec.ts"
|
|
23
|
+
"e2e/voice-input-settings.spec.ts"
|
|
24
|
+
"e2e/web-preview-fallback.spec.ts"
|
|
25
|
+
"e2e/windows-shell-selection.spec.ts"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
run_playwright() {
|
|
29
|
+
local mode="$1"
|
|
30
|
+
shift
|
|
31
|
+
|
|
32
|
+
echo "Running commit-time E2E in ${mode} mode..."
|
|
33
|
+
(
|
|
34
|
+
cd "$GUI_DIR"
|
|
35
|
+
if [ "$mode" = "headed" ]; then
|
|
36
|
+
pnpm exec playwright test "${SUITES[@]}" --project=chromium --headed --workers=1
|
|
37
|
+
else
|
|
38
|
+
pnpm exec playwright test "${SUITES[@]}" --project=chromium
|
|
39
|
+
fi
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
headed_possible=1
|
|
44
|
+
|
|
45
|
+
if [ -n "${CI:-}" ]; then
|
|
46
|
+
headed_possible=0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Linux/X11/Wayland environments without a display cannot run headed.
|
|
50
|
+
if [ "$(uname -s)" = "Linux" ] && [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then
|
|
51
|
+
headed_possible=0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
if [ "$headed_possible" -eq 1 ]; then
|
|
55
|
+
run_playwright headed
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo "Headed Playwright unavailable in this environment; using headless mode."
|
|
60
|
+
run_playwright headless
|