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.
Files changed (22) hide show
  1. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/PKG-INFO +1 -1
  2. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/tasks.py +63 -1
  3. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/utils.py +64 -4
  4. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/PKG-INFO +1 -1
  5. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/pyproject.toml +1 -1
  6. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/LICENSE +0 -0
  7. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/README.md +0 -0
  8. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/.pylintrc +0 -0
  9. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/__init__.py +0 -0
  10. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/cli.py +0 -0
  11. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/notify.py +0 -0
  12. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/ssr_tasks.py +0 -0
  13. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/templates/new_version_email.html +0 -0
  14. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/templates/server.sitemap.snippet.ts +0 -0
  15. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/templates/sitemap.routes.ts +0 -0
  16. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder/version.py +0 -0
  17. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/SOURCES.txt +0 -0
  18. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/dependency_links.txt +0 -0
  19. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/entry_points.txt +0 -0
  20. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/requires.txt +0 -0
  21. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/itw_python_builder.egg-info/top_level.txt +0 -0
  22. {itw_python_builder-0.1.44 → itw_python_builder-0.1.46}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itw_python_builder
3
- Version: 0.1.44
3
+ Version: 0.1.46
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
@@ -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
- _ = check_branch(ctx)
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
- result = ctx.run('git describe --tags $(git rev-list --tags --max-count=1)')
181
- latest_tag = result.stdout.splitlines()[0]
182
- return Version.parse(latest_tag)
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={'PRIVATE-TOKEN': token},
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itw_python_builder
3
- Version: 0.1.44
3
+ Version: 0.1.46
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.1.44"
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"