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.
Files changed (25) hide show
  1. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/PKG-INFO +1 -1
  2. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/task_utils.py +38 -12
  3. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/tasks.py +37 -11
  4. itw_python_builder-0.2.11/itw_python_builder/templates/new_version_email.html +105 -0
  5. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/PKG-INFO +1 -1
  6. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/pyproject.toml +1 -1
  7. itw_python_builder-0.2.8/itw_python_builder/templates/new_version_email.html +0 -38
  8. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/LICENSE +0 -0
  9. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/README.md +0 -0
  10. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/.pylintrc +0 -0
  11. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/__init__.py +0 -0
  12. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/_pyruntime/sitecustomize.py +0 -0
  13. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/cli.py +0 -0
  14. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/notify.py +0 -0
  15. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/ssr_tasks.py +0 -0
  16. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/templates/server.sitemap.snippet.ts +0 -0
  17. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/templates/sitemap.routes.ts +0 -0
  18. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/utils.py +0 -0
  19. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder/version.py +0 -0
  20. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/SOURCES.txt +0 -0
  21. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/dependency_links.txt +0 -0
  22. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/entry_points.txt +0 -0
  23. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/requires.txt +0 -0
  24. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/itw_python_builder.egg-info/top_level.txt +0 -0
  25. {itw_python_builder-0.2.8 → itw_python_builder-0.2.11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itw_python_builder
3
- Version: 0.2.8
3
+ Version: 0.2.11
4
4
  Summary: Standardized Django deployment pipeline with Docker, testing, and SonarQube integration
5
5
  Author-email: IT-Works <contact@it-works.io>
6
6
  License: MIT
@@ -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
- lines = block.splitlines()
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
- if not os.path.isdir(os.path.join(os.getcwd(), '.git')):
135
- raise RuntimeError('Not a git repository (no .git folder here).')
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
- raise
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 if it's an RC, or abort if it's a release."""
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
- raise RuntimeError(f"Tag {version} already exists! Aborting before pipeline to prevent overwrite.")
440
- print(f"[itw] Tag {version} already exists, auto-incrementing release candidate...")
441
- version.increment_release_candidate()
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>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itw_python_builder
3
- Version: 0.2.8
3
+ Version: 0.2.11
4
4
  Summary: Standardized Django deployment pipeline with Docker, testing, and SonarQube integration
5
5
  Author-email: IT-Works <contact@it-works.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "itw_python_builder"
7
- version = "0.2.8"
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>