itw-python-builder 0.1.25__tar.gz → 0.1.26__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 (16) hide show
  1. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/PKG-INFO +1 -1
  2. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder/tasks.py +84 -29
  3. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder.egg-info/PKG-INFO +1 -1
  4. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/pyproject.toml +1 -1
  5. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/LICENSE +0 -0
  6. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/README.md +0 -0
  7. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder/.pylintrc +0 -0
  8. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder/__init__.py +0 -0
  9. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder/cli.py +0 -0
  10. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder/version.py +0 -0
  11. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder.egg-info/SOURCES.txt +0 -0
  12. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder.egg-info/dependency_links.txt +0 -0
  13. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder.egg-info/entry_points.txt +0 -0
  14. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder.egg-info/requires.txt +0 -0
  15. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/itw_python_builder.egg-info/top_level.txt +0 -0
  16. {itw_python_builder-0.1.25 → itw_python_builder-0.1.26}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itw_python_builder
3
- Version: 0.1.25
3
+ Version: 0.1.26
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
@@ -88,7 +88,6 @@ def _parse_angular_env_file(path: str) -> dict:
88
88
 
89
89
 
90
90
  def load_env(ctx: Context = None):
91
- """Load env vars. Backend → .env in cwd. Frontend → src/environments/environment[.<branch>].ts."""
92
91
  if detect_project_type() == 'frontend':
93
92
  env_dir = os.path.join(os.getcwd(), 'src', 'environments')
94
93
  candidates = []
@@ -96,11 +95,10 @@ def load_env(ctx: Context = None):
96
95
  branch = get_current_branch(ctx)
97
96
  candidates.append(os.path.join(env_dir, f'environment.{branch}.ts'))
98
97
  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)
98
+ for path in candidates:
99
+ if os.path.exists(path):
100
+ for key, value in _parse_angular_env_file(path).items():
101
+ os.environ.setdefault(key, value)
104
102
  return
105
103
  from decouple import Config, RepositoryEnv
106
104
  env_path = os.path.join(os.getcwd(), '.env')
@@ -145,21 +143,15 @@ def get_latest_tag(ctx: Context) -> Version:
145
143
 
146
144
 
147
145
  def _update_package_json_version(version: Version) -> None:
148
- """Rewrite only the `"version": "..."` line in package.json, preserving formatting."""
146
+ """Update the top-level `version` in package.json (preserves key order)."""
147
+ import json
149
148
  pkg_path = os.path.join(os.getcwd(), 'package.json')
150
149
  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}')
150
+ data = json.load(fp)
151
+ data['version'] = version.bare_semver()
161
152
  with open(pkg_path, 'w', encoding='utf-8') as fp:
162
- fp.write(new_content)
153
+ json.dump(data, fp, indent=2)
154
+ fp.write('\n')
163
155
 
164
156
 
165
157
  def read_version(ctx: Context) -> Version:
@@ -200,17 +192,76 @@ def get_gitlab_username(ctx: Context) -> str:
200
192
 
201
193
  @task
202
194
  def login(ctx: Context, username=None):
203
- """Log in to the GitLab container registry."""
195
+ """Capture GitLab token. Backend also logs into the container registry."""
204
196
  if not username:
205
197
  username = get_gitlab_username(ctx)
206
- print(f'Logging in to gitreg.it-works.io:443 as {username}')
207
- token = getpass.getpass(f'Enter GitLab token (password) for {username}: ')
198
+ token = getpass.getpass(f'Enter GitLab token for {username}: ')
208
199
  if not token:
209
200
  raise RuntimeError('No token entered; aborting login.')
201
+ os.environ['GITLAB_TOKEN'] = token
202
+ os.environ['GITLAB_USERNAME'] = username
203
+ if detect_project_type() == 'backend':
204
+ print(f'Logging in to gitreg.it-works.io:443 as {username}')
205
+ ctx.run(
206
+ f'podman login -u {username} --password-stdin gitreg.it-works.io:443 --tls-verify=false',
207
+ in_stream=io.StringIO(token)
208
+ )
209
+ else:
210
+ print(f'[itw] GitLab token captured for {username} (will be used for package upload).')
211
+
212
+
213
+ def _read_package_name() -> str:
214
+ """Read 'name' field from package.json in cwd."""
215
+ import json
216
+ pkg_path = os.path.join(os.getcwd(), 'package.json')
217
+ with open(pkg_path, 'r', encoding='utf-8') as fp:
218
+ return json.load(fp)['name']
219
+
220
+
221
+ def _parse_gitlab_remote(ctx: Context) -> tuple:
222
+ """Return (host, project_path) parsed from `git remote get-url origin`."""
223
+ result = ctx.run('git remote get-url origin', hide=True)
224
+ url = result.stdout.strip()
225
+ if url.endswith('.git'):
226
+ url = url[:-4]
227
+ if url.startswith('git@'):
228
+ host, path = url.split('@', 1)[1].split(':', 1)
229
+ elif '://' in url:
230
+ host, path = url.split('://', 1)[1].split('/', 1)
231
+ else:
232
+ raise RuntimeError(f'Cannot parse GitLab remote URL: {url}')
233
+ return host, path
234
+
235
+
236
+ def build_frontend(ctx: Context, branch: str) -> None:
237
+ """Build the Angular app. Master branch → production config; otherwise staging."""
238
+ config = 'production' if branch == 'master' else 'staging'
239
+ ctx.run(f'npx ng build --configuration={config}')
240
+
241
+
242
+ def package_dist(ctx: Context) -> None:
243
+ """Tar the dist/ output directory into dist.tar.gz."""
244
+ if not os.path.isdir(os.path.join(os.getcwd(), 'dist')):
245
+ raise RuntimeError('No dist/ directory found after build — did `ng build` succeed?')
246
+ ctx.run('tar -czf dist.tar.gz -C dist .')
247
+
248
+
249
+ def upload_dist(ctx: Context, version) -> None:
250
+ """Upload dist.tar.gz to this project's GitLab Generic Package Registry under <version>/."""
251
+ from urllib.parse import quote
252
+ if not os.environ.get('GITLAB_TOKEN'):
253
+ raise RuntimeError('GITLAB_TOKEN not set; run `itw login` first.')
254
+ host, path = _parse_gitlab_remote(ctx)
255
+ encoded = quote(path, safe='')
256
+ url = f'https://{host}/api/v4/projects/{encoded}/packages/generic/frontend/{version}/dist.tar.gz'
257
+ print(f'[itw] Uploading dist.tar.gz → {url}')
210
258
  ctx.run(
211
- f'podman login -u {username} --password-stdin gitreg.it-works.io:443 --tls-verify=false',
212
- in_stream=io.StringIO(token)
259
+ 'curl --fail --silent --show-error '
260
+ '--header "PRIVATE-TOKEN: $GITLAB_TOKEN" '
261
+ '--upload-file dist.tar.gz '
262
+ f'"{url}"'
213
263
  )
264
+ print('[itw] Upload complete.')
214
265
 
215
266
 
216
267
  def commit_version(ctx: Context, version: Version) -> None:
@@ -255,6 +306,11 @@ def tag_build_push(ctx: Context, version: Version, skip_pipeline: bool = False)
255
306
  if project_type == 'backend':
256
307
  buildimage(ctx)
257
308
  pushimage(ctx)
309
+ else:
310
+ branch = get_current_branch(ctx)
311
+ build_frontend(ctx, branch)
312
+ package_dist(ctx)
313
+ upload_dist(ctx, version)
258
314
  changelog(ctx, version)
259
315
  push(ctx)
260
316
 
@@ -272,9 +328,8 @@ def taginit(ctx: Context) -> None:
272
328
  def incrementrc(ctx: Context, skip_pipeline=False, pylintrc=None) -> None:
273
329
  """Increment release candidate version and deploy"""
274
330
  check_branch(ctx)
331
+ login(ctx)
275
332
  project_type = detect_project_type()
276
- if project_type == 'backend':
277
- login(ctx)
278
333
  version = read_version(ctx)
279
334
  version.increment_release_candidate()
280
335
  if project_type == 'backend':
@@ -325,8 +380,7 @@ def incrementmajor(ctx: Context, skip_pipeline=False) -> None:
325
380
  def release(ctx: Context, skip_pipeline=False) -> None:
326
381
  """Promote release candidate to stable release and deploy"""
327
382
  _ = check_branch(ctx)
328
- if detect_project_type() == 'backend':
329
- login(ctx)
383
+ login(ctx)
330
384
  version = read_version(ctx)
331
385
  version.reset_release_candidate(release=True)
332
386
  tag_build_push(ctx, version, skip_pipeline)
@@ -407,9 +461,10 @@ def analyze(ctx: Context):
407
461
 
408
462
  @task
409
463
  def buildlocal(ctx: Context):
410
- """Build locally (Docker image for backend, npm build for frontend)"""
464
+ """Build locally (Docker image for backend, ng build for frontend)"""
411
465
  if detect_project_type() == 'frontend':
412
- ctx.run('npm run build')
466
+ branch = get_current_branch(ctx)
467
+ build_frontend(ctx, branch)
413
468
  print('✓ Built frontend (dist/)')
414
469
  return
415
470
  current_branch = get_current_branch(ctx)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itw_python_builder
3
- Version: 0.1.25
3
+ Version: 0.1.26
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.25"
7
+ version = "0.1.26"
8
8
  description = "Standardized Django deployment pipeline with Docker, testing, and SonarQube integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"