itw-python-builder 0.2.8__tar.gz → 0.2.11__tar.gz
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.
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/PKG-INFO +1 -1
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/task_utils.py +38 -12
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/tasks.py +37 -11
- itw_python_builder-0.2.11/itw_python_builder/templates/new_version_email.html +105 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/PKG-INFO +1 -1
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/pyproject.toml +1 -1
- itw_python_builder-0.2.8/itw_python_builder/templates/new_version_email.html +0 -38
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/LICENSE +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/README.md +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/.pylintrc +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/__init__.py +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/_pyruntime/sitecustomize.py +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/cli.py +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/notify.py +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/ssr_tasks.py +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/templates/server.sitemap.snippet.ts +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/templates/sitemap.routes.ts +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/utils.py +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/version.py +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/SOURCES.txt +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/dependency_links.txt +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/entry_points.txt +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/requires.txt +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/top_level.txt +0 -0
- {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/setup.cfg +0 -0
|
@@ -141,10 +141,37 @@ def _strip_milestone_wrap(raw: str) -> str:
|
|
|
141
141
|
return raw
|
|
142
142
|
|
|
143
143
|
|
|
144
|
+
_CRED_KEYS = ('glab_host', 'glab_username', 'glab_token')
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def extract_glab_credentials(file_path: str) -> dict:
|
|
148
|
+
"""Scan md file for /glab_host, /glab_username, /glab_token top-level directives."""
|
|
149
|
+
with open(file_path, 'r', encoding='utf-8') as fp:
|
|
150
|
+
content = fp.read().replace('\r\n', '\n')
|
|
151
|
+
creds = {k: None for k in _CRED_KEYS}
|
|
152
|
+
for line in content.splitlines():
|
|
153
|
+
stripped = line.strip()
|
|
154
|
+
for key in _CRED_KEYS:
|
|
155
|
+
m = re.match(rf'^/{key}\s+(\S+)\s*$', stripped, re.IGNORECASE)
|
|
156
|
+
if m:
|
|
157
|
+
creds[key] = m.group(1)
|
|
158
|
+
break
|
|
159
|
+
return creds
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def strip_glab_credentials_lines(content: str) -> str:
|
|
163
|
+
"""Remove /glab_* lines so they don't leak into issue bodies."""
|
|
164
|
+
pattern = re.compile(
|
|
165
|
+
rf'(?im)^\s*/(?:{"|".join(_CRED_KEYS)})\s+\S+\s*$\n?'
|
|
166
|
+
)
|
|
167
|
+
return pattern.sub('', content)
|
|
168
|
+
|
|
169
|
+
|
|
144
170
|
def parse_md_issues(file_path: str) -> list:
|
|
145
171
|
"""Parse a markdown file into structured issue dicts separated by '==='."""
|
|
146
172
|
with open(file_path, 'r', encoding='utf-8') as fp:
|
|
147
173
|
content = fp.read().replace('\r\n', '\n')
|
|
174
|
+
content = strip_glab_credentials_lines(content)
|
|
148
175
|
blocks = re.split(r'(?m)^\s*===\s*$', content)
|
|
149
176
|
issues = []
|
|
150
177
|
for idx, block in enumerate(blocks, start=1):
|
|
@@ -171,18 +198,7 @@ def _parse_single_issue(block: str, idx: int, file_path: str) -> dict:
|
|
|
171
198
|
'but no closing "## Comment" tag.'
|
|
172
199
|
)
|
|
173
200
|
|
|
174
|
-
|
|
175
|
-
title = None
|
|
176
|
-
body_start = 0
|
|
177
|
-
for i, line in enumerate(lines):
|
|
178
|
-
if line.strip():
|
|
179
|
-
title = line.lstrip('#').strip()
|
|
180
|
-
body_start = i + 1
|
|
181
|
-
break
|
|
182
|
-
if not title:
|
|
183
|
-
raise RuntimeError(f'Block {idx} in {file_path}: missing title (first non-empty line).')
|
|
184
|
-
|
|
185
|
-
remaining = '\n'.join(lines[body_start:])
|
|
201
|
+
remaining = block
|
|
186
202
|
|
|
187
203
|
comment_text = None
|
|
188
204
|
if len(cm_headers) >= 2:
|
|
@@ -198,6 +214,7 @@ def _parse_single_issue(block: str, idx: int, file_path: str) -> dict:
|
|
|
198
214
|
acceptance_text = m.group(1).strip()
|
|
199
215
|
remaining = _RE_AC_BLOCK.sub('', remaining, count=1)
|
|
200
216
|
|
|
217
|
+
title = None
|
|
201
218
|
repo = None
|
|
202
219
|
milestone = None
|
|
203
220
|
milestone_start = None
|
|
@@ -210,6 +227,10 @@ def _parse_single_issue(block: str, idx: int, file_path: str) -> dict:
|
|
|
210
227
|
for line in remaining.splitlines():
|
|
211
228
|
stripped = line.strip()
|
|
212
229
|
|
|
230
|
+
m = re.match(r'^/title\s+(.+)$', stripped, re.IGNORECASE)
|
|
231
|
+
if m:
|
|
232
|
+
title = m.group(1).strip()
|
|
233
|
+
continue
|
|
213
234
|
m = re.match(r'^/repo\s+(\S+)\s*$', stripped, re.IGNORECASE)
|
|
214
235
|
if m:
|
|
215
236
|
repo = m.group(1)
|
|
@@ -238,6 +259,11 @@ def _parse_single_issue(block: str, idx: int, file_path: str) -> dict:
|
|
|
238
259
|
|
|
239
260
|
body_lines.append(line)
|
|
240
261
|
|
|
262
|
+
if not title:
|
|
263
|
+
raise RuntimeError(
|
|
264
|
+
f'Block {idx} in {file_path}: missing title. Add a "/title <text>" line to the block.'
|
|
265
|
+
)
|
|
266
|
+
|
|
241
267
|
body = '\n'.join(body_lines).strip()
|
|
242
268
|
return {
|
|
243
269
|
'title': title,
|
|
@@ -34,6 +34,7 @@ from itw_python_builder.task_utils import (
|
|
|
34
34
|
resolve_pm_repo,
|
|
35
35
|
create_gitlab_issue,
|
|
36
36
|
parse_md_issues,
|
|
37
|
+
extract_glab_credentials,
|
|
37
38
|
build_issue_description,
|
|
38
39
|
get_project_by_path,
|
|
39
40
|
find_project_milestone,
|
|
@@ -131,20 +132,38 @@ def create_task(ctx: Context, file=None):
|
|
|
131
132
|
raise RuntimeError(f'File must be .md, got {file!r}.')
|
|
132
133
|
if not os.path.isfile(file):
|
|
133
134
|
raise RuntimeError(f'File not found: {file}')
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
|
|
136
|
+
creds = extract_glab_credentials(file)
|
|
137
|
+
provided = [k for k, v in creds.items() if v]
|
|
138
|
+
if provided and len(provided) != 3:
|
|
139
|
+
missing = [k for k, v in creds.items() if not v]
|
|
140
|
+
log_red(
|
|
141
|
+
f'Partial GitLab credentials in {file}: missing {", ".join("/" + m for m in missing)}. '
|
|
142
|
+
'Provide all of /glab_host, /glab_username, /glab_token or none.'
|
|
143
|
+
)
|
|
144
|
+
sys.exit(1)
|
|
145
|
+
|
|
146
|
+
if provided:
|
|
147
|
+
host = creds['glab_host']
|
|
148
|
+
token = creds['glab_token']
|
|
149
|
+
api_host = _GITLAB_API_HOST_MAP.get(host, host)
|
|
150
|
+
project_path = None
|
|
151
|
+
log_info(f'Using {host} and given credentials to authenticate in GitLab')
|
|
152
|
+
else:
|
|
153
|
+
if not os.path.isdir(os.path.join(os.getcwd(), '.git')):
|
|
154
|
+
raise RuntimeError('Not a git repository (no .git folder here).')
|
|
155
|
+
log_green('Using .git in current directory to authenticate in GitLab')
|
|
156
|
+
token = ensure_gitlab_token(ctx)
|
|
157
|
+
host, project_path = _parse_gitlab_remote(ctx)
|
|
158
|
+
api_host = _GITLAB_API_HOST_MAP.get(host, host)
|
|
136
159
|
|
|
137
160
|
try:
|
|
138
161
|
issues = parse_md_issues(file)
|
|
139
162
|
except RuntimeError as exc:
|
|
140
163
|
log_red(str(exc))
|
|
141
|
-
|
|
164
|
+
sys.exit(1)
|
|
142
165
|
print(f'[itw] Parsed {len(issues)} issue(s) from {file}.')
|
|
143
166
|
|
|
144
|
-
token = ensure_gitlab_token(ctx)
|
|
145
|
-
host, project_path = _parse_gitlab_remote(ctx)
|
|
146
|
-
api_host = _GITLAB_API_HOST_MAP.get(host, host)
|
|
147
|
-
|
|
148
167
|
for idx, issue in enumerate(issues, start=1):
|
|
149
168
|
print(f'\n[itw] Issue {idx}/{len(issues)}: {issue["title"]}')
|
|
150
169
|
try:
|
|
@@ -199,6 +218,11 @@ def _resolve_target_project(api_host: str, project_path: str, issue: dict, token
|
|
|
199
218
|
log_info(f'Using {issue["repo"]} repo')
|
|
200
219
|
return get_project_by_path(api_host, issue['repo'], token)
|
|
201
220
|
|
|
221
|
+
if not project_path:
|
|
222
|
+
raise RuntimeError(
|
|
223
|
+
'No /repo defined and no .git directory to auto-detect from. '
|
|
224
|
+
'Add a /repo directive to the block or run inside a git repo.'
|
|
225
|
+
)
|
|
202
226
|
log_green(f'Using current git repo {project_path}')
|
|
203
227
|
candidates = resolve_pm_repo(api_host, project_path, token)
|
|
204
228
|
if not candidates:
|
|
@@ -433,12 +457,14 @@ def taginit(ctx: Context) -> None:
|
|
|
433
457
|
|
|
434
458
|
|
|
435
459
|
def _ensure_unique_tag(ctx: Context, version: Version) -> None:
|
|
436
|
-
"""If the tag already exists, auto-increment RC
|
|
460
|
+
"""If the tag already exists, auto-increment RC (RC tags) or patch (release tags)."""
|
|
437
461
|
while ctx.run(f'git rev-parse -q --verify "refs/tags/{version}"', warn=True, hide=True).ok:
|
|
438
462
|
if version.release_candidate == 0:
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
463
|
+
print(f"[itw] Tag {version} already exists, auto-incrementing patch...")
|
|
464
|
+
version.increment_patch(release=True)
|
|
465
|
+
else:
|
|
466
|
+
print(f"[itw] Tag {version} already exists, auto-incrementing release candidate...")
|
|
467
|
+
version.increment_release_candidate()
|
|
442
468
|
|
|
443
469
|
|
|
444
470
|
@task
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="sq">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>itw-python-builder — Version i ri</title>
|
|
6
|
+
<style>
|
|
7
|
+
.email-body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
background: #f4f6f8;
|
|
11
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
12
|
+
color: #1f2937;
|
|
13
|
+
}
|
|
14
|
+
.outer-table {
|
|
15
|
+
background: #f4f6f8;
|
|
16
|
+
padding: 24px 0;
|
|
17
|
+
}
|
|
18
|
+
.container-table {
|
|
19
|
+
background: #ffffff;
|
|
20
|
+
border-radius: 8px;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
23
|
+
}
|
|
24
|
+
.header-cell {
|
|
25
|
+
background: #0f172a;
|
|
26
|
+
color: #ffffff;
|
|
27
|
+
padding: 20px 28px;
|
|
28
|
+
}
|
|
29
|
+
.header-title {
|
|
30
|
+
margin: 0;
|
|
31
|
+
font-size: 20px;
|
|
32
|
+
}
|
|
33
|
+
.content-cell {
|
|
34
|
+
padding: 28px;
|
|
35
|
+
}
|
|
36
|
+
.text-paragraph {
|
|
37
|
+
margin: 0 0 16px 0;
|
|
38
|
+
font-size: 15px;
|
|
39
|
+
}
|
|
40
|
+
.text-label {
|
|
41
|
+
margin: 0 0 8px 0;
|
|
42
|
+
font-size: 15px;
|
|
43
|
+
}
|
|
44
|
+
.commit-box {
|
|
45
|
+
background: #f3f4f6;
|
|
46
|
+
border-left: 4px solid #0f172a;
|
|
47
|
+
padding: 12px 16px;
|
|
48
|
+
font-size: 14px;
|
|
49
|
+
line-height: 1.5;
|
|
50
|
+
color: #374151;
|
|
51
|
+
margin: 0 0 20px 0;
|
|
52
|
+
}
|
|
53
|
+
.command-box {
|
|
54
|
+
background: #0f172a;
|
|
55
|
+
color: #f9fafb;
|
|
56
|
+
padding: 14px 16px;
|
|
57
|
+
border-radius: 6px;
|
|
58
|
+
font-size: 13px;
|
|
59
|
+
overflow-x: auto;
|
|
60
|
+
margin: 0 0 20px 0;
|
|
61
|
+
-webkit-user-select: all;
|
|
62
|
+
-moz-user-select: all;
|
|
63
|
+
-ms-user-select: all;
|
|
64
|
+
user-select: all;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
}
|
|
67
|
+
.footer-text {
|
|
68
|
+
margin: 0;
|
|
69
|
+
font-size: 13px;
|
|
70
|
+
color: #6b7280;
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body class="email-body">
|
|
75
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" class="outer-table">
|
|
76
|
+
<tr>
|
|
77
|
+
<td align="center">
|
|
78
|
+
<table role="presentation" width="600" cellpadding="0" cellspacing="0" class="container-table">
|
|
79
|
+
<tr>
|
|
80
|
+
<td class="header-cell">
|
|
81
|
+
<h1 class="header-title">itw-python-builder — Version i ri</h1>
|
|
82
|
+
</td>
|
|
83
|
+
</tr>
|
|
84
|
+
<tr>
|
|
85
|
+
<td class="content-cell">
|
|
86
|
+
<p class="text-paragraph">Përshëndetje kolegë,</p>
|
|
87
|
+
<p class="text-paragraph">
|
|
88
|
+
Një version i ri i paketës <strong>itw-python-builder</strong> është publikuar:
|
|
89
|
+
<strong>{VERSION}</strong>.
|
|
90
|
+
</p>
|
|
91
|
+
<p class="text-label"><strong>Mesazhi i commit-it:</strong></p>
|
|
92
|
+
<div class="commit-box">
|
|
93
|
+
{COMMIT_MESSAGE}
|
|
94
|
+
</div>
|
|
95
|
+
<p class="text-label"><strong>Komanda për të bërë upgrade:</strong></p>
|
|
96
|
+
<pre class="command-box" title="Kliko për të përzgjedhur të gjithë komandën">pip install itw-python-builder=={VERSION} --no-cache-dir</pre>
|
|
97
|
+
<p class="footer-text">Faleminderit</p>
|
|
98
|
+
</td>
|
|
99
|
+
</tr>
|
|
100
|
+
</table>
|
|
101
|
+
</td>
|
|
102
|
+
</tr>
|
|
103
|
+
</table>
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "itw_python_builder"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.11"
|
|
8
8
|
description = "Standardized Django deployment pipeline with Docker, testing, and SonarQube integration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="sq">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<title>itw-python-builder — Version i ri</title>
|
|
6
|
-
</head>
|
|
7
|
-
<body style="margin:0;padding:0;background:#f4f6f8;font-family:Arial,Helvetica,sans-serif;color:#1f2937;">
|
|
8
|
-
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f6f8;padding:24px 0;">
|
|
9
|
-
<tr>
|
|
10
|
-
<td align="center">
|
|
11
|
-
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,0.08);">
|
|
12
|
-
<tr>
|
|
13
|
-
<td style="background:#0f172a;color:#ffffff;padding:20px 28px;">
|
|
14
|
-
<h1 style="margin:0;font-size:20px;">itw-python-builder — Version i ri</h1>
|
|
15
|
-
</td>
|
|
16
|
-
</tr>
|
|
17
|
-
<tr>
|
|
18
|
-
<td style="padding:28px;">
|
|
19
|
-
<p style="margin:0 0 16px 0;font-size:15px;">Përshëndetje kolegë,</p>
|
|
20
|
-
<p style="margin:0 0 16px 0;font-size:15px;">
|
|
21
|
-
Një version i ri i paketës <strong>itw-python-builder</strong> është publikuar:
|
|
22
|
-
<strong>{VERSION}</strong>.
|
|
23
|
-
</p>
|
|
24
|
-
<p style="margin:0 0 8px 0;font-size:15px;"><strong>Mesazhi i commit-it:</strong></p>
|
|
25
|
-
<div style="background:#f3f4f6;border-left:4px solid #0f172a;padding:12px 16px;font-size:14px;line-height:1.5;color:#374151;margin:0 0 20px 0;">
|
|
26
|
-
{COMMIT_MESSAGE}
|
|
27
|
-
</div>
|
|
28
|
-
<p style="margin:0 0 8px 0;font-size:15px;"><strong>Komanda për të bërë upgrade:</strong></p>
|
|
29
|
-
<pre style="background:#0f172a;color:#f9fafb;padding:14px 16px;border-radius:6px;font-size:13px;overflow-x:auto;margin:0 0 20px 0;">pip install itw-python-builder=={VERSION} --no-cache-dir</pre>
|
|
30
|
-
<p style="margin:0;font-size:13px;color:#6b7280;">Faleminderit</p>
|
|
31
|
-
</td>
|
|
32
|
-
</tr>
|
|
33
|
-
</table>
|
|
34
|
-
</td>
|
|
35
|
-
</tr>
|
|
36
|
-
</table>
|
|
37
|
-
</body>
|
|
38
|
-
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/requires.txt
RENAMED
|
File without changes
|
{itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|