itw-python-builder 0.1.24__tar.gz → 0.1.25__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.24 → itw_python_builder-0.1.25}/PKG-INFO +1 -1
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder/tasks.py +124 -39
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder/version.py +4 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/PKG-INFO +1 -1
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/pyproject.toml +1 -1
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/LICENSE +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/README.md +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder/.pylintrc +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder/__init__.py +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder/cli.py +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/SOURCES.txt +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/dependency_links.txt +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/entry_points.txt +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/requires.txt +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/top_level.txt +0 -0
- {itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/setup.cfg +0 -0
|
@@ -3,6 +3,7 @@ from itw_python_builder.version import Version
|
|
|
3
3
|
import getpass
|
|
4
4
|
import io
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
from datetime import datetime
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
@@ -78,8 +79,29 @@ def detect_and_activate_venv():
|
|
|
78
79
|
print(f"[itw] Using venv: {venv_path}")
|
|
79
80
|
_venv_activated = True
|
|
80
81
|
|
|
81
|
-
def
|
|
82
|
-
"""
|
|
82
|
+
def _parse_angular_env_file(path: str) -> dict:
|
|
83
|
+
"""Extract string-valued keys from an Angular environment.ts object literal."""
|
|
84
|
+
with open(path, 'r', encoding='utf-8') as fp:
|
|
85
|
+
content = fp.read()
|
|
86
|
+
pattern = re.compile(r"""^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*['"]([^'"]*)['"]""", re.MULTILINE)
|
|
87
|
+
return dict(pattern.findall(content))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def load_env(ctx: Context = None):
|
|
91
|
+
"""Load env vars. Backend → .env in cwd. Frontend → src/environments/environment[.<branch>].ts."""
|
|
92
|
+
if detect_project_type() == 'frontend':
|
|
93
|
+
env_dir = os.path.join(os.getcwd(), 'src', 'environments')
|
|
94
|
+
candidates = []
|
|
95
|
+
if ctx is not None:
|
|
96
|
+
branch = get_current_branch(ctx)
|
|
97
|
+
candidates.append(os.path.join(env_dir, f'environment.{branch}.ts'))
|
|
98
|
+
candidates.append(os.path.join(env_dir, 'environment.ts'))
|
|
99
|
+
env_path = next((p for p in candidates if os.path.exists(p)), None)
|
|
100
|
+
if not env_path:
|
|
101
|
+
return
|
|
102
|
+
for key, value in _parse_angular_env_file(env_path).items():
|
|
103
|
+
os.environ.setdefault(key, value)
|
|
104
|
+
return
|
|
83
105
|
from decouple import Config, RepositoryEnv
|
|
84
106
|
env_path = os.path.join(os.getcwd(), '.env')
|
|
85
107
|
if not os.path.exists(env_path):
|
|
@@ -122,6 +144,39 @@ def get_latest_tag(ctx: Context) -> Version:
|
|
|
122
144
|
return Version.parse(latest_tag)
|
|
123
145
|
|
|
124
146
|
|
|
147
|
+
def _update_package_json_version(version: Version) -> None:
|
|
148
|
+
"""Rewrite only the `"version": "..."` line in package.json, preserving formatting."""
|
|
149
|
+
pkg_path = os.path.join(os.getcwd(), 'package.json')
|
|
150
|
+
with open(pkg_path, 'r', encoding='utf-8') as fp:
|
|
151
|
+
content = fp.read()
|
|
152
|
+
new_content, n = re.subn(
|
|
153
|
+
r'(^\s*"version"\s*:\s*")[^"]*(")',
|
|
154
|
+
lambda m: f'{m.group(1)}{version.bare_semver()}{m.group(2)}',
|
|
155
|
+
content,
|
|
156
|
+
count=1,
|
|
157
|
+
flags=re.MULTILINE,
|
|
158
|
+
)
|
|
159
|
+
if n == 0:
|
|
160
|
+
raise RuntimeError(f'Could not find "version" field in {pkg_path}')
|
|
161
|
+
with open(pkg_path, 'w', encoding='utf-8') as fp:
|
|
162
|
+
fp.write(new_content)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def read_version(ctx: Context) -> Version:
|
|
166
|
+
"""Read current version. Backend → VERSION file. Frontend → latest git tag."""
|
|
167
|
+
if detect_project_type() == 'frontend':
|
|
168
|
+
return get_latest_tag(ctx)
|
|
169
|
+
return Version.read_from_file()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def save_version(version: Version) -> None:
|
|
173
|
+
"""Persist version. Backend → VERSION file. Frontend → package.json (bare semver)."""
|
|
174
|
+
if detect_project_type() == 'frontend':
|
|
175
|
+
_update_package_json_version(version)
|
|
176
|
+
return
|
|
177
|
+
version.save_to_file()
|
|
178
|
+
|
|
179
|
+
|
|
125
180
|
def generate_image_path(ctx: Context, branch: str) -> str:
|
|
126
181
|
result = ctx.run('git remote get-url origin')
|
|
127
182
|
result = result.stdout.splitlines()[0]
|
|
@@ -159,7 +214,10 @@ def login(ctx: Context, username=None):
|
|
|
159
214
|
|
|
160
215
|
|
|
161
216
|
def commit_version(ctx: Context, version: Version) -> None:
|
|
162
|
-
|
|
217
|
+
if detect_project_type() == 'frontend':
|
|
218
|
+
ctx.run('git add package.json')
|
|
219
|
+
else:
|
|
220
|
+
ctx.run(f'git add {Version.VERSION_FILE_NAME}')
|
|
163
221
|
ctx.run('git add CHANGELOG.md')
|
|
164
222
|
ctx.run(f'git commit -m \'version update to {version}\'')
|
|
165
223
|
|
|
@@ -189,12 +247,14 @@ def pushimage(ctx: Context):
|
|
|
189
247
|
|
|
190
248
|
|
|
191
249
|
def tag_build_push(ctx: Context, version: Version, skip_pipeline: bool = False) -> None:
|
|
250
|
+
project_type = detect_project_type()
|
|
192
251
|
if not skip_pipeline:
|
|
193
252
|
test(ctx)
|
|
194
253
|
analyze(ctx)
|
|
195
254
|
tag(ctx, version)
|
|
196
|
-
|
|
197
|
-
|
|
255
|
+
if project_type == 'backend':
|
|
256
|
+
buildimage(ctx)
|
|
257
|
+
pushimage(ctx)
|
|
198
258
|
changelog(ctx, version)
|
|
199
259
|
push(ctx)
|
|
200
260
|
|
|
@@ -205,19 +265,22 @@ def taginit(ctx: Context) -> None:
|
|
|
205
265
|
check_branch(ctx)
|
|
206
266
|
version = Version(0, 1, 0, 1)
|
|
207
267
|
tag(ctx, version)
|
|
208
|
-
version
|
|
268
|
+
save_version(version)
|
|
209
269
|
|
|
210
270
|
|
|
211
271
|
@task
|
|
212
272
|
def incrementrc(ctx: Context, skip_pipeline=False, pylintrc=None) -> None:
|
|
213
273
|
"""Increment release candidate version and deploy"""
|
|
214
274
|
check_branch(ctx)
|
|
215
|
-
|
|
216
|
-
|
|
275
|
+
project_type = detect_project_type()
|
|
276
|
+
if project_type == 'backend':
|
|
277
|
+
login(ctx)
|
|
278
|
+
version = read_version(ctx)
|
|
217
279
|
version.increment_release_candidate()
|
|
218
|
-
|
|
280
|
+
if project_type == 'backend':
|
|
281
|
+
lint(ctx, pylintrc=pylintrc)
|
|
219
282
|
tag_build_push(ctx, version, skip_pipeline)
|
|
220
|
-
version
|
|
283
|
+
save_version(version)
|
|
221
284
|
commit_version(ctx, version)
|
|
222
285
|
push_version(ctx)
|
|
223
286
|
|
|
@@ -226,10 +289,10 @@ def incrementrc(ctx: Context, skip_pipeline=False, pylintrc=None) -> None:
|
|
|
226
289
|
def incrementpatch(ctx: Context, skip_pipeline=False) -> None:
|
|
227
290
|
"""Increment patch version, build, and deploy"""
|
|
228
291
|
production = check_branch(ctx)
|
|
229
|
-
version =
|
|
292
|
+
version = read_version(ctx)
|
|
230
293
|
version.increment_patch(release=production)
|
|
231
294
|
tag_build_push(ctx, version, skip_pipeline)
|
|
232
|
-
version
|
|
295
|
+
save_version(version)
|
|
233
296
|
commit_version(ctx, version)
|
|
234
297
|
push_version(ctx)
|
|
235
298
|
|
|
@@ -238,10 +301,10 @@ def incrementpatch(ctx: Context, skip_pipeline=False) -> None:
|
|
|
238
301
|
def incrementminor(ctx: Context, skip_pipeline=False) -> None:
|
|
239
302
|
"""Increment minor version, build, and deploy"""
|
|
240
303
|
production = check_branch(ctx)
|
|
241
|
-
version =
|
|
304
|
+
version = read_version(ctx)
|
|
242
305
|
version.increment_minor(release=production)
|
|
243
306
|
tag_build_push(ctx, version, skip_pipeline)
|
|
244
|
-
version
|
|
307
|
+
save_version(version)
|
|
245
308
|
commit_version(ctx, version)
|
|
246
309
|
push_version(ctx)
|
|
247
310
|
|
|
@@ -250,10 +313,10 @@ def incrementminor(ctx: Context, skip_pipeline=False) -> None:
|
|
|
250
313
|
def incrementmajor(ctx: Context, skip_pipeline=False) -> None:
|
|
251
314
|
"""Increment major version, build, and deploy"""
|
|
252
315
|
production = check_branch(ctx)
|
|
253
|
-
version =
|
|
316
|
+
version = read_version(ctx)
|
|
254
317
|
version.increment_major(release=production)
|
|
255
318
|
tag_build_push(ctx, version, skip_pipeline)
|
|
256
|
-
version
|
|
319
|
+
save_version(version)
|
|
257
320
|
commit_version(ctx, version)
|
|
258
321
|
push_version(ctx)
|
|
259
322
|
|
|
@@ -262,18 +325,22 @@ def incrementmajor(ctx: Context, skip_pipeline=False) -> None:
|
|
|
262
325
|
def release(ctx: Context, skip_pipeline=False) -> None:
|
|
263
326
|
"""Promote release candidate to stable release and deploy"""
|
|
264
327
|
_ = check_branch(ctx)
|
|
265
|
-
|
|
266
|
-
|
|
328
|
+
if detect_project_type() == 'backend':
|
|
329
|
+
login(ctx)
|
|
330
|
+
version = read_version(ctx)
|
|
267
331
|
version.reset_release_candidate(release=True)
|
|
268
332
|
tag_build_push(ctx, version, skip_pipeline)
|
|
269
|
-
version
|
|
333
|
+
save_version(version)
|
|
270
334
|
commit_version(ctx, version)
|
|
271
335
|
push_version(ctx)
|
|
272
336
|
|
|
273
337
|
|
|
274
338
|
@task
|
|
275
339
|
def test(ctx: Context, settings='backend.test_settings'):
|
|
276
|
-
"""Run
|
|
340
|
+
"""Run tests with coverage (Django or Angular/karma)"""
|
|
341
|
+
if detect_project_type() == 'frontend':
|
|
342
|
+
ctx.run('npx ng test --no-watch --code-coverage --browsers=ChromeHeadlessNoSandbox')
|
|
343
|
+
return
|
|
277
344
|
detect_and_activate_venv()
|
|
278
345
|
ctx.run(f'python -m coverage run manage.py test --settings={settings} --noinput -v 2')
|
|
279
346
|
ctx.run('python -m coverage report -m')
|
|
@@ -282,7 +349,10 @@ def test(ctx: Context, settings='backend.test_settings'):
|
|
|
282
349
|
|
|
283
350
|
@task
|
|
284
351
|
def lint(ctx: Context, pylintrc=None):
|
|
285
|
-
"""Run pylint, generate reports for SonarQube"""
|
|
352
|
+
"""Run pylint, generate reports for SonarQube (backend only)"""
|
|
353
|
+
if detect_project_type() == 'frontend':
|
|
354
|
+
print("[itw] Skipping lint for frontend (SonarQube handles code quality)")
|
|
355
|
+
return
|
|
286
356
|
detect_and_activate_venv()
|
|
287
357
|
rcfile = pylintrc if pylintrc else PYLINTRC
|
|
288
358
|
print("\n" + "=" * 60)
|
|
@@ -301,7 +371,10 @@ def lint(ctx: Context, pylintrc=None):
|
|
|
301
371
|
|
|
302
372
|
@task
|
|
303
373
|
def lintlocal(ctx: Context, pylintrc=None):
|
|
304
|
-
"""Run pylint locally with human-readable output (
|
|
374
|
+
"""Run pylint locally with human-readable output (backend only)"""
|
|
375
|
+
if detect_project_type() == 'frontend':
|
|
376
|
+
print("[itw] Skipping lint for frontend (SonarQube handles code quality)")
|
|
377
|
+
return
|
|
305
378
|
detect_and_activate_venv()
|
|
306
379
|
rcfile = pylintrc if pylintrc else PYLINTRC
|
|
307
380
|
print("\n" + "=" * 60)
|
|
@@ -313,10 +386,11 @@ def lintlocal(ctx: Context, pylintrc=None):
|
|
|
313
386
|
@task
|
|
314
387
|
def analyze(ctx: Context):
|
|
315
388
|
"""Run SonarQube analysis"""
|
|
316
|
-
|
|
317
|
-
|
|
389
|
+
if detect_project_type() == 'backend':
|
|
390
|
+
detect_and_activate_venv()
|
|
391
|
+
load_env(ctx)
|
|
318
392
|
try:
|
|
319
|
-
version =
|
|
393
|
+
version = read_version(ctx)
|
|
320
394
|
sonar_version = str(version).lstrip('v.')
|
|
321
395
|
except:
|
|
322
396
|
sonar_version = "0.0.0"
|
|
@@ -333,7 +407,11 @@ def analyze(ctx: Context):
|
|
|
333
407
|
|
|
334
408
|
@task
|
|
335
409
|
def buildlocal(ctx: Context):
|
|
336
|
-
"""Build Docker image
|
|
410
|
+
"""Build locally (Docker image for backend, npm build for frontend)"""
|
|
411
|
+
if detect_project_type() == 'frontend':
|
|
412
|
+
ctx.run('npm run build')
|
|
413
|
+
print('✓ Built frontend (dist/)')
|
|
414
|
+
return
|
|
337
415
|
current_branch = get_current_branch(ctx)
|
|
338
416
|
local_tag = f'myapp:{current_branch}-local'
|
|
339
417
|
ctx.run(f'podman build . --tag={local_tag}')
|
|
@@ -342,7 +420,11 @@ def buildlocal(ctx: Context):
|
|
|
342
420
|
|
|
343
421
|
@task
|
|
344
422
|
def testlocal(ctx: Context, settings='backend.test_settings'):
|
|
345
|
-
"""Run tests locally (
|
|
423
|
+
"""Run tests locally (Django or Angular/karma)"""
|
|
424
|
+
if detect_project_type() == 'frontend':
|
|
425
|
+
ctx.run('npx ng test --no-watch --code-coverage --browsers=ChromeHeadlessNoSandbox')
|
|
426
|
+
print("✓ Tests completed")
|
|
427
|
+
return
|
|
346
428
|
detect_and_activate_venv()
|
|
347
429
|
ctx.run(f'python -m coverage run manage.py test --settings={settings} --noinput -v 2')
|
|
348
430
|
ctx.run('python -m coverage report -m')
|
|
@@ -353,11 +435,12 @@ def testlocal(ctx: Context, settings='backend.test_settings'):
|
|
|
353
435
|
@task
|
|
354
436
|
def analyzelocal(ctx: Context):
|
|
355
437
|
"""Run SonarQube static analysis locally"""
|
|
356
|
-
|
|
357
|
-
|
|
438
|
+
if detect_project_type() == 'backend':
|
|
439
|
+
detect_and_activate_venv()
|
|
440
|
+
load_env(ctx)
|
|
358
441
|
print("Running SonarQube analysis...")
|
|
359
442
|
try:
|
|
360
|
-
version =
|
|
443
|
+
version = read_version(ctx)
|
|
361
444
|
sonar_version = str(version).lstrip('v.')
|
|
362
445
|
except:
|
|
363
446
|
sonar_version = "0.0.0"
|
|
@@ -376,20 +459,22 @@ def analyzelocal(ctx: Context):
|
|
|
376
459
|
|
|
377
460
|
@task
|
|
378
461
|
def pipelinelocal(ctx: Context, settings='backend.test_settings', pylintrc=None):
|
|
379
|
-
"""Local pipeline: lint → test → analyze
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
462
|
+
"""Local pipeline: (lint →) test → analyze"""
|
|
463
|
+
project_type = detect_project_type()
|
|
464
|
+
if project_type == 'backend':
|
|
465
|
+
detect_and_activate_venv()
|
|
466
|
+
print("=" * 60)
|
|
467
|
+
print("Step 1: Running lint...")
|
|
468
|
+
print("=" * 60)
|
|
469
|
+
lint(ctx, pylintrc=pylintrc)
|
|
385
470
|
|
|
386
471
|
print("\n" + "=" * 60)
|
|
387
|
-
print("Step 2: Running tests...")
|
|
472
|
+
print(f"Step {'2' if project_type == 'backend' else '1'}: Running tests...")
|
|
388
473
|
print("=" * 60)
|
|
389
474
|
testlocal(ctx, settings)
|
|
390
475
|
|
|
391
476
|
print("\n" + "=" * 60)
|
|
392
|
-
print("Step 3: Running SonarQube analysis...")
|
|
477
|
+
print(f"Step {'3' if project_type == 'backend' else '2'}: Running SonarQube analysis...")
|
|
393
478
|
print("=" * 60)
|
|
394
479
|
analyzelocal(ctx)
|
|
395
480
|
|
|
@@ -467,7 +552,7 @@ def changelog(ctx: Context, version: Version = None):
|
|
|
467
552
|
version_str = str(version)
|
|
468
553
|
else:
|
|
469
554
|
try:
|
|
470
|
-
version_str = str(
|
|
555
|
+
version_str = str(read_version(ctx))
|
|
471
556
|
except:
|
|
472
557
|
version_str = "Unreleased"
|
|
473
558
|
|
|
@@ -76,6 +76,10 @@ class Version:
|
|
|
76
76
|
else:
|
|
77
77
|
raise ValueError(f'Invalid version: {version}')
|
|
78
78
|
|
|
79
|
+
def bare_semver(self) -> str:
|
|
80
|
+
"""Return just major.minor.patch (e.g. '2.0.4') for package.json."""
|
|
81
|
+
return f'{self.major}.{self.minor}.{self.patch}'
|
|
82
|
+
|
|
79
83
|
def __str__(self):
|
|
80
84
|
version = f'v.{self.major}.{self.minor}.{self.patch}'
|
|
81
85
|
if self.release_candidate == 0:
|
|
@@ -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.25"
|
|
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
|
{itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/requires.txt
RENAMED
|
File without changes
|
{itw_python_builder-0.1.24 → itw_python_builder-0.1.25}/itw_python_builder.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|