@mriekki/lean 0.4.4 → 0.4.5

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. package/assets/components.json +3 -3
  2. package/assets/core/distribution.json +5 -0
  3. package/assets/deployment/distribution.json +2 -0
  4. package/assets/generated/ASSEMBLY.json +48 -13
  5. package/assets/generated/core/core/instructions/lean-docs.instructions.md +33 -2
  6. package/assets/generated/core/core/instructions/lean-workflow.instructions.md +42 -0
  7. package/assets/generated/core/core/process/DEPLOYMENT_ENABLEMENT.md +108 -71
  8. package/assets/generated/core/core/process/LEAN_ENGINEERING_GUIDE.md +317 -0
  9. package/assets/generated/core/core/process/PROJECT_DOCUMENTATION_FRAMEWORK.md +324 -0
  10. package/assets/generated/core/core/skills/deploy/SKILL.md +164 -121
  11. package/assets/generated/core/core/skills/deployment-readiness/SKILL.md +223 -250
  12. package/assets/generated/core/core/skills/docs/SKILL.md +116 -0
  13. package/assets/generated/core/core/skills/next-step/SKILL.md +15 -10
  14. package/assets/generated/core/core/skills/tear-down/SKILL.md +101 -61
  15. package/assets/generated/core/core/templates/ADR_TEMPLATE.md +41 -0
  16. package/assets/generated/core/tools/docs_site/docs_site.py +382 -0
  17. package/assets/generated/deployment/src/lean_poc_deployment/entrypoint.py +5 -1
  18. package/assets/generated/deployment/src/lean_poc_deployment/local_cli.py +112 -0
  19. package/assets/generated/deployment/src/lean_poc_deployment/local_runtime.py +523 -0
  20. package/assets/generated/deployment/src/lean_poc_deployment/teardown.py +49 -35
  21. package/assets/generated/deployment/src/lean_poc_deployment/teardown_cli.py +31 -12
  22. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- """Scoped destructive tear-down for Kit-managed POC environments and local Docker state."""
1
+ """Scoped destructive tear-down for Kit-managed named environments and local Docker state."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -17,7 +17,7 @@ from .teardown_remote import REMOTE_SCRIPT
17
17
 
18
18
  TEAR_DOWN_WRITE_PHRASE = "[POC TEAR DOWN]"
19
19
  PRODUCTION_DATA_DELETE_PHRASE = "[PRODUCTION DATA DELETE]"
20
- SUPPORTED_SCOPES = ("local", "staging", "production", "all")
20
+ RESERVED_SCOPES = ("local", "all")
21
21
 
22
22
  _SLUG_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$")
23
23
  _REPO_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
@@ -50,8 +50,8 @@ def parse_environment_targets(items: Iterable[str]) -> dict[str, str]:
50
50
  raise DeploymentKitError("environment target bindings must use environment=target form.")
51
51
  environment, target = item.split("=", 1)
52
52
  environment = _slug(environment, "environment")
53
- if environment not in {"staging", "production"}:
54
- raise DeploymentKitError("tear-down supports only staging and production environment bindings.")
53
+ if environment in RESERVED_SCOPES:
54
+ raise DeploymentKitError(f"{environment!r} is reserved and cannot be a remote environment name.")
55
55
  if not target or _TARGET_RE.fullmatch(target) is None:
56
56
  raise DeploymentKitError("target profile names contain unsupported characters.")
57
57
  if environment in result:
@@ -60,14 +60,14 @@ def parse_environment_targets(items: Iterable[str]) -> dict[str, str]:
60
60
  return dict(sorted(result.items()))
61
61
 
62
62
 
63
- def _remote_environments(scope: str) -> tuple[str, ...]:
64
- if scope == "staging":
65
- return ("staging",)
66
- if scope == "production":
67
- return ("production",)
68
- if scope == "all":
69
- return ("staging", "production")
70
- return ()
63
+ def _normalize_production_environments(items: Iterable[str]) -> set[str]:
64
+ result = {"production"}
65
+ for item in items:
66
+ environment = _slug(item, "productionEnvironment")
67
+ if environment in RESERVED_SCOPES:
68
+ raise DeploymentKitError("local/all cannot be classified as Production environments.")
69
+ result.add(environment)
70
+ return result
71
71
 
72
72
 
73
73
  def normalize_teardown_spec(
@@ -77,39 +77,45 @@ def normalize_teardown_spec(
77
77
  scope: str,
78
78
  environment_targets: dict[str, str] | Iterable[tuple[str, str]],
79
79
  local_package: str | None,
80
+ production_environments: Iterable[str] = (),
80
81
  ) -> dict[str, Any]:
81
- if scope not in SUPPORTED_SCOPES:
82
- raise DeploymentKitError("scope must be one of: local, staging, production, all.")
83
-
82
+ scope = _slug(scope, "scope")
84
83
  pairs = environment_targets.items() if isinstance(environment_targets, dict) else environment_targets
85
84
  normalized_targets: dict[str, str] = {}
86
85
  for environment, target in pairs:
87
86
  environment = _slug(environment, "environment")
88
- if environment not in {"staging", "production"}:
89
- raise DeploymentKitError("tear-down supports only staging and production environment bindings.")
87
+ if environment in RESERVED_SCOPES:
88
+ raise DeploymentKitError(f"{environment!r} is reserved and cannot be a remote environment name.")
90
89
  if not isinstance(target, str) or _TARGET_RE.fullmatch(target) is None:
91
90
  raise DeploymentKitError("target profile names contain unsupported characters.")
92
91
  if environment in normalized_targets:
93
92
  raise DeploymentKitError(f"Duplicate environment target binding: {environment}")
94
93
  normalized_targets[environment] = target
95
94
 
96
- selected_targets: dict[str, str] = {}
97
- for environment in _remote_environments(scope):
98
- if environment not in normalized_targets:
99
- raise DeploymentKitError(f"scope {scope!r} requires an explicit {environment}=target binding.")
100
- selected_targets[environment] = normalized_targets[environment]
95
+ if scope == "local":
96
+ selected_targets: dict[str, str] = {}
97
+ elif scope == "all":
98
+ selected_targets = dict(sorted(normalized_targets.items()))
99
+ else:
100
+ if scope not in normalized_targets:
101
+ raise DeploymentKitError(f"scope {scope!r} requires an explicit {scope}=target binding.")
102
+ selected_targets = {scope: normalized_targets[scope]}
101
103
 
102
104
  package = _package(local_package) if local_package is not None else None
103
- if scope in {"local", "all"} and package is None:
104
- raise DeploymentKitError(f"scope {scope!r} requires --local-package for exact local Docker ownership.")
105
+ if scope == "local" and package is None:
106
+ raise DeploymentKitError("scope 'local' requires --local-package for exact local Docker ownership.")
107
+
108
+ production_set = _normalize_production_environments(production_environments)
109
+ selected_production = sorted(set(selected_targets).intersection(production_set))
105
110
 
106
111
  return {
107
- "schemaVersion": 1,
112
+ "schemaVersion": 2,
108
113
  "ownerRepo": _owner_repo(owner_repo),
109
114
  "appSlug": _slug(app_slug, "appSlug"),
110
115
  "scope": scope,
111
116
  "environmentTargets": selected_targets,
112
117
  "localPackage": package,
118
+ "productionEnvironments": selected_production,
113
119
  }
114
120
 
115
121
 
@@ -197,25 +203,30 @@ def tear_down_workstation_package(package: str) -> dict[str, Any]:
197
203
 
198
204
  listed = _run_local([docker, "ps", "-a", "--format", "{{.ID}}"])
199
205
  if listed.returncode != 0:
200
- raise DeploymentKitError("Local Docker container inventory failed: " + (listed.stderr or listed.stdout).strip())
206
+ raise DeploymentKitError(
207
+ "Local Docker container inventory failed: " + (listed.stderr or listed.stdout).strip()
208
+ )
201
209
 
202
210
  containers: list[str] = []
203
211
  for container_id in [line.strip() for line in listed.stdout.splitlines() if line.strip()]:
204
212
  inspected = _run_local([docker, "inspect", "--format", "{{.Config.Image}}", container_id])
205
213
  if inspected.returncode != 0:
206
214
  raise DeploymentKitError(f"Local Docker inspect failed for container {container_id}.")
207
- image = inspected.stdout.strip()
208
- if _image_matches_package(image, package):
215
+ if _image_matches_package(inspected.stdout.strip(), package):
209
216
  containers.append(container_id)
210
217
 
211
218
  if containers:
212
219
  removed = _run_local([docker, "rm", "-f", "-v", *containers])
213
220
  if removed.returncode != 0:
214
- raise DeploymentKitError("Local POC container removal failed: " + (removed.stderr or removed.stdout).strip())
221
+ raise DeploymentKitError(
222
+ "Local POC container removal failed: " + (removed.stderr or removed.stdout).strip()
223
+ )
215
224
 
216
225
  images_result = _run_local([docker, "images", "--format", "{{.Repository}}:{{.Tag}}"])
217
226
  if images_result.returncode != 0:
218
- raise DeploymentKitError("Local Docker image inventory failed: " + (images_result.stderr or images_result.stdout).strip())
227
+ raise DeploymentKitError(
228
+ "Local Docker image inventory failed: " + (images_result.stderr or images_result.stdout).strip()
229
+ )
219
230
  image_refs = sorted(
220
231
  {
221
232
  line.strip()
@@ -226,8 +237,9 @@ def tear_down_workstation_package(package: str) -> dict[str, Any]:
226
237
  if image_refs:
227
238
  removed = _run_local([docker, "image", "rm", "-f", *image_refs])
228
239
  if removed.returncode != 0:
229
- raise DeploymentKitError("Local POC image removal failed: " + (removed.stderr or removed.stdout).strip())
230
-
240
+ raise DeploymentKitError(
241
+ "Local POC image removal failed: " + (removed.stderr or removed.stdout).strip()
242
+ )
231
243
  return {
232
244
  "state": "removed" if containers or image_refs else "absent",
233
245
  "mutated": bool(containers or image_refs),
@@ -243,6 +255,7 @@ def tear_down_poc(spec: dict[str, Any]) -> dict[str, Any]:
243
255
  scope=spec.get("scope"),
244
256
  environment_targets=(spec.get("environmentTargets") or {}).items(),
245
257
  local_package=spec.get("localPackage"),
258
+ production_environments=spec.get("productionEnvironments") or (),
246
259
  )
247
260
 
248
261
  environments: list[dict[str, Any]] = []
@@ -266,18 +279,19 @@ def tear_down_poc(spec: dict[str, Any]) -> dict[str, Any]:
266
279
  )
267
280
 
268
281
  workstation: dict[str, Any] = {"state": "not-selected", "mutated": False}
269
- if normalized["scope"] in {"local", "all"}:
282
+ if normalized["scope"] in {"local", "all"} and normalized["localPackage"] is not None:
270
283
  workstation = tear_down_workstation_package(normalized["localPackage"])
271
284
  mutated = mutated or workstation["mutated"]
272
285
 
273
286
  return {
274
- "schemaVersion": 1,
287
+ "schemaVersion": 2,
275
288
  "ownerRepo": normalized["ownerRepo"],
276
289
  "appSlug": normalized["appSlug"],
277
290
  "scope": normalized["scope"],
278
291
  "environments": environments,
279
292
  "workstation": workstation,
280
- "productionDataIncluded": normalized["scope"] in {"production", "all"},
293
+ "productionEnvironments": normalized["productionEnvironments"],
294
+ "productionDataIncluded": bool(normalized["productionEnvironments"]),
281
295
  "mutated": mutated,
282
296
  }
283
297
 
@@ -8,7 +8,6 @@ import sys
8
8
  from .errors import DeploymentKitError
9
9
  from .teardown import (
10
10
  PRODUCTION_DATA_DELETE_PHRASE,
11
- SUPPORTED_SCOPES,
12
11
  TEAR_DOWN_WRITE_PHRASE,
13
12
  normalize_teardown_spec,
14
13
  parse_environment_targets,
@@ -21,11 +20,15 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
21
20
  parser = argparse.ArgumentParser(
22
21
  prog="lean-poc-deploy tear-down",
23
22
  description=(
24
- "Remove the selected Kit-managed POC environment(s) and/or exact-package local Docker state. "
25
- "Remote tear-down deletes persistent application data for the selected environment."
23
+ "Remove one named Kit-managed environment, all explicitly bound environments, "
24
+ "and/or exact-package local Docker state. Remote tear-down deletes persistent "
25
+ "application data for selected environments."
26
26
  ),
27
27
  )
28
- parser.add_argument("scope", choices=SUPPORTED_SCOPES)
28
+ parser.add_argument(
29
+ "scope",
30
+ help="local, all, or one exact named environment such as demo, staging, qa, or production",
31
+ )
29
32
  parser.add_argument("--owner-repo", required=True)
30
33
  parser.add_argument("--app-slug", required=True)
31
34
  parser.add_argument(
@@ -33,11 +36,24 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
33
36
  action="append",
34
37
  default=[],
35
38
  metavar="ENV=TARGET",
36
- help="Explicit staging/production environment-to-target binding; repeat as needed by the selected scope.",
39
+ help="Explicit named environment-to-target binding; repeat as needed.",
40
+ )
41
+ parser.add_argument(
42
+ "--production-environment",
43
+ action="append",
44
+ default=[],
45
+ metavar="ENV",
46
+ help=(
47
+ "Classify an additional named environment as Production for the stronger data-deletion gate. "
48
+ "The literal 'production' environment is always Production."
49
+ ),
37
50
  )
38
51
  parser.add_argument(
39
52
  "--local-package",
40
- help="Exact local Docker package identity, e.g. ghcr.io/owner/package; required for local and all scopes.",
53
+ help=(
54
+ "Exact local Docker package identity, e.g. ghcr.io/owner/package. "
55
+ "Required for local; optional for all."
56
+ ),
41
57
  )
42
58
  parser.add_argument(
43
59
  "--authorization",
@@ -46,7 +62,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
46
62
  parser.add_argument(
47
63
  "--production-data-authorization",
48
64
  help=(
49
- "Additional phrase required for production/all because persistent Production data is deleted: "
65
+ "Additional phrase required whenever selected environments include Production data: "
50
66
  f"{PRODUCTION_DATA_DELETE_PHRASE}"
51
67
  ),
52
68
  )
@@ -54,14 +70,15 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
54
70
 
55
71
 
56
72
  def _preview(spec: dict[str, object]) -> dict[str, object]:
57
- production = spec["scope"] in {"production", "all"}
73
+ production = bool(spec["productionEnvironments"])
58
74
  return {
59
- "schemaVersion": 1,
75
+ "schemaVersion": 2,
60
76
  "ownerRepo": spec["ownerRepo"],
61
77
  "appSlug": spec["appSlug"],
62
78
  "scope": spec["scope"],
63
79
  "environmentTargets": spec["environmentTargets"],
64
80
  "localPackage": spec["localPackage"],
81
+ "productionEnvironments": spec["productionEnvironments"],
65
82
  "destructive": True,
66
83
  "deletesPersistentData": bool(spec["environmentTargets"]),
67
84
  "deletesProductionData": production,
@@ -83,13 +100,15 @@ def main(argv: list[str] | None = None) -> int:
83
100
  scope=args.scope,
84
101
  environment_targets=bindings,
85
102
  local_package=args.local_package,
103
+ production_environments=args.production_environment,
86
104
  )
87
105
  preview = _preview(spec)
88
106
 
89
107
  missing_general = args.authorization != TEAR_DOWN_WRITE_PHRASE
90
- needs_production = args.scope in {"production", "all"}
91
- missing_production = needs_production and (
92
- args.production_data_authorization != PRODUCTION_DATA_DELETE_PHRASE
108
+ needs_production = bool(spec["productionEnvironments"])
109
+ missing_production = (
110
+ needs_production
111
+ and args.production_data_authorization != PRODUCTION_DATA_DELETE_PHRASE
93
112
  )
94
113
  if missing_general or missing_production:
95
114
  print("POC TEAR-DOWN PREVIEW")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mriekki/lean",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Public consumer distribution and updater for Lean engineering assets.",
5
5
  "type": "module",
6
6
  "bin": {