git-repokit 0.9.10__py3-none-any.whl
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.
- git_repokit-0.9.10.dist-info/METADATA +422 -0
- git_repokit-0.9.10.dist-info/RECORD +174 -0
- git_repokit-0.9.10.dist-info/WHEEL +5 -0
- git_repokit-0.9.10.dist-info/entry_points.txt +2 -0
- git_repokit-0.9.10.dist-info/licenses/LICENSE +9 -0
- git_repokit-0.9.10.dist-info/top_level.txt +1 -0
- repokit/__init__.py +8 -0
- repokit/__main__.py +12 -0
- repokit/__version__.py +4 -0
- repokit/_vendor/__init__.py +0 -0
- repokit/_vendor/ailib/__init__.py +1 -0
- repokit/_vendor/ailib/analyzer.py +177 -0
- repokit/_vendor/ailib/backend.py +95 -0
- repokit/_vendor/ailib/cache.py +97 -0
- repokit/_vendor/ailib/parsers.py +52 -0
- repokit/_vendor/ailib/transports/__init__.py +16 -0
- repokit/_vendor/ailib/transports/cli.py +312 -0
- repokit/_vendor/ailib/transports/openai_compat.py +383 -0
- repokit/_vendor/ailib/transports/prompt_file.py +41 -0
- repokit/_vendor/ailib/types.py +224 -0
- repokit/auth_integration.py +244 -0
- repokit/badges.py +183 -0
- repokit/branch_utils.py +364 -0
- repokit/cli.py +2997 -0
- repokit/common_kit.py +202 -0
- repokit/config.py +686 -0
- repokit/defaults.py +220 -0
- repokit/directory_analyzer.py +1244 -0
- repokit/directory_profiles.py +423 -0
- repokit/git_config.py +370 -0
- repokit/github_create.py +142 -0
- repokit/github_setup.py +248 -0
- repokit/guardrails.py +707 -0
- repokit/history_cleaner.py +449 -0
- repokit/history_protection.py +434 -0
- repokit/hooks/pre-commit-guardrails +122 -0
- repokit/hooks/pre-commit-template +28 -0
- repokit/layers.py +510 -0
- repokit/licenses/0bsd.txt +14 -0
- repokit/licenses/LICENSES.md +27 -0
- repokit/licenses/__init__.py +142 -0
- repokit/licenses/agpl-3.0.txt +661 -0
- repokit/licenses/all-rights-reserved.txt +10 -0
- repokit/licenses/apache-2.0.txt +201 -0
- repokit/licenses/bsd-2-clause.txt +24 -0
- repokit/licenses/bsd-3-clause.txt +28 -0
- repokit/licenses/bsl-1.0.txt +23 -0
- repokit/licenses/cc-by-4.0.txt +395 -0
- repokit/licenses/cc-by-sa-4.0.txt +427 -0
- repokit/licenses/cc0-1.0.txt +121 -0
- repokit/licenses/epl-2.0.txt +277 -0
- repokit/licenses/gpl-2.0.txt +339 -0
- repokit/licenses/gpl-3.0.txt +674 -0
- repokit/licenses/isc.txt +15 -0
- repokit/licenses/lgpl-2.1.txt +504 -0
- repokit/licenses/lgpl-3.0.txt +165 -0
- repokit/licenses/mit.txt +21 -0
- repokit/licenses/mpl-2.0.txt +373 -0
- repokit/licenses/unlicense.txt +24 -0
- repokit/readme_blocks.py +112 -0
- repokit/remote_integration.py +627 -0
- repokit/repo_manager.py +2796 -0
- repokit/template_engine.py +391 -0
- repokit/templates/ai/claude/CLAUDE-nested.md.template +25 -0
- repokit/templates/ai/claude/CLAUDE.md.template +98 -0
- repokit/templates/ai/claude/instructions/step1_context_rebuilder.md +33 -0
- repokit/templates/ai/claude/instructions/step2_dev_workflow_process.md +67 -0
- repokit/templates/ai/claude/instructions/step3_context_bridge.md +52 -0
- repokit/templates/common/CHANGELOG.md.template +15 -0
- repokit/templates/common/CONTRIBUTING.md.template +31 -0
- repokit/templates/common/README.md.template +30 -0
- repokit/templates/common/gitattributes.template +17 -0
- repokit/templates/common/gitignore.template +141 -0
- repokit/templates/extras/FUNDING.yml.template +13 -0
- repokit/templates/extras/ROADMAP.md.template +17 -0
- repokit/templates/extras/dependabot.yml.template +22 -0
- repokit/templates/extras/one-offs-README.md.template +28 -0
- repokit/templates/extras/platform-support.md.template +11 -0
- repokit/templates/extras/pull_request_template.md.template +25 -0
- repokit/templates/extras/release-template.md.template +34 -0
- repokit/templates/extras/settings.json.template +11 -0
- repokit/templates/extras/stale.yml.template +14 -0
- repokit/templates/github/CODEOWNERS.template +11 -0
- repokit/templates/github/ISSUE_TEMPLATE/bug-report.md.template +29 -0
- repokit/templates/github/ISSUE_TEMPLATE/feature-request.md.template +19 -0
- repokit/templates/github/issues/notes.md.template +18 -0
- repokit/templates/github/issues/roadmap.md.template +15 -0
- repokit/templates/hooks/pre-commit.template +64 -0
- repokit/templates/layers/archetypes/any/claude-plugin/.claude-plugin/marketplace.json +20 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/.claude-plugin/plugin.json +15 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/README.md +28 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.github/workflows/ci-c-cpp.yml +24 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.repokit-common.toml.rkappend +4 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/launch.json +29 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/tasks.json +26 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakeLists.txt +47 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakePresets.json +23 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/cmake/GenerateStamp.cmake +45 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/src/main.cpp +27 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/tests/test_version.cpp +20 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/vcpkg.json +5 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/.vscode/launch.json +27 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/Makefile +10 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/build.bat +7 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/src/__RK_SNAKE__.c +26 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/launch.json +17 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/tasks.json +20 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.sln +19 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.vcxproj +41 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/src/main.cpp +26 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.repokit-common.toml +28 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/scripts/session-start.mjs +23 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/tests/manifest.test.js +30 -0
- repokit/templates/layers/archetypes/js-ts/node/.repokit-common.toml.rkappend +6 -0
- repokit/templates/layers/archetypes/js-ts/node/package.json +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/.github/workflows/release.yml +39 -0
- repokit/templates/layers/archetypes/js-ts/node-package/package.json +32 -0
- repokit/templates/layers/archetypes/js-ts/node-package/src/index.ts +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/tests/index.test.ts +11 -0
- repokit/templates/layers/archetypes/js-ts/script/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/script/__RK_PROJECT__.js +37 -0
- repokit/templates/layers/archetypes/js-ts/script/tests/version.test.js +26 -0
- repokit/templates/layers/archetypes/js-ts/ts/package.json +18 -0
- repokit/templates/layers/archetypes/js-ts/ts/tsconfig.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.github/workflows/ci-python.yml +44 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.repokit-common.toml +21 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/claude-plugin/VERSION +1 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/run-hook.mjs +42 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/session_start.py +31 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/tests/test_manifest.py +42 -0
- repokit/templates/layers/archetypes/python/pypi/.github/workflows/release.yml +63 -0
- repokit/templates/layers/archetypes/python/pypi/.vscode/launch.json +43 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__init__.py +5 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__main__.py +6 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/_version.py +96 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/cli.py +18 -0
- repokit/templates/layers/archetypes/python/pypi/pyproject.toml +71 -0
- repokit/templates/layers/archetypes/python/pypi/tests/conftest.py +11 -0
- repokit/templates/layers/archetypes/python/pypi/tests/test_version.py +50 -0
- repokit/templates/layers/archetypes/python/script/.github/workflows/ci-python.yml +43 -0
- repokit/templates/layers/archetypes/python/script/.repokit-common.toml +14 -0
- repokit/templates/layers/archetypes/python/script/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/script/VERSION +1 -0
- repokit/templates/layers/archetypes/python/script/__RK_PROJECT__.py +40 -0
- repokit/templates/layers/archetypes/python/script/tests/test_version.py +25 -0
- repokit/templates/layers/archetypes/python/setuppy/requirements.txt +8 -0
- repokit/templates/layers/archetypes/python/setuppy/setup.py +23 -0
- repokit/templates/layers/languages/c-cpp/.gitignore.rkappend +20 -0
- repokit/templates/layers/languages/c-cpp/.repokit-common.toml +7 -0
- repokit/templates/layers/languages/c-cpp/include/__RK_SNAKE__/version.h +20 -0
- repokit/templates/layers/languages/c-cpp/src/app.rc +25 -0
- repokit/templates/layers/languages/js-ts/.editorconfig +15 -0
- repokit/templates/layers/languages/js-ts/.github/workflows/ci-js-ts.yml +41 -0
- repokit/templates/layers/languages/js-ts/.gitignore.rkappend +13 -0
- repokit/templates/layers/languages/js-ts/.repokit-common.toml +19 -0
- repokit/templates/layers/languages/js-ts/.vscode/launch.json +24 -0
- repokit/templates/layers/languages/js-ts/VERSION +1 -0
- repokit/templates/layers/languages/python/.github/workflows/ci-python.yml +76 -0
- repokit/templates/layers/languages/python/.gitignore.rkappend +15 -0
- repokit/templates/layers/languages/python/.vscode/launch.json +33 -0
- repokit/templates/layers/manifest.toml +302 -0
- repokit/templates/vault/CLAUDE.md +29 -0
- repokit/templates/vault/claude/_maps/MOC-project.md +15 -0
- repokit/templates/vault/claude/_oracle/backlinks.md +3 -0
- repokit/templates/vault/claude/_oracle/concepts.md +12 -0
- repokit/templates/vault/claude/_oracle/manifest.md +10 -0
- repokit/templates/vault/claude/_todo/ACTIVE-WORK.md +18 -0
- repokit/templates/vault/claude/_todo/OPEN-LOOPS.md +14 -0
- repokit/utils.py +316 -0
- repokit/vault.py +248 -0
repokit/github_setup.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""The GitHub half of a new project (0.9.3): what `/github-issues-setup` and
|
|
2
|
+
`/create-project` did by hand after the repository existed, behind the
|
|
3
|
+
`github_setup` setting (a list in the config, or `--github-setup a,b|all`).
|
|
4
|
+
|
|
5
|
+
The order is the skill's own and it matters: the labels and the two
|
|
6
|
+
evergreen issues go in BETWEEN `gh repo create` and the first push. The first
|
|
7
|
+
push lets Dependabot (the `dependabot` extra) open a pull request, which would
|
|
8
|
+
take issue number 1; the Roadmap and the Quick Notes must be #1 and #2
|
|
9
|
+
(djdarcy, 2026-10-22). After the push: discussions, topics, the `pypi`
|
|
10
|
+
environment for a preset that publishes there. Every step is idempotent: a
|
|
11
|
+
second run creates nothing that exists. ghtraf creates gists and needs a
|
|
12
|
+
secret only the person can set, so its commands are printed, not run.
|
|
13
|
+
|
|
14
|
+
All gh calls name the repository (`--repo OWNER/NAME`), so nothing depends on
|
|
15
|
+
the working directory; `run` is injectable so the tests can answer for gh.
|
|
16
|
+
Design: 2026-10-22__22-06-57__dev-workflow-process__0-9-x-parity-with-the-old-route.md (row 0.9.3).
|
|
17
|
+
"""
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import string
|
|
22
|
+
import tempfile
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Callable, List, Optional, Tuple
|
|
25
|
+
|
|
26
|
+
STEPS = ("labels", "issues", "pin", "discussions", "topics", "pypi-environment", "traffic")
|
|
27
|
+
|
|
28
|
+
# /github-issues-setup, Step 1 (names, colours, descriptions as the skill has them)
|
|
29
|
+
LABELS = [
|
|
30
|
+
("pinned", "d4c5f9", "Permanently open issue"),
|
|
31
|
+
("evergreen", "2ea44f", "Never close -- living document updated over time"),
|
|
32
|
+
("roadmap", "0075ca", "Project roadmap"),
|
|
33
|
+
("scratchpad", "f9d0c4", "Quick notes and scratch space"),
|
|
34
|
+
("architecture", "bfd4f2", "Structural decisions"),
|
|
35
|
+
("epic", "5319e7", "Large multi-phase initiative"),
|
|
36
|
+
("ideas", "c5def5", "Exploratory ideas"),
|
|
37
|
+
("CurrentTask", "0e8a16", "Currently being worked on"),
|
|
38
|
+
("NextTask", "e6b800", "Next item to pick up"),
|
|
39
|
+
]
|
|
40
|
+
# Labels a created project's own files name (its issue templates' `labels:`,
|
|
41
|
+
# dependabot.yml's `labels:`), with GitHub's and Dependabot's usual colours.
|
|
42
|
+
# A repository created by gh has no default labels, so without these an issue
|
|
43
|
+
# filed from the bug template, or a Dependabot pull request, would ask for a
|
|
44
|
+
# label that does not exist (0.9.6; the live golden had them from the template).
|
|
45
|
+
REFERENCED = {
|
|
46
|
+
"bug": ("d73a4a", "Something isn't working"),
|
|
47
|
+
"enhancement": ("a2eeef", "New feature or request"),
|
|
48
|
+
"documentation": ("0075ca", "Improvements or additions to documentation"),
|
|
49
|
+
"dependencies": ("0366d6", "Pull requests that update a dependency file"),
|
|
50
|
+
"github-actions": ("000000", "Pull requests that update GitHub Actions"),
|
|
51
|
+
"pip": ("2b67c6", "Pull requests that update Python dependencies"),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def referenced_labels(repo_root: str) -> List[str]:
|
|
56
|
+
"""Every label the project's issue templates and dependabot.yml name, in
|
|
57
|
+
the order first seen."""
|
|
58
|
+
names: List[str] = []
|
|
59
|
+
gh_dir = Path(repo_root, ".github")
|
|
60
|
+
for path in sorted((gh_dir / "ISSUE_TEMPLATE").glob("*.md")) if (gh_dir / "ISSUE_TEMPLATE").is_dir() else []:
|
|
61
|
+
m = re.search(r"^labels:\s*(.+)$", path.read_text(encoding="utf-8"), re.M)
|
|
62
|
+
if m:
|
|
63
|
+
names += [n.strip().strip("'\"") for n in m.group(1).split(",")]
|
|
64
|
+
dependabot = gh_dir / "dependabot.yml"
|
|
65
|
+
if dependabot.is_file():
|
|
66
|
+
names += re.findall(r'^\s*-\s*"?([A-Za-z0-9_.-]+)"?\s*$',
|
|
67
|
+
"\n".join(_labels_blocks(dependabot.read_text(encoding="utf-8"))), re.M)
|
|
68
|
+
seen: List[str] = []
|
|
69
|
+
for n in names:
|
|
70
|
+
if n and n not in seen:
|
|
71
|
+
seen.append(n)
|
|
72
|
+
return seen
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _labels_blocks(text: str) -> List[str]:
|
|
76
|
+
"""The list items under each `labels:` key of a YAML file, as lines."""
|
|
77
|
+
out, lines = [], text.splitlines()
|
|
78
|
+
for i, line in enumerate(lines):
|
|
79
|
+
if line.strip() == "labels:":
|
|
80
|
+
indent = len(line) - len(line.lstrip())
|
|
81
|
+
for nxt in lines[i + 1:]:
|
|
82
|
+
if nxt.strip().startswith("-") and len(nxt) - len(nxt.lstrip()) >= indent:
|
|
83
|
+
out.append(nxt)
|
|
84
|
+
else:
|
|
85
|
+
break
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
ISSUES = [ # title, labels, template
|
|
90
|
+
("Roadmap", "pinned,roadmap,evergreen", "roadmap.md.template"),
|
|
91
|
+
("Quick Notes -- Bugs, Features, Ideas", "pinned,scratchpad,evergreen", "notes.md.template"),
|
|
92
|
+
]
|
|
93
|
+
TEMPLATES = Path(__file__).resolve().parent / "templates" / "github" / "issues"
|
|
94
|
+
PUBLISHES_TO_PYPI = ("python/pypi",)
|
|
95
|
+
|
|
96
|
+
Runner = Callable[[List[str]], Tuple[int, str]]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def setup_names(requested) -> List[str]:
|
|
100
|
+
"""The steps to run, in STEPS order; `all` means every one; unknown refused."""
|
|
101
|
+
wanted = list(requested or [])
|
|
102
|
+
if "all" in wanted:
|
|
103
|
+
return list(STEPS)
|
|
104
|
+
unknown = [w for w in wanted if w not in STEPS]
|
|
105
|
+
if unknown:
|
|
106
|
+
raise ValueError(f"unknown github-setup steps {', '.join(unknown)}; known: {', '.join(STEPS)}, all")
|
|
107
|
+
return [s for s in STEPS if s in wanted]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def render_issue(template: str, project_name: str) -> str:
|
|
111
|
+
text = (TEMPLATES / template).read_text(encoding="utf-8")
|
|
112
|
+
return string.Template(text).safe_substitute(project_name=project_name)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class GhSetup:
|
|
116
|
+
"""The steps for one repository. Each method returns report lines."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, gh: List[str], repo: str, run: Runner):
|
|
119
|
+
self.gh, self.repo, self.run = gh, repo, run
|
|
120
|
+
|
|
121
|
+
def _gh(self, *args: str) -> Tuple[int, str]:
|
|
122
|
+
return self.run(self.gh + list(args))
|
|
123
|
+
|
|
124
|
+
# --- before the first push ------------------------------------------------
|
|
125
|
+
|
|
126
|
+
def labels(self, referenced: Optional[List[str]] = None) -> List[str]:
|
|
127
|
+
"""The nine house labels, then any label the project's own files name
|
|
128
|
+
(referenced_labels) that the repository lacks."""
|
|
129
|
+
rc, out = self._gh("label", "list", "--repo", self.repo, "--limit", "200", "--json", "name")
|
|
130
|
+
if rc != 0:
|
|
131
|
+
return [f"labels: could not list them: {out}"]
|
|
132
|
+
have = {l["name"] for l in json.loads(out or "[]")}
|
|
133
|
+
wanted = list(LABELS) + [(n, *REFERENCED.get(n, ("ededed", ""))) for n in (referenced or [])
|
|
134
|
+
if n not in {l[0] for l in LABELS}]
|
|
135
|
+
made = []
|
|
136
|
+
for name, color, desc in wanted:
|
|
137
|
+
if name in have:
|
|
138
|
+
continue
|
|
139
|
+
cmd = ["label", "create", name, "--repo", self.repo, "--color", color]
|
|
140
|
+
if desc:
|
|
141
|
+
cmd += ["--description", desc]
|
|
142
|
+
rc, out = self._gh(*cmd)
|
|
143
|
+
if rc != 0:
|
|
144
|
+
return [f"labels: creating {name} failed: {out}"]
|
|
145
|
+
made.append(name)
|
|
146
|
+
have.add(name)
|
|
147
|
+
return [f"labels: created {', '.join(made)}" if made else "labels: all already there"]
|
|
148
|
+
|
|
149
|
+
def _issues(self) -> Optional[list]:
|
|
150
|
+
rc, out = self._gh("issue", "list", "--repo", self.repo, "--state", "all", "--limit", "100", "--json", "number,title")
|
|
151
|
+
return json.loads(out or "[]") if rc == 0 else None
|
|
152
|
+
|
|
153
|
+
def issues(self, project_name: str, drafts_dir: Optional[str] = None) -> List[str]:
|
|
154
|
+
existing = self._issues()
|
|
155
|
+
if existing is None:
|
|
156
|
+
return ["issues: could not list them"]
|
|
157
|
+
titles = {i["title"] for i in existing}
|
|
158
|
+
if all(t in titles for t, _, _ in ISSUES):
|
|
159
|
+
return ["issues: Roadmap and Quick Notes already there"]
|
|
160
|
+
if existing:
|
|
161
|
+
# They would not be #1 and #2: refuse rather than take later numbers.
|
|
162
|
+
return [f"issues: the repository already has {len(existing)} issue(s); Roadmap and Quick Notes "
|
|
163
|
+
"were not created, because they would not be #1 and #2"]
|
|
164
|
+
report = []
|
|
165
|
+
for title, labels, template in ISSUES:
|
|
166
|
+
body = render_issue(template, project_name)
|
|
167
|
+
if drafts_dir and os.path.isdir(drafts_dir):
|
|
168
|
+
Path(drafts_dir, "issue_" + template.replace(".md.template", ".md")).write_text(body, encoding="utf-8")
|
|
169
|
+
with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as f:
|
|
170
|
+
f.write(body)
|
|
171
|
+
body_file = f.name
|
|
172
|
+
try:
|
|
173
|
+
rc, out = self._gh("issue", "create", "--repo", self.repo, "--title", title,
|
|
174
|
+
"--label", labels, "--body-file", body_file)
|
|
175
|
+
finally:
|
|
176
|
+
os.unlink(body_file)
|
|
177
|
+
if rc != 0:
|
|
178
|
+
return report + [f"issues: creating {title!r} failed: {out}"]
|
|
179
|
+
m = re.search(r"/issues/(\d+)", out)
|
|
180
|
+
report.append(f"issues: #{m.group(1) if m else '?'} {title}")
|
|
181
|
+
return report
|
|
182
|
+
|
|
183
|
+
def pin(self) -> List[str]:
|
|
184
|
+
existing = self._issues() or []
|
|
185
|
+
owner, name = self.repo.split("/", 1)
|
|
186
|
+
q = ('query { repository(owner: "%s", name: "%s") { pinnedIssues(first: 10) '
|
|
187
|
+
'{ nodes { issue { number } } } } }') % (owner, name)
|
|
188
|
+
rc, out = self._gh("api", "graphql", "-f", f"query={q}")
|
|
189
|
+
pinned = set()
|
|
190
|
+
if rc == 0:
|
|
191
|
+
try:
|
|
192
|
+
nodes = json.loads(out)["data"]["repository"]["pinnedIssues"]["nodes"]
|
|
193
|
+
pinned = {n["issue"]["number"] for n in nodes}
|
|
194
|
+
except (ValueError, KeyError, TypeError):
|
|
195
|
+
pinned = set()
|
|
196
|
+
report = []
|
|
197
|
+
for title, _, _ in ISSUES:
|
|
198
|
+
number = next((i["number"] for i in existing if i["title"] == title), None)
|
|
199
|
+
if number is None:
|
|
200
|
+
report.append(f"pin: {title!r} not found")
|
|
201
|
+
continue
|
|
202
|
+
if number in pinned:
|
|
203
|
+
report.append(f"pin: #{number} already pinned")
|
|
204
|
+
continue
|
|
205
|
+
rc, node = self._gh("issue", "view", str(number), "--repo", self.repo, "--json", "id", "-q", ".id")
|
|
206
|
+
if rc != 0 or not node.strip():
|
|
207
|
+
report.append(f"pin: could not read #{number}'s id: {node}")
|
|
208
|
+
continue
|
|
209
|
+
m = 'mutation { pinIssue(input: {issueId: "%s"}) { issue { number } } }' % node.strip()
|
|
210
|
+
rc, out = self._gh("api", "graphql", "-f", f"query={m}")
|
|
211
|
+
report.append(f"pin: #{number} pinned" if rc == 0 else f"pin: #{number} failed: {out}")
|
|
212
|
+
return report
|
|
213
|
+
|
|
214
|
+
# --- after the push ---------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
def discussions(self) -> List[str]:
|
|
217
|
+
rc, out = self._gh("repo", "edit", self.repo, "--enable-discussions")
|
|
218
|
+
return ["discussions: on" if rc == 0 else f"discussions: failed: {out}"]
|
|
219
|
+
|
|
220
|
+
def topics(self, topics: List[str]) -> List[str]:
|
|
221
|
+
if not topics:
|
|
222
|
+
return ["topics: none named (--topics a,b)"]
|
|
223
|
+
args = ["repo", "edit", self.repo]
|
|
224
|
+
for t in topics:
|
|
225
|
+
args += ["--add-topic", t]
|
|
226
|
+
rc, out = self._gh(*args)
|
|
227
|
+
return [f"topics: {', '.join(topics)}" if rc == 0 else f"topics: failed: {out}"]
|
|
228
|
+
|
|
229
|
+
def pypi_environment(self, archetype: Optional[str]) -> List[str]:
|
|
230
|
+
if archetype not in PUBLISHES_TO_PYPI:
|
|
231
|
+
return []
|
|
232
|
+
rc, out = self._gh("api", f"repos/{self.repo}/environments/pypi", "-X", "PUT")
|
|
233
|
+
return ["pypi-environment: created (register the trusted publisher on PyPI: workflow release.yml, environment pypi)"
|
|
234
|
+
if rc == 0 else f"pypi-environment: failed: {out}"]
|
|
235
|
+
|
|
236
|
+
def traffic(self, project_name: str) -> List[str]:
|
|
237
|
+
return ["traffic: ghtraf creates gists and needs a secret only you can set, so it is not run here.",
|
|
238
|
+
f" From the project: `ghtraf create --help` (the old route used --configure --ci-workflows "
|
|
239
|
+
f"--non-interactive), then `gh secret set TRAFFIC_GIST_TOKEN --repo {self.repo}`"]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def reminders(repo: str, extras: List[str]) -> List[str]:
|
|
243
|
+
"""What has no API: said once, after the setup."""
|
|
244
|
+
out = [f"CI: watch the first run with `gh run list -R {repo} --limit 5`"]
|
|
245
|
+
if "funding" in extras:
|
|
246
|
+
out.append("Sponsorships: Settings > General > Features > Sponsorships (FUNDING.yml is in .github/)")
|
|
247
|
+
out.append("Pages, if the project serves docs/: Settings > Pages > main, /docs")
|
|
248
|
+
return out
|