@mriekki/project-forge 0.0.0-stage → 2.0.0
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/README.md +134 -2
- package/assets/components.json +33 -0
- package/assets/core/distribution.json +66 -0
- package/assets/deployment/distribution.json +54 -0
- package/assets/generated/ASSEMBLY.json +557 -0
- package/assets/generated/core/core/agents/coach.agent.md +69 -0
- package/assets/generated/core/core/agents/reviewer.agent.md +85 -0
- package/assets/generated/core/core/instructions/project-forge-docs.instructions.md +45 -0
- package/assets/generated/core/core/instructions/project-forge-workflow.instructions.md +79 -0
- package/assets/generated/core/core/learning/CODING_DRILLS.md +35 -0
- package/assets/generated/core/core/learning/DIAGRAM_GUIDE.md +59 -0
- package/assets/generated/core/core/learning/ENGINEERING_LEARNING_WORKBOOK.md +108 -0
- package/assets/generated/core/core/learning/LEARNING_MAP.md +108 -0
- package/assets/generated/core/core/learning/LEARNING_PROGRESS.md +39 -0
- package/assets/generated/core/core/learning/RESOURCE_CATALOG.md +25 -0
- package/assets/generated/core/core/learning/SQL_DRILLS.md +90 -0
- package/assets/generated/core/core/learning/SYSTEM_DESIGN_DRILLS.md +301 -0
- package/assets/generated/core/core/process/COACHING_GUIDE.md +92 -0
- package/assets/generated/core/core/process/DATABASE_LEARNING_WORKBENCH.md +397 -0
- package/assets/generated/core/core/process/DEPLOYMENT_ENABLEMENT.md +166 -0
- package/assets/generated/core/core/process/GUIDED_NEXT_STEPS.md +48 -0
- package/assets/generated/core/core/process/HUMAN_GATES.md +83 -0
- package/assets/generated/core/core/process/INTERACTIVE_SESSION_GUIDE.md +64 -0
- package/assets/generated/core/core/process/PROJECT_DOCUMENTATION_FRAMEWORK.md +326 -0
- package/assets/generated/core/core/process/PROJECT_FORGE_ENGINEERING_GUIDE.md +308 -0
- package/assets/generated/core/core/process/PR_WORKFLOW.md +55 -0
- package/assets/generated/core/core/process/SETUP_ACCEPTANCE_CHECKLIST.md +72 -0
- package/assets/generated/core/core/process/SYSTEM_DESIGN_LEARNING_WORKBENCH.md +520 -0
- package/assets/generated/core/core/process/WORK_ITEM_MODEL.md +56 -0
- package/assets/generated/core/core/skills/cleanup-branches/SKILL.md +190 -0
- package/assets/generated/core/core/skills/coach/SKILL.md +174 -0
- package/assets/generated/core/core/skills/code-walkthrough/SKILL.md +197 -0
- package/assets/generated/core/core/skills/core-sync/SKILL.md +178 -0
- package/assets/generated/core/core/skills/db-lab/SKILL.md +245 -0
- package/assets/generated/core/core/skills/define-feature/SKILL.md +144 -0
- package/assets/generated/core/core/skills/deliver/SKILL.md +397 -0
- package/assets/generated/core/core/skills/deploy/SKILL.md +188 -0
- package/assets/generated/core/core/skills/deployment-readiness/SKILL.md +234 -0
- package/assets/generated/core/core/skills/developer-env-mode/SKILL.md +231 -0
- package/assets/generated/core/core/skills/docs/SKILL.md +116 -0
- package/assets/generated/core/core/skills/grill-me/SKILL.md +77 -0
- package/assets/generated/core/core/skills/grill-with-docs/SKILL.md +90 -0
- package/assets/generated/core/core/skills/learning-review/SKILL.md +90 -0
- package/assets/generated/core/core/skills/next-step/SKILL.md +221 -0
- package/assets/generated/core/core/skills/progress/SKILL.md +207 -0
- package/assets/generated/core/core/skills/project-forge-update/SKILL.md +228 -0
- package/assets/generated/core/core/skills/pull-latest/SKILL.md +166 -0
- package/assets/generated/core/core/skills/refine-requirements/SKILL.md +207 -0
- package/assets/generated/core/core/skills/setup/SKILL.md +362 -0
- package/assets/generated/core/core/skills/start-work/SKILL.md +190 -0
- package/assets/generated/core/core/skills/system-lab/SKILL.md +333 -0
- package/assets/generated/core/core/skills/tear-down/SKILL.md +140 -0
- package/assets/generated/core/core/skills/upstream-fix/SKILL.md +221 -0
- package/assets/generated/core/core/skills/wait-what/SKILL.md +86 -0
- package/assets/generated/core/core/templates/ADR_TEMPLATE.md +41 -0
- package/assets/generated/core/core/templates/FEATURE_BRIEF_TEMPLATE.md +27 -0
- package/assets/generated/core/core/templates/PRODUCT_BRIEF_TEMPLATE.md +27 -0
- package/assets/generated/core/core/templates/PULL_REQUEST_TEMPLATE.md +37 -0
- package/assets/generated/core/core/templates/REQUIREMENTS_TEMPLATE.md +17 -0
- package/assets/generated/core/profiles/greenfield/project-forge-workspace.json +12 -0
- package/assets/generated/core/tools/db_lab/sqlite_workbench.py +463 -0
- package/assets/generated/core/tools/docs_site/docs_site.py +512 -0
- package/assets/generated/core/tools/package/check_baseline.py +102 -0
- package/assets/generated/core/tools/package/check_upstream.py +171 -0
- package/assets/generated/core/tools/system_lab/system_workbench.py +243 -0
- package/assets/generated/deployment/distribution/project-forge-deploy.py +8 -0
- package/assets/generated/deployment/docs/DEVELOPER_ENVIRONMENTS.md +335 -0
- package/assets/generated/deployment/docs/ENVIRONMENT_PROOF.md +95 -0
- package/assets/generated/deployment/docs/WORKSPACE_INSTANCES.md +154 -0
- package/assets/generated/deployment/docs/images/developer-environment-journey.svg +56 -0
- package/assets/generated/deployment/docs/images/personal-environment-architecture.svg +54 -0
- package/assets/generated/deployment/docs/images/poc-environment-model.svg +61 -0
- package/assets/generated/deployment/src/project_forge_deployment/__init__.py +3 -0
- package/assets/generated/deployment/src/project_forge_deployment/__main__.py +3 -0
- package/assets/generated/deployment/src/project_forge_deployment/app_cli.py +163 -0
- package/assets/generated/deployment/src/project_forge_deployment/app_runtime.py +455 -0
- package/assets/generated/deployment/src/project_forge_deployment/app_runtime_remote.py +416 -0
- package/assets/generated/deployment/src/project_forge_deployment/app_runtime_workstation.py +136 -0
- package/assets/generated/deployment/src/project_forge_deployment/box_platform.py +208 -0
- package/assets/generated/deployment/src/project_forge_deployment/box_platform_assets.py +100 -0
- package/assets/generated/deployment/src/project_forge_deployment/box_platform_remote.py +271 -0
- package/assets/generated/deployment/src/project_forge_deployment/cli.py +437 -0
- package/assets/generated/deployment/src/project_forge_deployment/doctor.py +121 -0
- package/assets/generated/deployment/src/project_forge_deployment/entrypoint.py +37 -0
- package/assets/generated/deployment/src/project_forge_deployment/environment_cli.py +271 -0
- package/assets/generated/deployment/src/project_forge_deployment/environment_probe.py +228 -0
- package/assets/generated/deployment/src/project_forge_deployment/errors.py +5 -0
- package/assets/generated/deployment/src/project_forge_deployment/home_lab.py +328 -0
- package/assets/generated/deployment/src/project_forge_deployment/image_transfer.py +105 -0
- package/assets/generated/deployment/src/project_forge_deployment/keys.py +171 -0
- package/assets/generated/deployment/src/project_forge_deployment/local_cli.py +189 -0
- package/assets/generated/deployment/src/project_forge_deployment/local_runtime.py +526 -0
- package/assets/generated/deployment/src/project_forge_deployment/local_spec.py +82 -0
- package/assets/generated/deployment/src/project_forge_deployment/local_sqlite.py +205 -0
- package/assets/generated/deployment/src/project_forge_deployment/portable_profile.py +181 -0
- package/assets/generated/deployment/src/project_forge_deployment/profile_migration.py +70 -0
- package/assets/generated/deployment/src/project_forge_deployment/profile_repository.py +348 -0
- package/assets/generated/deployment/src/project_forge_deployment/profiles.py +204 -0
- package/assets/generated/deployment/src/project_forge_deployment/registry_access.py +114 -0
- package/assets/generated/deployment/src/project_forge_deployment/registry_cli.py +51 -0
- package/assets/generated/deployment/src/project_forge_deployment/route_registry.py +367 -0
- package/assets/generated/deployment/src/project_forge_deployment/route_registry_remote.py +443 -0
- package/assets/generated/deployment/src/project_forge_deployment/security.py +290 -0
- package/assets/generated/deployment/src/project_forge_deployment/sqlite_adapter.py +335 -0
- package/assets/generated/deployment/src/project_forge_deployment/sqlite_adapter_remote.py +340 -0
- package/assets/generated/deployment/src/project_forge_deployment/sqlite_cli.py +86 -0
- package/assets/generated/deployment/src/project_forge_deployment/sqlite_import_recovery.py +437 -0
- package/assets/generated/deployment/src/project_forge_deployment/ssh.py +119 -0
- package/assets/generated/deployment/src/project_forge_deployment/teardown.py +300 -0
- package/assets/generated/deployment/src/project_forge_deployment/teardown_cli.py +139 -0
- package/assets/generated/deployment/src/project_forge_deployment/teardown_remote.py +100 -0
- package/assets/generated/deployment/src/project_forge_deployment/trust.py +278 -0
- package/bin/project-forge.mjs +9 -0
- package/package.json +31 -4
- package/src/cli.mjs +150 -0
- package/src/engine.mjs +243 -0
- package/src/package-data.mjs +18 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Personal Project Forge environment architecture</title>
|
|
3
|
+
<desc id="desc">A developer has a private portable profile, separate workstation-local keys and trust on each computer, and remote targets reached over SSH without storing developer GitHub credentials on the target.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
6
|
+
<stop offset="0" stop-color="#0b1220"/>
|
|
7
|
+
<stop offset="1" stop-color="#172033"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
10
|
+
<feDropShadow dx="0" dy="8" stdDeviation="9" flood-opacity="0.3"/>
|
|
11
|
+
</filter>
|
|
12
|
+
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
|
13
|
+
<path d="M0,0 L10,5 L0,10 Z" fill="#8ea0bd"/>
|
|
14
|
+
</marker>
|
|
15
|
+
</defs>
|
|
16
|
+
<rect width="1200" height="700" rx="24" fill="url(#bg)"/>
|
|
17
|
+
<text x="70" y="70" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="30" font-weight="700" fill="#f7f9fc">Personal Environment Architecture</text>
|
|
18
|
+
<text x="70" y="102" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="17" fill="#aebbd0">Portable definitions sync. Private machine identity stays on each computer.</text>
|
|
19
|
+
|
|
20
|
+
<g filter="url(#shadow)" font-family="Inter,Segoe UI,Arial,sans-serif">
|
|
21
|
+
<rect x="365" y="135" width="470" height="130" rx="20" fill="#25294a" stroke="#7277c9" stroke-width="2"/>
|
|
22
|
+
<text x="600" y="176" text-anchor="middle" font-size="15" font-weight="700" fill="#aaaef1">PRIVATE PERSONAL PROFILE</text>
|
|
23
|
+
<text x="600" y="208" text-anchor="middle" font-size="23" font-weight="700" fill="#ffffff">Portable target definitions</text>
|
|
24
|
+
<text x="600" y="238" text-anchor="middle" font-size="15" fill="#c6c8ea">host • user • port • stable alias • safe preferences</text>
|
|
25
|
+
|
|
26
|
+
<rect x="110" y="340" width="390" height="190" rx="20" fill="#1c3342" stroke="#3b7f9b" stroke-width="2"/>
|
|
27
|
+
<text x="305" y="381" text-anchor="middle" font-size="15" font-weight="700" fill="#8ed2eb">WORKSTATION A</text>
|
|
28
|
+
<text x="305" y="415" text-anchor="middle" font-size="24" font-weight="700" fill="#ffffff">Windows / Mac</text>
|
|
29
|
+
<text x="305" y="452" text-anchor="middle" font-size="15" fill="#bddce8">own SSH private key + trust</text>
|
|
30
|
+
<text x="305" y="478" text-anchor="middle" font-size="15" fill="#bddce8">Docker + local runtime/data</text>
|
|
31
|
+
<text x="305" y="504" text-anchor="middle" font-size="15" fill="#bddce8">local credential prompts/cache</text>
|
|
32
|
+
|
|
33
|
+
<rect x="700" y="340" width="390" height="190" rx="20" fill="#1c3342" stroke="#3b7f9b" stroke-width="2"/>
|
|
34
|
+
<text x="895" y="381" text-anchor="middle" font-size="15" font-weight="700" fill="#8ed2eb">WORKSTATION B</text>
|
|
35
|
+
<text x="895" y="415" text-anchor="middle" font-size="24" font-weight="700" fill="#ffffff">second computer</text>
|
|
36
|
+
<text x="895" y="452" text-anchor="middle" font-size="15" fill="#bddce8">different SSH private key + trust</text>
|
|
37
|
+
<text x="895" y="478" text-anchor="middle" font-size="15" fill="#bddce8">Docker + local runtime/data</text>
|
|
38
|
+
<text x="895" y="504" text-anchor="middle" font-size="15" fill="#bddce8">same portable definitions</text>
|
|
39
|
+
|
|
40
|
+
<rect x="365" y="585" width="470" height="82" rx="18" fill="#213825" stroke="#4a9a58" stroke-width="2"/>
|
|
41
|
+
<text x="600" y="620" text-anchor="middle" font-size="15" font-weight="700" fill="#8fda9d">REMOTE TARGETS</text>
|
|
42
|
+
<text x="600" y="648" text-anchor="middle" font-size="18" fill="#ffffff">Docker runtime + Project Forge platform • no developer PAT required</text>
|
|
43
|
+
</g>
|
|
44
|
+
|
|
45
|
+
<g stroke="#8ea0bd" stroke-width="3" fill="none" marker-end="url(#arrow)">
|
|
46
|
+
<path d="M505 265 C460 300 390 310 330 332"/>
|
|
47
|
+
<path d="M695 265 C740 300 810 310 870 332"/>
|
|
48
|
+
<path d="M305 530 C350 570 430 588 505 600"/>
|
|
49
|
+
<path d="M895 530 C850 570 770 588 695 600"/>
|
|
50
|
+
</g>
|
|
51
|
+
|
|
52
|
+
<text x="84" y="578" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="14" fill="#8ea0bd">Verified SSH</text>
|
|
53
|
+
<text x="1018" y="578" text-anchor="end" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="14" fill="#8ea0bd">Verified SSH</text>
|
|
54
|
+
</svg>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="720" viewBox="0 0 1200 720" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Project Forge environment model</title>
|
|
3
|
+
<desc id="desc">A POC uses local workstation runtime and optional remote logical staging and production environments bound to reusable remote targets and shared host platform infrastructure.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
6
|
+
<stop offset="0" stop-color="#0b1220"/>
|
|
7
|
+
<stop offset="1" stop-color="#172033"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
10
|
+
<feDropShadow dx="0" dy="8" stdDeviation="9" flood-opacity="0.3"/>
|
|
11
|
+
</filter>
|
|
12
|
+
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
|
13
|
+
<path d="M0,0 L10,5 L0,10 Z" fill="#8ea0bd"/>
|
|
14
|
+
</marker>
|
|
15
|
+
</defs>
|
|
16
|
+
<rect width="1200" height="720" rx="24" fill="url(#bg)"/>
|
|
17
|
+
<text x="70" y="70" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="30" font-weight="700" fill="#f7f9fc">POC Environment Model</text>
|
|
18
|
+
<text x="70" y="102" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="17" fill="#aebbd0">The POC owns application/environment facts; reusable workstation and target layers own machine mechanics.</text>
|
|
19
|
+
|
|
20
|
+
<g filter="url(#shadow)" font-family="Inter,Segoe UI,Arial,sans-serif">
|
|
21
|
+
<rect x="390" y="135" width="420" height="106" rx="20" fill="#25294a" stroke="#7277c9" stroke-width="2"/>
|
|
22
|
+
<text x="600" y="176" text-anchor="middle" font-size="15" font-weight="700" fill="#aaaef1">GENERATED POC</text>
|
|
23
|
+
<text x="600" y="209" text-anchor="middle" font-size="21" font-weight="700" fill="#ffffff">Environment bindings + app runtime facts</text>
|
|
24
|
+
|
|
25
|
+
<rect x="95" y="330" width="300" height="190" rx="20" fill="#173c35" stroke="#3a9d82" stroke-width="2"/>
|
|
26
|
+
<text x="245" y="370" text-anchor="middle" font-size="15" font-weight="700" fill="#79d9bc">LOCAL</text>
|
|
27
|
+
<text x="245" y="405" text-anchor="middle" font-size="23" font-weight="700" fill="#ffffff">Developer workstation</text>
|
|
28
|
+
<text x="245" y="442" text-anchor="middle" font-size="15" fill="#b7ded3">Docker app container</text>
|
|
29
|
+
<text x="245" y="468" text-anchor="middle" font-size="15" fill="#b7ded3">local SQLite / app data</text>
|
|
30
|
+
<text x="245" y="494" text-anchor="middle" font-size="15" fill="#b7ded3">loopback exposure</text>
|
|
31
|
+
|
|
32
|
+
<rect x="450" y="310" width="650" height="300" rx="22" fill="#20293a" stroke="#50627d" stroke-width="2"/>
|
|
33
|
+
<text x="775" y="350" text-anchor="middle" font-size="15" font-weight="700" fill="#9baac2">REMOTE TARGET LAYER</text>
|
|
34
|
+
|
|
35
|
+
<rect x="500" y="385" width="245" height="150" rx="18" fill="#3d3219" stroke="#b58b2e" stroke-width="2"/>
|
|
36
|
+
<text x="622" y="423" text-anchor="middle" font-size="15" font-weight="700" fill="#e5bf65">STAGING</text>
|
|
37
|
+
<text x="622" y="455" text-anchor="middle" font-size="21" font-weight="700" fill="#ffffff">Docker app</text>
|
|
38
|
+
<text x="622" y="484" text-anchor="middle" font-size="14" fill="#ddc98e">own runtime + data</text>
|
|
39
|
+
<text x="622" y="509" text-anchor="middle" font-size="14" fill="#ddc98e">logical target binding</text>
|
|
40
|
+
|
|
41
|
+
<rect x="805" y="385" width="245" height="150" rx="18" fill="#3b2228" stroke="#b55a6f" stroke-width="2"/>
|
|
42
|
+
<text x="928" y="423" text-anchor="middle" font-size="15" font-weight="700" fill="#ed8ca2">PRODUCTION</text>
|
|
43
|
+
<text x="928" y="455" text-anchor="middle" font-size="21" font-weight="700" fill="#ffffff">Docker app</text>
|
|
44
|
+
<text x="928" y="484" text-anchor="middle" font-size="14" fill="#ddb2bc">own runtime + data</text>
|
|
45
|
+
<text x="928" y="509" text-anchor="middle" font-size="14" fill="#ddb2bc">separate authorization</text>
|
|
46
|
+
|
|
47
|
+
<rect x="545" y="558" width="460" height="80" rx="16" fill="#273246" stroke="#5b6d89" stroke-width="2"/>
|
|
48
|
+
<text x="775" y="590" text-anchor="middle" font-size="15" font-weight="700" fill="#a9b6cc">REUSABLE SHARED HOST PLATFORM</text>
|
|
49
|
+
<text x="775" y="616" text-anchor="middle" font-size="14" fill="#c3ccda">Docker engine • Caddy • shared network • target access mechanics</text>
|
|
50
|
+
</g>
|
|
51
|
+
|
|
52
|
+
<g stroke="#8ea0bd" stroke-width="3" fill="none" marker-end="url(#arrow)">
|
|
53
|
+
<path d="M500 241 C420 275 330 292 275 320"/>
|
|
54
|
+
<path d="M600 241 C620 285 620 320 622 375"/>
|
|
55
|
+
<path d="M700 241 C850 280 915 315 928 375"/>
|
|
56
|
+
<path d="M622 535 V548"/>
|
|
57
|
+
<path d="M928 535 V548"/>
|
|
58
|
+
</g>
|
|
59
|
+
|
|
60
|
+
<text x="70" y="668" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="14" fill="#8ea0bd">Staging and Production may bind to separate targets or share a physical host; logical identity and Production gates remain distinct.</text>
|
|
61
|
+
</svg>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""CLI for generic immutable application runtime deployment."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .app_runtime import (
|
|
11
|
+
APP_DEPLOY_WRITE_PHRASE,
|
|
12
|
+
PRODUCTION_APP_DEPLOY_WRITE_PHRASE,
|
|
13
|
+
app_inventory,
|
|
14
|
+
apply_app_deploy,
|
|
15
|
+
deploy_write_phrase,
|
|
16
|
+
normalize_app_spec,
|
|
17
|
+
plan_app_deploy,
|
|
18
|
+
)
|
|
19
|
+
from .app_runtime_workstation import apply_app_deploy_from_workstation
|
|
20
|
+
from .errors import DeploymentKitError
|
|
21
|
+
from .sqlite_adapter import assert_app_spec_matches_persistence
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _print(value: Any, *, as_json: bool) -> None:
|
|
25
|
+
if as_json:
|
|
26
|
+
print(json.dumps(value, indent=2, sort_keys=True))
|
|
27
|
+
return
|
|
28
|
+
if isinstance(value, dict):
|
|
29
|
+
for key, item in value.items():
|
|
30
|
+
if isinstance(item, (dict, list)):
|
|
31
|
+
print(f"{key}: {json.dumps(item, sort_keys=True)}")
|
|
32
|
+
else:
|
|
33
|
+
print(f"{key}: {item}")
|
|
34
|
+
return
|
|
35
|
+
print(value)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_env(items: list[str]) -> list[tuple[str, str]]:
|
|
39
|
+
result: list[tuple[str, str]] = []
|
|
40
|
+
for item in items:
|
|
41
|
+
if "=" not in item:
|
|
42
|
+
raise DeploymentKitError("--env entries must use NAME=value form.")
|
|
43
|
+
key, value = item.split("=", 1)
|
|
44
|
+
result.append((key, value))
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _confirm(args: argparse.Namespace, phrase: str, action: str) -> None:
|
|
49
|
+
if args.yes:
|
|
50
|
+
return
|
|
51
|
+
entered = input(f"Type {phrase} to {action}: ").strip()
|
|
52
|
+
if entered != phrase:
|
|
53
|
+
raise DeploymentKitError(f"{action.capitalize()} was not authorized.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
57
|
+
parser = argparse.ArgumentParser(prog="project-forge-deploy app")
|
|
58
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
59
|
+
|
|
60
|
+
inventory = sub.add_parser(
|
|
61
|
+
"inventory",
|
|
62
|
+
help="Read Kit-managed application runtime ownership/readiness without writes",
|
|
63
|
+
)
|
|
64
|
+
inventory.add_argument("name")
|
|
65
|
+
inventory.add_argument("--json", action="store_true")
|
|
66
|
+
|
|
67
|
+
deploy = sub.add_parser(
|
|
68
|
+
"deploy",
|
|
69
|
+
help="Deploy one immutable application runtime without modifying routes",
|
|
70
|
+
)
|
|
71
|
+
deploy.add_argument("name")
|
|
72
|
+
deploy.add_argument("--owner-repo", required=True)
|
|
73
|
+
deploy.add_argument("--environment", required=True)
|
|
74
|
+
deploy.add_argument(
|
|
75
|
+
"--production",
|
|
76
|
+
action="store_true",
|
|
77
|
+
help="Classify this named environment as Production even when its name is not literally 'production'",
|
|
78
|
+
)
|
|
79
|
+
deploy.add_argument("--app-slug", required=True)
|
|
80
|
+
deploy.add_argument("--container-name", required=True)
|
|
81
|
+
deploy.add_argument("--image", required=True)
|
|
82
|
+
deploy.add_argument("--runtime-port", type=int, required=True)
|
|
83
|
+
deploy.add_argument("--health-path", required=True)
|
|
84
|
+
deploy.add_argument("--run-as", required=True)
|
|
85
|
+
deploy.add_argument(
|
|
86
|
+
"--persistence-mode",
|
|
87
|
+
choices=("none", "directory"),
|
|
88
|
+
required=True,
|
|
89
|
+
)
|
|
90
|
+
deploy.add_argument("--data-container-path")
|
|
91
|
+
deploy.add_argument(
|
|
92
|
+
"--env",
|
|
93
|
+
action="append",
|
|
94
|
+
default=[],
|
|
95
|
+
help="Non-secret runtime environment value in NAME=value form; repeat as needed",
|
|
96
|
+
)
|
|
97
|
+
deploy.add_argument(
|
|
98
|
+
"--image-source",
|
|
99
|
+
choices=("remote-registry", "workstation"),
|
|
100
|
+
default="remote-registry",
|
|
101
|
+
help=(
|
|
102
|
+
"remote-registry preserves the existing target-side pull path; workstation streams an exact local image "
|
|
103
|
+
"over verified SSH so the target does not need registry credentials"
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
deploy.add_argument("--json", action="store_true")
|
|
107
|
+
deploy.add_argument("--yes", action="store_true")
|
|
108
|
+
return parser
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def main(argv: list[str] | None = None) -> int:
|
|
112
|
+
args = build_parser().parse_args(argv)
|
|
113
|
+
try:
|
|
114
|
+
if args.command == "inventory":
|
|
115
|
+
_print(app_inventory(args.name), as_json=args.json)
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
spec = normalize_app_spec(
|
|
119
|
+
owner_repo=args.owner_repo,
|
|
120
|
+
environment=args.environment,
|
|
121
|
+
app_slug=args.app_slug,
|
|
122
|
+
container_name=args.container_name,
|
|
123
|
+
image=args.image,
|
|
124
|
+
runtime_port=args.runtime_port,
|
|
125
|
+
health_path=args.health_path,
|
|
126
|
+
run_as=args.run_as,
|
|
127
|
+
persistence_mode=args.persistence_mode,
|
|
128
|
+
data_container_path=args.data_container_path,
|
|
129
|
+
environment_variables=_parse_env(args.env),
|
|
130
|
+
)
|
|
131
|
+
assert_app_spec_matches_persistence(args.name, spec)
|
|
132
|
+
plan = plan_app_deploy(args.name, spec)
|
|
133
|
+
plan_view = {key: value for key, value in plan.items() if key != "spec"}
|
|
134
|
+
plan_view["imageSource"] = args.image_source
|
|
135
|
+
production_classified = args.production or plan["environment"] == "production"
|
|
136
|
+
plan_view["productionClassified"] = production_classified
|
|
137
|
+
if args.image_source == "workstation":
|
|
138
|
+
plan_view["targetRegistryCredentialRequired"] = False
|
|
139
|
+
_print(plan_view, as_json=False)
|
|
140
|
+
phrase = PRODUCTION_APP_DEPLOY_WRITE_PHRASE if production_classified else deploy_write_phrase(plan)
|
|
141
|
+
_confirm(
|
|
142
|
+
args,
|
|
143
|
+
phrase,
|
|
144
|
+
f"deploy {plan['appSlug']} ({plan['environment']}) on target {args.name}",
|
|
145
|
+
)
|
|
146
|
+
result = (
|
|
147
|
+
apply_app_deploy_from_workstation(plan)
|
|
148
|
+
if args.image_source == "workstation"
|
|
149
|
+
else apply_app_deploy(plan)
|
|
150
|
+
)
|
|
151
|
+
_print(result, as_json=args.json)
|
|
152
|
+
return 0
|
|
153
|
+
except DeploymentKitError as error:
|
|
154
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
155
|
+
return 2
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
__all__ = [
|
|
159
|
+
"APP_DEPLOY_WRITE_PHRASE",
|
|
160
|
+
"PRODUCTION_APP_DEPLOY_WRITE_PHRASE",
|
|
161
|
+
"build_parser",
|
|
162
|
+
"main",
|
|
163
|
+
]
|