itw-python-builder 0.1.31__tar.gz → 0.1.33__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 (20) hide show
  1. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/PKG-INFO +1 -1
  2. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/ssr_tasks.py +169 -25
  3. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder.egg-info/PKG-INFO +1 -1
  4. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/pyproject.toml +1 -1
  5. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/LICENSE +0 -0
  6. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/README.md +0 -0
  7. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/.pylintrc +0 -0
  8. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/__init__.py +0 -0
  9. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/cli.py +0 -0
  10. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/tasks.py +0 -0
  11. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/templates/server.sitemap.snippet.ts +0 -0
  12. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/templates/sitemap.routes.ts +0 -0
  13. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/utils.py +0 -0
  14. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder/version.py +0 -0
  15. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder.egg-info/SOURCES.txt +0 -0
  16. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder.egg-info/dependency_links.txt +0 -0
  17. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder.egg-info/entry_points.txt +0 -0
  18. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder.egg-info/requires.txt +0 -0
  19. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/itw_python_builder.egg-info/top_level.txt +0 -0
  20. {itw_python_builder-0.1.31 → itw_python_builder-0.1.33}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itw_python_builder
3
- Version: 0.1.31
3
+ Version: 0.1.33
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
@@ -43,6 +43,147 @@ def _angular_core_installed_version() -> str:
43
43
  return json.load(fp).get('version', '')
44
44
 
45
45
 
46
+ def _read_angular_json() -> dict:
47
+ """Read angular.json from cwd. Returns empty dict if missing/invalid."""
48
+ path = os.path.join(os.getcwd(), 'angular.json')
49
+ if not os.path.isfile(path):
50
+ return {}
51
+ try:
52
+ with open(path, 'r', encoding='utf-8') as fp:
53
+ return json.load(fp)
54
+ except (OSError, json.JSONDecodeError):
55
+ return {}
56
+
57
+
58
+ def _detect_angular_project(angular_json: dict) -> tuple:
59
+ """Return (project_name, project_data) for the project SSR was scaffolded into.
60
+
61
+ Priority: package.json `name` match → defaultProject → only project in repo.
62
+ Returns (None, None) if angular.json has no usable projects.
63
+ """
64
+ projects = angular_json.get('projects') or {}
65
+ if not projects:
66
+ return (None, None)
67
+ try:
68
+ pkg_name = _read_package_name()
69
+ if pkg_name in projects:
70
+ return (pkg_name, projects[pkg_name])
71
+ except Exception:
72
+ pass
73
+ default = angular_json.get('defaultProject')
74
+ if default and default in projects:
75
+ return (default, projects[default])
76
+ if len(projects) == 1:
77
+ name = next(iter(projects))
78
+ return (name, projects[name])
79
+ return (None, None)
80
+
81
+
82
+ def _maybe_add_ssr_scripts(ctx: Context) -> None:
83
+ """Add build:ssr, build:ssr:staging, build:ssr:dev npm scripts based on angular.json.
84
+
85
+ Reads angular.json to detect:
86
+ - The builder (legacy `:browser` + separate `server` target, or modern `:application`)
87
+ - The project name (for `ng run <name>:server:<config>` in the legacy form)
88
+ - Which configurations exist (only writes scripts for configs that exist in both
89
+ `build` and, for legacy, `server` targets)
90
+
91
+ Never overwrites a script that already exists in package.json. Prints a summary
92
+ of what was added vs preserved vs skipped (no matching config).
93
+ """
94
+ angular_json = _read_angular_json()
95
+ if not angular_json:
96
+ print('[itw] No angular.json found — skipping SSR script setup.')
97
+ return
98
+
99
+ project_name, project = _detect_angular_project(angular_json)
100
+ if not project:
101
+ print('[itw] Could not detect Angular project in angular.json — skipping SSR script setup.')
102
+ return
103
+
104
+ arch = project.get('architect') or project.get('targets') or {}
105
+ build = arch.get('build') or {}
106
+ server = arch.get('server') or {}
107
+ builder = build.get('builder', '')
108
+
109
+ is_legacy = builder == '@angular-devkit/build-angular:browser' and bool(server)
110
+ is_modern = builder == '@angular-devkit/build-angular:application'
111
+ if not (is_legacy or is_modern):
112
+ print(f'[itw] Unrecognized build builder {builder!r} — skipping SSR script setup.')
113
+ return
114
+
115
+ build_configs = set((build.get('configurations') or {}).keys())
116
+ if is_legacy:
117
+ server_configs = set((server.get('configurations') or {}).keys())
118
+ usable_configs = build_configs & server_configs
119
+ builder_label = 'legacy (browser + server targets)'
120
+ else:
121
+ usable_configs = build_configs
122
+ builder_label = 'modern (application builder)'
123
+
124
+ print(f"[itw] Detected {builder_label} for project '{project_name}'.")
125
+
126
+ def cmd_for(config: str) -> str:
127
+ if is_legacy:
128
+ return f'ng build --configuration={config} && ng run {project_name}:server:{config}'
129
+ return f'ng build --configuration={config}'
130
+
131
+ script_map = {
132
+ 'production': 'build:ssr',
133
+ 'staging': 'build:ssr:staging',
134
+ 'development': 'build:ssr:dev',
135
+ }
136
+
137
+ pkg_path = os.path.join(os.getcwd(), 'package.json')
138
+ with open(pkg_path, 'r', encoding='utf-8') as fp:
139
+ data = json.load(fp)
140
+ scripts = data.setdefault('scripts', {})
141
+
142
+ # The "ng add" schematic writes `build:ssr` as the implicit form
143
+ # `ng build && ng run <name>:server` (relies on Angular defaults). It works
144
+ # but it's not explicit. We normalize JUST that exact stub to the explicit
145
+ # form; any other existing value (custom prebuild steps, etc.) is preserved.
146
+ ng_add_stub = f'ng build && ng run {project_name}:server'
147
+
148
+ added, normalized, preserved, skipped = [], [], [], []
149
+ for config_name, script_name in script_map.items():
150
+ if config_name not in usable_configs:
151
+ skipped.append((script_name, config_name))
152
+ continue
153
+ existing = scripts.get(script_name)
154
+ is_stub = (
155
+ script_name == 'build:ssr'
156
+ and existing is not None
157
+ and ' '.join(existing.split()) == ng_add_stub
158
+ )
159
+ if existing is not None and not is_stub:
160
+ preserved.append(script_name)
161
+ continue
162
+ new_cmd = cmd_for(config_name)
163
+ scripts[script_name] = new_cmd
164
+ if is_stub:
165
+ normalized.append((script_name, new_cmd))
166
+ else:
167
+ added.append((script_name, new_cmd))
168
+
169
+ if added or normalized:
170
+ with open(pkg_path, 'w', encoding='utf-8') as fp:
171
+ json.dump(data, fp, indent=2)
172
+ fp.write('\n')
173
+
174
+ print('[itw] SSR npm scripts:')
175
+ for name, cmd in added:
176
+ print(f' + added: {name} → {cmd}')
177
+ for name, cmd in normalized:
178
+ print(f' ~ normalized: {name} → {cmd} (was ng-add\'s implicit form)')
179
+ for name in preserved:
180
+ print(f' = preserved: {name} (already in package.json, not overwritten)')
181
+ for name, cfg in skipped:
182
+ print(f" - skipped: {name} (no '{cfg}' config in angular.json)")
183
+ if not (added or normalized or preserved):
184
+ print(' (nothing added)')
185
+
186
+
46
187
  def _pin_ssr_deps_in_package_json(target_version: str) -> list:
47
188
  """Pin @angular/ssr and @angular/platform-server in package.json to exact target_version.
48
189
 
@@ -118,6 +259,24 @@ def _run_ng_add_ssr(ctx: Context) -> None:
118
259
  print(f'[itw] Running `ng add {package_spec}` (Angular {major}, this may take a minute)...')
119
260
  ctx.run(f'npx ng add {package_spec} {flags}')
120
261
 
262
+ # `ng add` typically rewrites package.json with a caret prefix (e.g. "^18.2.14"),
263
+ # and its internal `npm install` then resolves that range to the LATEST patch
264
+ # (often newer than the installed @angular/core), reintroducing the strict-peer
265
+ # conflict and pulling in @angular/ssr versions that reference symbols not in
266
+ # the installed @angular/platform-browser. Force-pin again post-ng-add and
267
+ # reinstall with --save-exact to lock the exact versions across package.json,
268
+ # package-lock.json, and node_modules.
269
+ if installed_core:
270
+ re_pinned = _pin_ssr_deps_in_package_json(installed_core)
271
+ if re_pinned:
272
+ print(f'[itw] Re-pinned {", ".join(re_pinned)} → {installed_core} (ng add introduced a caret range).')
273
+ print(f'[itw] Locking installed versions to {installed_core} with --save-exact...')
274
+ ctx.run(
275
+ f'npm install --save-exact '
276
+ f'@angular/ssr@{installed_core} '
277
+ f'@angular/platform-server@{installed_core}'
278
+ )
279
+
121
280
 
122
281
  def _install_ngx_seo_helper(ctx: Context) -> None:
123
282
  print('[itw] Installing ngx-seo-helper...')
@@ -182,29 +341,12 @@ def _print_next_steps(project_name: str) -> None:
182
341
  print()
183
342
  print(' 1. Add `NgxSeoModule` to your AppModule imports.')
184
343
  print(' 2. Fill in src/sitemap.routes.ts with your route tree.')
185
- print(' 3. Add `staging` (and optionally `development`) configurations to')
186
- print(' angular.json under projects.' + project_name + '.architect.build.configurations.')
187
- print(' ng add @angular/ssr only creates the `production` configuration.')
188
- print()
189
- print(' 4. Add these two npm scripts to package.json — the deploy pipeline')
190
- print(' calls them by name:')
191
- print()
192
- print(' "build:ssr": "<build command for production>"')
193
- print(' "build:ssr:staging": "<build command for staging>"')
194
- print()
195
- print(' The exact command depends on which builder angular.json uses:')
196
- print()
197
- print(' • Modern application builder (default for fresh ng add @angular/ssr):')
198
- print(' "build:ssr": "ng build --configuration=production"')
199
- print(' "build:ssr:staging": "ng build --configuration=staging"')
200
- print()
201
- print(' • Legacy browser builder (older projects with a separate server target):')
202
- print(f' "build:ssr": "ng build --configuration=production && ng run {project_name}:server:production"')
203
- print(f' "build:ssr:staging": "ng build --configuration=staging && ng run {project_name}:server:staging"')
204
- print()
205
- print(' 5. Verify locally: npm run build:ssr && npm run serve:ssr')
206
- print(' 6. Commit the new files.')
207
- print(' 7. Deploy with: itw incrementrc --ssr (or incrementpatch/release --ssr)')
344
+ print(' 3. If `build:ssr:staging` was skipped above, add a `staging` configuration')
345
+ print(f' to angular.json (and the `server` target, if legacy builder), then re-run')
346
+ print(' `itw ssr-init --force` to add the missing scripts.')
347
+ print(' 4. Verify locally: npm run build:ssr && npm run serve:ssr')
348
+ print(' 5. Commit the new files.')
349
+ print(' 6. Deploy with: itw incrementrc --ssr (or incrementpatch/release --ssr)')
208
350
  print('=' * 70)
209
351
 
210
352
 
@@ -212,8 +354,9 @@ def _print_next_steps(project_name: str) -> None:
212
354
  def ssr_init(ctx: Context, force: bool = False) -> None:
213
355
  """One-time SSR scaffold: ng add @angular/ssr, ngx-seo-helper, sitemap stub, server.ts patch.
214
356
 
215
- Does NOT write package.json scripts those depend on which builder Angular
216
- chose. The post-run message tells you the two scripts the pipeline expects.
357
+ Auto-adds the build:ssr / build:ssr:staging / build:ssr:dev npm scripts based
358
+ on angular.json (detects builder type and available configs). Existing scripts
359
+ are never overwritten.
217
360
  """
218
361
  if detect_project_type() != 'frontend':
219
362
  raise RuntimeError('ssr-init only runs on frontend (Angular) projects.')
@@ -230,6 +373,7 @@ def ssr_init(ctx: Context, force: bool = False) -> None:
230
373
  if not is_ssr_project():
231
374
  _run_ng_add_ssr(ctx)
232
375
 
376
+ _maybe_add_ssr_scripts(ctx)
233
377
  _install_ngx_seo_helper(ctx)
234
378
  _write_sitemap_template()
235
379
  _patch_server_ts()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itw_python_builder
3
- Version: 0.1.31
3
+ Version: 0.1.33
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.31"
7
+ version = "0.1.33"
8
8
  description = "Standardized Django deployment pipeline with Docker, testing, and SonarQube integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"