itw-python-builder 0.1.44__tar.gz → 0.1.46__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.1.44 → itw_python_builder-0.1.46}/PKG-INFO +1 -1
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/tasks.py +63 -1
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/utils.py +64 -4
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/PKG-INFO +1 -1
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/pyproject.toml +1 -1
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/LICENSE +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/README.md +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/.pylintrc +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/__init__.py +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/cli.py +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/notify.py +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/ssr_tasks.py +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/templates/new_version_email.html +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/templates/server.sitemap.snippet.ts +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/templates/sitemap.routes.ts +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/version.py +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/SOURCES.txt +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/dependency_links.txt +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/entry_points.txt +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/requires.txt +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/top_level.txt +0 -0
- {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/setup.cfg +0 -0
|
@@ -22,6 +22,7 @@ from itw_python_builder.utils import (
|
|
|
22
22
|
save_token_to_cache,
|
|
23
23
|
probe_gitlab_token,
|
|
24
24
|
TOKEN_CACHE_PATH,
|
|
25
|
+
maybe_override_release_version,
|
|
25
26
|
)
|
|
26
27
|
from itw_python_builder.ssr_tasks import * # noqa: F401,F403 — exposes ssr-init
|
|
27
28
|
|
|
@@ -169,6 +170,54 @@ def push_version(ctx: Context) -> None:
|
|
|
169
170
|
ctx.run(f'git push origin {get_current_branch(ctx)}')
|
|
170
171
|
|
|
171
172
|
|
|
173
|
+
_CHANGELOG_TARGETS = {
|
|
174
|
+
'master': ['staging', 'develop'],
|
|
175
|
+
'staging': ['develop'],
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _sync_changelog_to_branch(ctx: Context, source: str, target: str) -> None:
|
|
180
|
+
"""Apply CHANGELOG.md from source onto target, commit, push, then return to source."""
|
|
181
|
+
print(f'[itw] Propagating CHANGELOG.md to {target}...')
|
|
182
|
+
checkout = ctx.run(f'git checkout {target}', warn=True, hide=True)
|
|
183
|
+
if not checkout.ok:
|
|
184
|
+
fetch = ctx.run(f'git fetch origin {target}:{target}', warn=True, hide=True)
|
|
185
|
+
if not fetch.ok:
|
|
186
|
+
raise RuntimeError(f'branch {target!r} not found locally or on origin')
|
|
187
|
+
ctx.run(f'git checkout {target}')
|
|
188
|
+
ctx.run(f'git pull --rebase origin {target}', warn=True)
|
|
189
|
+
ctx.run(f'git checkout {source} -- CHANGELOG.md')
|
|
190
|
+
diff = ctx.run('git diff --cached --quiet CHANGELOG.md', warn=True, hide=True)
|
|
191
|
+
if diff.ok:
|
|
192
|
+
print(f'[itw] CHANGELOG.md already up to date on {target} — nothing to commit.')
|
|
193
|
+
else:
|
|
194
|
+
ctx.run(f'git commit -m "changelog sync from {source}"')
|
|
195
|
+
ctx.run(f'git push origin {target}')
|
|
196
|
+
print(f'[itw] CHANGELOG.md pushed to {target}.')
|
|
197
|
+
ctx.run(f'git checkout {source}')
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def propagate_changelog(ctx: Context) -> None:
|
|
201
|
+
"""Push CHANGELOG.md from current branch to develop (+ staging on master)."""
|
|
202
|
+
source = get_current_branch(ctx)
|
|
203
|
+
targets = _CHANGELOG_TARGETS.get(source)
|
|
204
|
+
if not targets:
|
|
205
|
+
return
|
|
206
|
+
if not os.path.exists('CHANGELOG.md'):
|
|
207
|
+
print('[itw] No CHANGELOG.md to propagate — skipping.')
|
|
208
|
+
return
|
|
209
|
+
dirty = ctx.run('git status --porcelain', hide=True).stdout.strip()
|
|
210
|
+
if dirty:
|
|
211
|
+
print('[itw] Working tree not clean — skipping CHANGELOG propagation.')
|
|
212
|
+
return
|
|
213
|
+
for target in targets:
|
|
214
|
+
try:
|
|
215
|
+
_sync_changelog_to_branch(ctx, source, target)
|
|
216
|
+
except Exception as exc:
|
|
217
|
+
print(f'[itw] Failed to propagate CHANGELOG.md to {target}: {exc}')
|
|
218
|
+
ctx.run(f'git checkout {source}', warn=True, hide=True)
|
|
219
|
+
|
|
220
|
+
|
|
172
221
|
def tag(ctx: Context, version: Version) -> None:
|
|
173
222
|
ctx.run(f'git tag {version}')
|
|
174
223
|
|
|
@@ -245,6 +294,7 @@ def incrementrc(ctx: Context, skip_pipeline=False, pylintrc=None, ssr=False) ->
|
|
|
245
294
|
save_version(version)
|
|
246
295
|
commit_version(ctx, version)
|
|
247
296
|
push_version(ctx)
|
|
297
|
+
propagate_changelog(ctx)
|
|
248
298
|
|
|
249
299
|
|
|
250
300
|
@task
|
|
@@ -254,11 +304,14 @@ def incrementpatch(ctx: Context, skip_pipeline=False, ssr=False) -> None:
|
|
|
254
304
|
ensure_gitlab_auth(ctx)
|
|
255
305
|
version = read_version(ctx)
|
|
256
306
|
version.increment_patch(release=production)
|
|
307
|
+
if production and detect_project_type() == 'frontend':
|
|
308
|
+
version = maybe_override_release_version(version)
|
|
257
309
|
_ensure_unique_tag(ctx, version)
|
|
258
310
|
tag_build_push(ctx, version, skip_pipeline, ssr=ssr)
|
|
259
311
|
save_version(version)
|
|
260
312
|
commit_version(ctx, version)
|
|
261
313
|
push_version(ctx)
|
|
314
|
+
propagate_changelog(ctx)
|
|
262
315
|
|
|
263
316
|
|
|
264
317
|
@task
|
|
@@ -268,11 +321,14 @@ def incrementminor(ctx: Context, skip_pipeline=False, ssr=False) -> None:
|
|
|
268
321
|
ensure_gitlab_auth(ctx)
|
|
269
322
|
version = read_version(ctx)
|
|
270
323
|
version.increment_minor(release=production)
|
|
324
|
+
if production and detect_project_type() == 'frontend':
|
|
325
|
+
version = maybe_override_release_version(version)
|
|
271
326
|
_ensure_unique_tag(ctx, version)
|
|
272
327
|
tag_build_push(ctx, version, skip_pipeline, ssr=ssr)
|
|
273
328
|
save_version(version)
|
|
274
329
|
commit_version(ctx, version)
|
|
275
330
|
push_version(ctx)
|
|
331
|
+
propagate_changelog(ctx)
|
|
276
332
|
|
|
277
333
|
|
|
278
334
|
@task
|
|
@@ -282,25 +338,31 @@ def incrementmajor(ctx: Context, skip_pipeline=False, ssr=False) -> None:
|
|
|
282
338
|
ensure_gitlab_auth(ctx)
|
|
283
339
|
version = read_version(ctx)
|
|
284
340
|
version.increment_major(release=production)
|
|
341
|
+
if production and detect_project_type() == 'frontend':
|
|
342
|
+
version = maybe_override_release_version(version)
|
|
285
343
|
_ensure_unique_tag(ctx, version)
|
|
286
344
|
tag_build_push(ctx, version, skip_pipeline, ssr=ssr)
|
|
287
345
|
save_version(version)
|
|
288
346
|
commit_version(ctx, version)
|
|
289
347
|
push_version(ctx)
|
|
348
|
+
propagate_changelog(ctx)
|
|
290
349
|
|
|
291
350
|
|
|
292
351
|
@task
|
|
293
352
|
def release(ctx: Context, skip_pipeline=False, ssr=False) -> None:
|
|
294
353
|
"""Promote release candidate to stable release and deploy. Use --ssr for Angular SSR builds."""
|
|
295
|
-
|
|
354
|
+
production = check_branch(ctx)
|
|
296
355
|
ensure_gitlab_auth(ctx)
|
|
297
356
|
version = read_version(ctx)
|
|
298
357
|
version.reset_release_candidate(release=True)
|
|
358
|
+
if production and detect_project_type() == 'frontend':
|
|
359
|
+
version = maybe_override_release_version(version)
|
|
299
360
|
_ensure_unique_tag(ctx, version)
|
|
300
361
|
tag_build_push(ctx, version, skip_pipeline, ssr=ssr)
|
|
301
362
|
save_version(version)
|
|
302
363
|
commit_version(ctx, version)
|
|
303
364
|
push_version(ctx)
|
|
365
|
+
propagate_changelog(ctx)
|
|
304
366
|
|
|
305
367
|
|
|
306
368
|
@task
|
|
@@ -177,9 +177,17 @@ def check_branch(ctx: Context) -> bool:
|
|
|
177
177
|
|
|
178
178
|
|
|
179
179
|
def get_latest_tag(ctx: Context) -> Version:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
"""Read latest tag for current branch — release tags on master, RC tags elsewhere."""
|
|
181
|
+
branch = get_current_branch(ctx)
|
|
182
|
+
pattern = 'v.*-release' if is_branch_production(branch) else 'v.*-rc*'
|
|
183
|
+
result = ctx.run(f'git tag --list "{pattern}" --sort=-v:refname', hide=True)
|
|
184
|
+
tags = [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
|
185
|
+
if not tags:
|
|
186
|
+
raise RuntimeError(
|
|
187
|
+
f'No tags matching {pattern!r} found on branch {branch!r}. '
|
|
188
|
+
'Run `itw tag-init` first if this is a new project.'
|
|
189
|
+
)
|
|
190
|
+
return Version.parse(tags[0])
|
|
183
191
|
|
|
184
192
|
|
|
185
193
|
def _update_package_json_version(version: Version) -> None:
|
|
@@ -334,7 +342,10 @@ def probe_gitlab_token(ctx: Context, project_type: str, api_host: str, username:
|
|
|
334
342
|
import urllib.request
|
|
335
343
|
req = urllib.request.Request(
|
|
336
344
|
f'https://{api_host}/api/v4/user',
|
|
337
|
-
headers={
|
|
345
|
+
headers={
|
|
346
|
+
'PRIVATE-TOKEN': token,
|
|
347
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
|
|
348
|
+
},
|
|
338
349
|
)
|
|
339
350
|
ssl_ctx = ssl.create_default_context()
|
|
340
351
|
ssl_ctx.check_hostname = False
|
|
@@ -348,6 +359,55 @@ def probe_gitlab_token(ctx: Context, project_type: str, api_host: str, username:
|
|
|
348
359
|
return 'unreachable'
|
|
349
360
|
|
|
350
361
|
|
|
362
|
+
def maybe_override_release_version(default: Version, timeout: float = 5.0) -> Version:
|
|
363
|
+
"""Offer a 5s window to override the computed release version via TTY (POSIX only)."""
|
|
364
|
+
print(f'[itw] Next release tag will be: {default}')
|
|
365
|
+
print(f'[itw] Press any key within {int(timeout)}s to enter a custom version (e.g. 2.3.0), or wait to accept.')
|
|
366
|
+
sys.stdout.flush()
|
|
367
|
+
|
|
368
|
+
if not sys.stdin.isatty():
|
|
369
|
+
print(f'[itw] Non-interactive stdin — using default {default}.')
|
|
370
|
+
return default
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
import select
|
|
374
|
+
import termios
|
|
375
|
+
import tty
|
|
376
|
+
except ImportError:
|
|
377
|
+
print(f'[itw] Interactive override unsupported on this platform — using default {default}.')
|
|
378
|
+
return default
|
|
379
|
+
|
|
380
|
+
fd = sys.stdin.fileno()
|
|
381
|
+
old = termios.tcgetattr(fd)
|
|
382
|
+
try:
|
|
383
|
+
tty.setcbreak(fd)
|
|
384
|
+
ready, _, _ = select.select([sys.stdin], [], [], timeout)
|
|
385
|
+
if not ready:
|
|
386
|
+
print(f'[itw] No input within {int(timeout)}s — using default {default}.')
|
|
387
|
+
return default
|
|
388
|
+
first = sys.stdin.read(1)
|
|
389
|
+
finally:
|
|
390
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
391
|
+
|
|
392
|
+
if first in ('\n', '\r'):
|
|
393
|
+
print(f'[itw] Empty input — using default {default}.')
|
|
394
|
+
return default
|
|
395
|
+
|
|
396
|
+
rest = sys.stdin.readline().rstrip('\r\n')
|
|
397
|
+
raw = (first + rest).strip()
|
|
398
|
+
if not raw:
|
|
399
|
+
print(f'[itw] Empty input — using default {default}.')
|
|
400
|
+
return default
|
|
401
|
+
|
|
402
|
+
candidate = f'v.{raw}-release'
|
|
403
|
+
try:
|
|
404
|
+
return Version.parse(candidate)
|
|
405
|
+
except ValueError as exc:
|
|
406
|
+
raise RuntimeError(
|
|
407
|
+
f'Invalid version input {raw!r} (must be like 2.3.0). Aborting.'
|
|
408
|
+
) from exc
|
|
409
|
+
|
|
410
|
+
|
|
351
411
|
def is_ssr_project() -> bool:
|
|
352
412
|
"""Return True if the current frontend project already has SSR scaffolding."""
|
|
353
413
|
cwd = os.getcwd()
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "itw_python_builder"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.46"
|
|
8
8
|
description = "Standardized Django deployment pipeline with Docker, testing, and SonarQube integration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
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.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/requires.txt
RENAMED
|
File without changes
|
{itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|