@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.
- package/assets/components.json +3 -3
- package/assets/core/distribution.json +5 -0
- package/assets/deployment/distribution.json +2 -0
- package/assets/generated/ASSEMBLY.json +48 -13
- package/assets/generated/core/core/instructions/lean-docs.instructions.md +33 -2
- package/assets/generated/core/core/instructions/lean-workflow.instructions.md +42 -0
- package/assets/generated/core/core/process/DEPLOYMENT_ENABLEMENT.md +108 -71
- package/assets/generated/core/core/process/LEAN_ENGINEERING_GUIDE.md +317 -0
- package/assets/generated/core/core/process/PROJECT_DOCUMENTATION_FRAMEWORK.md +324 -0
- package/assets/generated/core/core/skills/deploy/SKILL.md +164 -121
- package/assets/generated/core/core/skills/deployment-readiness/SKILL.md +223 -250
- package/assets/generated/core/core/skills/docs/SKILL.md +116 -0
- package/assets/generated/core/core/skills/next-step/SKILL.md +15 -10
- package/assets/generated/core/core/skills/tear-down/SKILL.md +101 -61
- package/assets/generated/core/core/templates/ADR_TEMPLATE.md +41 -0
- package/assets/generated/core/tools/docs_site/docs_site.py +382 -0
- package/assets/generated/deployment/src/lean_poc_deployment/entrypoint.py +5 -1
- package/assets/generated/deployment/src/lean_poc_deployment/local_cli.py +112 -0
- package/assets/generated/deployment/src/lean_poc_deployment/local_runtime.py +523 -0
- package/assets/generated/deployment/src/lean_poc_deployment/teardown.py +49 -35
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_cli.py +31 -12
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Scoped destructive tear-down for Kit-managed
|
|
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
|
-
|
|
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
|
|
54
|
-
raise DeploymentKitError("
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
89
|
-
raise DeploymentKitError("
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
104
|
-
raise DeploymentKitError(
|
|
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":
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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":
|
|
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
|
-
"
|
|
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
|
|
25
|
-
"Remote tear-down deletes persistent
|
|
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(
|
|
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
|
|
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=
|
|
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
|
|
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["
|
|
73
|
+
production = bool(spec["productionEnvironments"])
|
|
58
74
|
return {
|
|
59
|
-
"schemaVersion":
|
|
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 =
|
|
91
|
-
missing_production =
|
|
92
|
-
|
|
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")
|