SDT update
This commit is contained in:
parent
27cc379eb8
commit
06c0d30aaa
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
63
Journal.DevTool/scripts/README.md
Normal file
63
Journal.DevTool/scripts/README.md
Normal file
@ -0,0 +1,63 @@
|
||||
# Scripts (Python-first, cross-platform)
|
||||
|
||||
This folder now uses Python as the default runtime for orchestration and diagnostics.
|
||||
|
||||
## Preferred scripts
|
||||
|
||||
- `diag.py`: tool probing and install-plan generation (`dotnet`, `python`, `node`, `npm`, `cargo`, `tauri`)
|
||||
- `build.py`: normalized build actions used by SDT workflows
|
||||
- `dev_shell.py`: cross-platform shell bootstrap export/doctor helper
|
||||
- `dotnet-min.py`: resilient `dotnet` wrapper with local cache env
|
||||
- `pip-min.py`: resilient `pip` wrapper with local cache env and repo-local target default
|
||||
- `npm-clean.py`: remove `node_modules` cross-platform
|
||||
- `migration-gate.py`: build/test quality gate
|
||||
- `nuget-export-cache.py`: archive `.nuget` cache
|
||||
- `nuget-import-cache.py`: restore `.nuget` cache from archive
|
||||
- `publish-app.py`: build web or tauri app (cross-platform)
|
||||
- `publish-sidecar.py`: publish sidecar .NET service
|
||||
- `publish-webgateway.py`: publish gateway .NET service and optional web assets
|
||||
- `run-webgateway.py`: run gateway in dev or published-output mode
|
||||
- `publish-output.py`: orchestrate sidecar/web/gateway/desktop publish steps
|
||||
- `sync-output.py`: sweep newest build artifacts into `output/`
|
||||
- `script_common.py`: shared helpers (repo root resolution, env shaping, command runner)
|
||||
- `project.rootHints` supports glob markers (for example `*.sln`) and directory/file markers (`.git`, `package.json`)
|
||||
- Windows PATH token expansion (`%NVM_HOME%`, `%NVM_SYMLINK%`, etc.) is applied during command resolution
|
||||
|
||||
## Shell bootstrap wrappers
|
||||
|
||||
- `dev-shell.ps1`: PowerShell wrapper over `dev_shell.py`
|
||||
- `dev-shell.sh`: bash/zsh wrapper over `dev_shell.py`
|
||||
- `dev-shell.cmd`: cmd wrapper over `dev_shell.py`
|
||||
|
||||
## Legacy scripts
|
||||
|
||||
Existing `.ps1` entrypoints are now compatibility wrappers that forward to Python scripts.
|
||||
`script-common.ps1` is legacy-only compatibility and not used by active SDT workflows.
|
||||
|
||||
Original PowerShell implementations are archived under `scripts/legacy/` as `*.legacy.ps1` for reference during transition.
|
||||
|
||||
## Root Hint Semantics
|
||||
|
||||
`project.rootHints` is evaluated in this order:
|
||||
1. Exact marker exists at candidate root (file or directory)
|
||||
2. Root-level glob match (`glob`)
|
||||
3. Recursive glob match (`rglob`)
|
||||
|
||||
Examples:
|
||||
- `"*.sln"`
|
||||
- `".git"`
|
||||
- `"package.json"`
|
||||
- `"src-tauri/tauri.conf.json"`
|
||||
|
||||
## Quick usage
|
||||
|
||||
```powershell
|
||||
python scripts/diag.py probe --tool dotnet --json
|
||||
python scripts/dotnet-min.py build
|
||||
python scripts/migration-gate.py
|
||||
python scripts/nuget-export-cache.py --output-zip nuget-cache-export.zip
|
||||
python scripts/nuget-import-cache.py --input-zip nuget-cache-export.zip
|
||||
python scripts/npm-clean.py --working-dir .
|
||||
python scripts/dev_shell.py export --shell pwsh --json
|
||||
python scripts/dev_shell.py doctor
|
||||
```
|
||||
67
Journal.DevTool/scripts/WORKFLOWS.md
Normal file
67
Journal.DevTool/scripts/WORKFLOWS.md
Normal file
@ -0,0 +1,67 @@
|
||||
# Cross-Platform Script Workflows
|
||||
|
||||
## 1) Probe toolchain availability
|
||||
|
||||
```powershell
|
||||
python scripts/diag.py probe --tool dotnet --json
|
||||
python scripts/diag.py probe --tool python --json
|
||||
python scripts/diag.py probe --tool node --json
|
||||
python scripts/diag.py probe --tool npm --json
|
||||
python scripts/diag.py probe --tool cargo --json
|
||||
python scripts/diag.py probe --tool tauri --json
|
||||
python scripts/diag.py probe --tool git --json
|
||||
python scripts/diag.py probe --tool docker --json
|
||||
```
|
||||
|
||||
## Shell bootstrap (cross-platform)
|
||||
|
||||
```powershell
|
||||
python scripts/dev_shell.py export --shell pwsh --json
|
||||
python scripts/dev_shell.py doctor
|
||||
```
|
||||
|
||||
## 2) Build and run SDT
|
||||
|
||||
```powershell
|
||||
python scripts/dotnet-min.py build
|
||||
dotnet run --project DevTool.csproj
|
||||
```
|
||||
|
||||
## 3) Run migration gate
|
||||
|
||||
```powershell
|
||||
python scripts/migration-gate.py
|
||||
```
|
||||
|
||||
## 3.1) Verify workflow route resolution (path + optional execution)
|
||||
|
||||
```powershell
|
||||
# Static route checks only
|
||||
python scripts/verify-workflow-routes.py --project-root .
|
||||
|
||||
# Static + headless execution checks for selected workflows
|
||||
python scripts/verify-workflow-routes.py --project-root . --workflow build --workflow tauri --execute --env-profile dev
|
||||
```
|
||||
|
||||
## 4) Manage NuGet cache
|
||||
|
||||
```powershell
|
||||
python scripts/nuget-export-cache.py --output-zip nuget-cache-export.zip
|
||||
python scripts/nuget-import-cache.py --input-zip nuget-cache-export.zip
|
||||
```
|
||||
|
||||
## 5) Clean Node modules
|
||||
|
||||
```powershell
|
||||
python scripts/npm-clean.py --working-dir .
|
||||
```
|
||||
|
||||
## 6) Build app/gateway bundles
|
||||
|
||||
```powershell
|
||||
python scripts/publish-app.py --target web
|
||||
python scripts/publish-sidecar.py --project path/to/sidecar.csproj
|
||||
python scripts/publish-webgateway.py --project path/to/gateway.csproj --skip-web-assets
|
||||
python scripts/publish-output.py --dry-run
|
||||
python scripts/sync-output.py
|
||||
```
|
||||
@ -8,11 +8,15 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from script_common import resolve_command
|
||||
from typing import Any
|
||||
|
||||
from script_common import resolve_command, SdtResult # type: ignore
|
||||
|
||||
StepResult = dict[str, Any]
|
||||
|
||||
|
||||
def run_step(command, args, cwd):
|
||||
resolved = resolve_command(command)
|
||||
def run_step(command: str, args: list[str], cwd: str) -> StepResult:
|
||||
resolved = str(resolve_command(command))
|
||||
if shutil.which(resolved) is None and not pathlib.Path(resolved).exists():
|
||||
return {
|
||||
"command": resolved,
|
||||
@ -38,7 +42,7 @@ def run_step(command, args, cwd):
|
||||
}
|
||||
|
||||
|
||||
def resolve_python_executable():
|
||||
def resolve_python_executable() -> str:
|
||||
candidates = ["py", "python"] if os.name == "nt" else ["python3", "python"]
|
||||
for c in candidates:
|
||||
if shutil.which(c):
|
||||
@ -46,20 +50,20 @@ def resolve_python_executable():
|
||||
return "python"
|
||||
|
||||
|
||||
def parse_common(parser):
|
||||
parser.add_argument("--project-root", required=True)
|
||||
parser.add_argument("--working-dir", default=".")
|
||||
parser.add_argument("--json", action="store_true")
|
||||
def parse_common(parser: argparse.ArgumentParser) -> None:
|
||||
_ = parser.add_argument("--project-root", required=True)
|
||||
_ = parser.add_argument("--working-dir", default=".")
|
||||
_ = parser.add_argument("--json", action="store_true")
|
||||
|
||||
|
||||
def resolve_cwd(project_root, working_dir):
|
||||
def resolve_cwd(project_root: str, working_dir: str) -> str:
|
||||
return os.path.abspath(os.path.join(project_root, working_dir))
|
||||
|
||||
|
||||
EXCLUDED_SCAN_DIRS = {".git", "node_modules", "bin", "obj", ".venv", "venv", ".sdt", "dist", "build"}
|
||||
|
||||
|
||||
def discover_dotnet_target(project_root: str, cwd: str):
|
||||
def discover_dotnet_target(project_root: str, cwd: str) -> str | None:
|
||||
# Prefer local solution first (.slnx, then .sln), then csproj, then bounded scan from project root.
|
||||
local_slnx = sorted(pathlib.Path(cwd).glob("*.slnx"))
|
||||
if len(local_slnx) == 1:
|
||||
@ -88,9 +92,9 @@ def discover_dotnet_target(project_root: str, cwd: str):
|
||||
return None
|
||||
|
||||
|
||||
def bounded_find_files(root: str, extension: str, max_depth: int):
|
||||
def bounded_find_files(root: str, extension: str, max_depth: int) -> list[str]:
|
||||
root_path = pathlib.Path(root).resolve()
|
||||
results = []
|
||||
results: list[str] = []
|
||||
for current_root, dirs, files in os.walk(root_path):
|
||||
rel = pathlib.Path(current_root).resolve().relative_to(root_path)
|
||||
depth = len(rel.parts)
|
||||
@ -105,7 +109,7 @@ def bounded_find_files(root: str, extension: str, max_depth: int):
|
||||
return sorted(results)
|
||||
|
||||
|
||||
def run_dotnet_action(project_root, working_dir, verb):
|
||||
def run_dotnet_action(project_root: str, working_dir: str, verb: str) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(project_root, working_dir)
|
||||
target = discover_dotnet_target(project_root, cwd)
|
||||
if not target:
|
||||
@ -124,10 +128,14 @@ def run_dotnet_action(project_root, working_dir, verb):
|
||||
args = [verb, target]
|
||||
step = run_step("dotnet", args, cwd)
|
||||
step["resolved_target"] = target
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
|
||||
# Explicitly ensure the first return value is an integer and narrow the step result
|
||||
exit_val = step.get("exit_code", 1)
|
||||
exit_code = int(exit_val) if isinstance(exit_val, (int, float, str)) and str(exit_val).isdigit() else 1
|
||||
return exit_code, step
|
||||
|
||||
|
||||
def _deps_hash(app_root):
|
||||
def _deps_hash(app_root: str) -> str:
|
||||
h = hashlib.sha256()
|
||||
for name in ("package.json", "package-lock.json"):
|
||||
p = pathlib.Path(app_root) / name
|
||||
@ -136,7 +144,7 @@ def _deps_hash(app_root):
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def ensure_npm_dependencies(app_root):
|
||||
def ensure_npm_dependencies(app_root: str) -> dict[str, Any]:
|
||||
package_json = pathlib.Path(app_root) / "package.json"
|
||||
if not package_json.exists():
|
||||
return {"installed": False, "reason": "not_applicable"}
|
||||
@ -174,7 +182,7 @@ def ensure_npm_dependencies(app_root):
|
||||
return {"installed": True, "reason": "installed", "step": install_step}
|
||||
|
||||
|
||||
def read_package_json(cwd: str):
|
||||
def read_package_json(cwd: str) -> dict[str, Any] | None:
|
||||
package_json = pathlib.Path(cwd) / "package.json"
|
||||
if not package_json.exists():
|
||||
return None
|
||||
@ -194,23 +202,23 @@ def has_npm_script(cwd: str, script_name: str) -> bool:
|
||||
return script_name in scripts and isinstance(scripts.get(script_name), str)
|
||||
|
||||
|
||||
def action_dotnet_build(args):
|
||||
def action_dotnet_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
return run_dotnet_action(args.project_root, args.working_dir, "build")
|
||||
|
||||
|
||||
def action_dotnet_restore(args):
|
||||
def action_dotnet_restore(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
return run_dotnet_action(args.project_root, args.working_dir, "restore")
|
||||
|
||||
|
||||
def action_dotnet_test(args):
|
||||
def action_dotnet_test(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
return run_dotnet_action(args.project_root, args.working_dir, "test")
|
||||
|
||||
|
||||
def action_dotnet_publish(args):
|
||||
def action_dotnet_publish(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
return run_dotnet_action(args.project_root, args.working_dir, "publish")
|
||||
|
||||
|
||||
def action_npm_install(args):
|
||||
def action_npm_install(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||
return 0, {
|
||||
@ -224,10 +232,10 @@ def action_npm_install(args):
|
||||
"skip_reason": "not_applicable_no_package_json",
|
||||
}
|
||||
step = run_step("npm", ["install"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_npm_ci(args):
|
||||
def action_npm_ci(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||
return 0, {
|
||||
@ -241,10 +249,10 @@ def action_npm_ci(args):
|
||||
"skip_reason": "not_applicable_no_package_json",
|
||||
}
|
||||
step = run_step("npm", ["ci"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_npm_build(args):
|
||||
def action_npm_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||
return 0, {
|
||||
@ -283,12 +291,12 @@ def action_npm_build(args):
|
||||
if deps.get("reason") == "install_failed":
|
||||
step = deps["step"]
|
||||
step["failure_reason"] = "deps_install_failed"
|
||||
return step["exit_code"], step
|
||||
return int(step["exit_code"]), step
|
||||
step = run_step("npm", ["run", "build"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_npm_test(args):
|
||||
def action_npm_test(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||
return 0, {
|
||||
@ -327,12 +335,12 @@ def action_npm_test(args):
|
||||
if deps.get("reason") == "install_failed":
|
||||
step = deps["step"]
|
||||
step["failure_reason"] = "deps_install_failed"
|
||||
return step["exit_code"], step
|
||||
return int(step["exit_code"]), step
|
||||
step = run_step("npm", ["test"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_npm_audit(args):
|
||||
def action_npm_audit(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||
return 0, {
|
||||
@ -346,37 +354,37 @@ def action_npm_audit(args):
|
||||
"skip_reason": "not_applicable_no_package_json",
|
||||
}
|
||||
step = run_step("npm", ["audit"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_python_venv_create(args):
|
||||
def action_python_venv_create(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, ".")
|
||||
venv_dir = args.venv_dir or ".venv"
|
||||
venv_dir = str(args.venv_dir) if hasattr(args, "venv_dir") else ".venv"
|
||||
step = run_step(resolve_python_executable(), ["-m", "venv", venv_dir], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_python_pip_install(args):
|
||||
def action_python_pip_install(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, ".")
|
||||
req = args.requirements
|
||||
req = str(args.requirements)
|
||||
step = run_step(resolve_python_executable(), ["-m", "pip", "install", "-r", req], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_python_pip_sync(args):
|
||||
def action_python_pip_sync(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, ".")
|
||||
req = args.requirements
|
||||
req = str(args.requirements)
|
||||
step = run_step(resolve_python_executable(), ["-m", "pip", "install", "-r", req], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_python_pytest(args):
|
||||
def action_python_pytest(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
step = run_step(resolve_python_executable(), ["-m", "pytest"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_cargo_build(args):
|
||||
def action_cargo_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
if not (pathlib.Path(cwd) / "Cargo.toml").exists():
|
||||
return 0, {
|
||||
@ -390,10 +398,10 @@ def action_cargo_build(args):
|
||||
"skip_reason": "not_applicable_no_cargo_toml",
|
||||
}
|
||||
step = run_step("cargo", ["build"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_cargo_test(args):
|
||||
def action_cargo_test(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
if not (pathlib.Path(cwd) / "Cargo.toml").exists():
|
||||
return 0, {
|
||||
@ -407,10 +415,10 @@ def action_cargo_test(args):
|
||||
"skip_reason": "not_applicable_no_cargo_toml",
|
||||
}
|
||||
step = run_step("cargo", ["test"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_tauri_build(args):
|
||||
def action_tauri_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
tauri_conf = pathlib.Path(cwd) / "src-tauri" / "tauri.conf.json"
|
||||
if not tauri_conf.exists():
|
||||
@ -431,133 +439,94 @@ def action_tauri_build(args):
|
||||
if deps.get("reason") == "install_failed":
|
||||
step = deps["step"]
|
||||
step["failure_reason"] = "deps_install_failed"
|
||||
return step["exit_code"], step
|
||||
return int(step["exit_code"]), step
|
||||
|
||||
tauri_args = ["run", "tauri", "build"]
|
||||
if args.no_bundle:
|
||||
if hasattr(args, "no_bundle") and args.no_bundle:
|
||||
tauri_args.extend(["--", "--no-bundle"])
|
||||
step = run_step("npm", tauri_args, cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_git_status(args):
|
||||
def action_git_status(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
step = run_step("git", ["status"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_git_fetch(args):
|
||||
def action_git_fetch(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
step = run_step("git", ["fetch"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_git_pull(args):
|
||||
def action_git_pull(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
step = run_step("git", ["pull"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_git_clean(args):
|
||||
def action_git_clean(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
step = run_step("git", ["clean", "-fd"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_docker_build(args):
|
||||
def action_docker_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
step = run_step("docker", ["build", "."], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_docker_compose_up(args):
|
||||
def action_docker_compose_up(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
step = run_step("docker", ["compose", "up", "-d"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def action_docker_compose_down(args):
|
||||
def action_docker_compose_down(args: argparse.Namespace) -> tuple[int, StepResult]:
|
||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||
step = run_step("docker", ["compose", "down"], cwd)
|
||||
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="SDT normalized build actions")
|
||||
sub = parser.add_subparsers(dest="action", required=True)
|
||||
|
||||
p0 = sub.add_parser("dotnet-restore")
|
||||
parse_common(p0)
|
||||
p0 = sub.add_parser("dotnet-restore"); parse_common(p0)
|
||||
p1 = sub.add_parser("dotnet-build"); parse_common(p1)
|
||||
p1b = sub.add_parser("dotnet-test"); parse_common(p1b)
|
||||
p1c = sub.add_parser("dotnet-publish"); parse_common(p1c)
|
||||
p2 = sub.add_parser("npm-install"); parse_common(p2)
|
||||
p2b = sub.add_parser("npm-ci"); parse_common(p2b)
|
||||
p3 = sub.add_parser("npm-build"); parse_common(p3)
|
||||
p3b = sub.add_parser("npm-test"); parse_common(p3b)
|
||||
p3c = sub.add_parser("npm-audit"); parse_common(p3c)
|
||||
|
||||
p1 = sub.add_parser("dotnet-build")
|
||||
parse_common(p1)
|
||||
p4 = sub.add_parser("python-venv-create"); parse_common(p4)
|
||||
_ = p4.add_argument("--venv-dir", default=".venv")
|
||||
|
||||
p1b = sub.add_parser("dotnet-test")
|
||||
parse_common(p1b)
|
||||
p5 = sub.add_parser("python-pip-install"); parse_common(p5)
|
||||
_ = p5.add_argument("--requirements", required=True)
|
||||
|
||||
p1c = sub.add_parser("dotnet-publish")
|
||||
parse_common(p1c)
|
||||
p5b = sub.add_parser("python-pip-sync"); parse_common(p5b)
|
||||
_ = p5b.add_argument("--requirements", required=True)
|
||||
|
||||
p2 = sub.add_parser("npm-install")
|
||||
parse_common(p2)
|
||||
p5c = sub.add_parser("python-pytest"); parse_common(p5c)
|
||||
p6 = sub.add_parser("cargo-build"); parse_common(p6)
|
||||
p6b = sub.add_parser("cargo-test"); parse_common(p6b)
|
||||
|
||||
p2b = sub.add_parser("npm-ci")
|
||||
parse_common(p2b)
|
||||
p7 = sub.add_parser("tauri-build"); parse_common(p7)
|
||||
_ = p7.add_argument("--no-bundle", action="store_true")
|
||||
|
||||
p3 = sub.add_parser("npm-build")
|
||||
parse_common(p3)
|
||||
|
||||
p3b = sub.add_parser("npm-test")
|
||||
parse_common(p3b)
|
||||
|
||||
p3c = sub.add_parser("npm-audit")
|
||||
parse_common(p3c)
|
||||
|
||||
p4 = sub.add_parser("python-venv-create")
|
||||
parse_common(p4)
|
||||
p4.add_argument("--venv-dir", default=".venv")
|
||||
|
||||
p5 = sub.add_parser("python-pip-install")
|
||||
parse_common(p5)
|
||||
p5.add_argument("--requirements", required=True)
|
||||
|
||||
p5b = sub.add_parser("python-pip-sync")
|
||||
parse_common(p5b)
|
||||
p5b.add_argument("--requirements", required=True)
|
||||
|
||||
p5c = sub.add_parser("python-pytest")
|
||||
parse_common(p5c)
|
||||
|
||||
p6 = sub.add_parser("cargo-build")
|
||||
parse_common(p6)
|
||||
|
||||
p6b = sub.add_parser("cargo-test")
|
||||
parse_common(p6b)
|
||||
|
||||
p7 = sub.add_parser("tauri-build")
|
||||
parse_common(p7)
|
||||
p7.add_argument("--no-bundle", action="store_true")
|
||||
|
||||
p8 = sub.add_parser("git-status")
|
||||
parse_common(p8)
|
||||
|
||||
p9 = sub.add_parser("git-fetch")
|
||||
parse_common(p9)
|
||||
|
||||
p10 = sub.add_parser("git-pull")
|
||||
parse_common(p10)
|
||||
|
||||
p11 = sub.add_parser("git-clean")
|
||||
parse_common(p11)
|
||||
|
||||
p12 = sub.add_parser("docker-build")
|
||||
parse_common(p12)
|
||||
|
||||
p13 = sub.add_parser("docker-compose-up")
|
||||
parse_common(p13)
|
||||
|
||||
p14 = sub.add_parser("docker-compose-down")
|
||||
parse_common(p14)
|
||||
p8 = sub.add_parser("git-status"); parse_common(p8)
|
||||
p9 = sub.add_parser("git-fetch"); parse_common(p9)
|
||||
p10 = sub.add_parser("git-pull"); parse_common(p10)
|
||||
p11 = sub.add_parser("git-clean"); parse_common(p11)
|
||||
p12 = sub.add_parser("docker-build"); parse_common(p12)
|
||||
p13 = sub.add_parser("docker-compose-up"); parse_common(p13)
|
||||
p14 = sub.add_parser("docker-compose-down"); parse_common(p14)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@ -590,7 +559,8 @@ def main():
|
||||
code, summary = handlers[args.action](args)
|
||||
if args.json:
|
||||
print(json.dumps(summary))
|
||||
return code
|
||||
|
||||
return int(code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -1,90 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
|
||||
|
||||
from script_common import find_node_app_root, resolve_repo_root, run, sha256_files
|
||||
from typing import cast
|
||||
from script_common import ensure_npm_build, find_node_app_root, resolve_repo_root
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Cross-platform web/tauri publish helper")
|
||||
parser.add_argument("--target", choices=["web", "tauri"], default="web")
|
||||
parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||
parser.add_argument("--tauri-bundles", choices=["none", "nsis", "msi"], default="none")
|
||||
parser.add_argument("--install-deps", action="store_true")
|
||||
parser.add_argument("--skip-install", action="store_true")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--repo-root", default=None)
|
||||
parser.add_argument("--app-root", default=None, help="Relative or absolute app root with package.json")
|
||||
_ = parser.add_argument("--target", choices=["web", "tauri"], default="web")
|
||||
_ = parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||
_ = parser.add_argument("--tauri-bundles", choices=["none", "nsis", "msi"], default="none")
|
||||
_ = parser.add_argument("--install-deps", action="store_true")
|
||||
_ = parser.add_argument("--skip-install", action="store_true")
|
||||
_ = parser.add_argument("--dry-run", action="store_true")
|
||||
_ = parser.add_argument("--repo-root", default=None)
|
||||
_ = parser.add_argument("--app-root", default=None, help="Relative or absolute app root with package.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = resolve_repo_root(args.repo_root)
|
||||
app_root = find_node_app_root(repo_root, args.app_root)
|
||||
repo_root_val = cast(str | None, args.repo_root)
|
||||
repo_root = resolve_repo_root(repo_root_val)
|
||||
app_root_val = cast(str | None, args.app_root)
|
||||
app_root = find_node_app_root(repo_root, app_root_val)
|
||||
if app_root is None:
|
||||
print("Unable to locate app root (no unique package.json found).")
|
||||
return 2
|
||||
|
||||
package_json = app_root / "package.json"
|
||||
lock_file = app_root / "package-lock.json"
|
||||
node_modules = app_root / "node_modules"
|
||||
deps_hash_file = node_modules / ".sdt-deps.sha256"
|
||||
expected_hash = sha256_files([package_json, lock_file])
|
||||
|
||||
should_install = args.install_deps or not node_modules.exists()
|
||||
if not should_install and not args.skip_install:
|
||||
if not deps_hash_file.exists():
|
||||
should_install = True
|
||||
else:
|
||||
current = deps_hash_file.read_text(encoding="utf-8").strip()
|
||||
should_install = current != expected_hash
|
||||
if args.skip_install:
|
||||
should_install = False
|
||||
|
||||
print(f"App root: {app_root}")
|
||||
print(f"Target: {args.target} ({args.configuration})")
|
||||
|
||||
if should_install:
|
||||
install_args = ["ci", "--no-audit", "--fund=false"] if lock_file.exists() else ["install", "--no-audit", "--fund=false"]
|
||||
print("$ npm " + " ".join(install_args))
|
||||
if not args.dry_run:
|
||||
code = run("npm", install_args, app_root)
|
||||
if code != 0:
|
||||
if lock_file.exists() and install_args[0] == "ci":
|
||||
print("npm ci failed (likely lockfile out of sync). Falling back to npm install...")
|
||||
fallback_args = ["install", "--no-audit", "--fund=false"]
|
||||
print("$ npm " + " ".join(fallback_args))
|
||||
code = run("npm", fallback_args, app_root)
|
||||
if code != 0:
|
||||
return code
|
||||
else:
|
||||
return code
|
||||
node_modules.mkdir(parents=True, exist_ok=True)
|
||||
deps_hash_file.write_text(expected_hash, encoding="utf-8")
|
||||
else:
|
||||
print("Skipping dependency install.")
|
||||
|
||||
if args.target == "web":
|
||||
cmd = ["run", "build"]
|
||||
print("$ npm " + " ".join(cmd))
|
||||
if not args.dry_run:
|
||||
return run("npm", cmd, app_root)
|
||||
# If dry-run is requested, we just print intent.
|
||||
if args.dry_run:
|
||||
print(f"Dry-run: Would build {args.target} ({args.configuration}) in {app_root}")
|
||||
return 0
|
||||
|
||||
tauri_cmd = ["run", "tauri", "build"]
|
||||
tauri_tail: list[str] = []
|
||||
if args.tauri_bundles == "none":
|
||||
tauri_tail.extend(["--no-bundle"])
|
||||
else:
|
||||
tauri_tail.extend(["--bundles", args.tauri_bundles])
|
||||
if args.configuration == "Debug":
|
||||
tauri_tail.append("--debug")
|
||||
if tauri_tail:
|
||||
tauri_cmd.extend(["--", *tauri_tail])
|
||||
res = ensure_npm_build(
|
||||
app_root=app_root,
|
||||
target=str(args.target),
|
||||
configuration=str(args.configuration),
|
||||
tauri_bundles=str(args.tauri_bundles)
|
||||
)
|
||||
|
||||
print("$ npm " + " ".join(tauri_cmd))
|
||||
if not args.dry_run:
|
||||
return run("npm", tauri_cmd, app_root)
|
||||
|
||||
return 0
|
||||
return int(res["exit_code"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -1,22 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from script_common import find_csproj_by_keyword, find_node_app_root, resolve_repo_root
|
||||
|
||||
|
||||
def run_step(label: str, cmd: list[str], cwd: Path, dry_run: bool) -> int:
|
||||
print(f"\n> {label}")
|
||||
print("$", " ".join(cmd))
|
||||
if dry_run:
|
||||
return 0
|
||||
proc = subprocess.run(cmd, cwd=str(cwd), check=False)
|
||||
return proc.returncode
|
||||
|
||||
from script_common import find_csproj_by_keyword, find_node_app_root, resolve_repo_root, run # type: ignore
|
||||
|
||||
def has_package_script(app_root: Path, script_name: str) -> bool:
|
||||
package_json = app_root / "package.json"
|
||||
@ -35,18 +24,18 @@ def has_package_script(app_root: Path, script_name: str) -> bool:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Publish bundled outputs using Python script entrypoints")
|
||||
parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||
parser.add_argument("--runtime", default="win-x64")
|
||||
parser.add_argument("--skip-sidecar", action="store_true")
|
||||
parser.add_argument("--skip-web", action="store_true")
|
||||
parser.add_argument("--skip-webgateway", action="store_true")
|
||||
parser.add_argument("--skip-tauri", action="store_true")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--repo-root", default=None)
|
||||
parser.add_argument("--sidecar-project", default=None)
|
||||
parser.add_argument("--gateway-project", default=None)
|
||||
parser.add_argument("--app-root", default=None)
|
||||
parser.add_argument("--output-dir", default="output")
|
||||
_ = parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||
_ = parser.add_argument("--runtime", default="win-x64")
|
||||
_ = parser.add_argument("--skip-sidecar", action="store_true")
|
||||
_ = parser.add_argument("--skip-web", action="store_true")
|
||||
_ = parser.add_argument("--skip-webgateway", action="store_true")
|
||||
_ = parser.add_argument("--skip-tauri", action="store_true")
|
||||
_ = parser.add_argument("--dry-run", action="store_true")
|
||||
_ = parser.add_argument("--repo-root", default=None)
|
||||
_ = parser.add_argument("--sidecar-project", default=None)
|
||||
_ = parser.add_argument("--gateway-project", default=None)
|
||||
_ = parser.add_argument("--app-root", default=None)
|
||||
_ = parser.add_argument("--output-dir", default="output")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = resolve_repo_root(args.repo_root)
|
||||
@ -56,45 +45,48 @@ def main() -> int:
|
||||
sidecar_project = (repo_root / args.sidecar_project).resolve() if args.sidecar_project else find_csproj_by_keyword(repo_root, ["sidecar"])
|
||||
gateway_project = (repo_root / args.gateway_project).resolve() if args.gateway_project else find_csproj_by_keyword(repo_root, ["webgateway", "gateway"])
|
||||
app_root = (repo_root / args.app_root).resolve() if args.app_root else find_node_app_root(repo_root, None)
|
||||
|
||||
tauri_conf = None
|
||||
if app_root is not None:
|
||||
candidate_a = app_root / "src-tauri" / "tauri.conf.json"
|
||||
candidate_b = app_root / "tauri.conf.json"
|
||||
if candidate_a.exists():
|
||||
tauri_conf = candidate_a
|
||||
elif candidate_b.exists():
|
||||
tauri_conf = candidate_b
|
||||
tauri_conf = next((p for p in [app_root/"src-tauri"/"tauri.conf.json", app_root/"tauri.conf.json"] if p.exists()), None)
|
||||
|
||||
py = sys.executable
|
||||
scripts_dir = Path(__file__).parent
|
||||
|
||||
if not args.skip_sidecar:
|
||||
if sidecar_project is None:
|
||||
print("Skipping sidecar: no sidecar csproj detected.")
|
||||
else:
|
||||
cmd = [py, "scripts/publish-sidecar.py", "--configuration", args.configuration, "--runtime", args.runtime]
|
||||
cmd.extend(["--project", str(sidecar_project)])
|
||||
code = run_step("Publish sidecar", cmd, repo_root, args.dry_run)
|
||||
if code != 0:
|
||||
return code
|
||||
cmd = ["-m", "scripts.publish-sidecar" if __package__ else "publish-sidecar",
|
||||
"--configuration", args.configuration, "--runtime", args.runtime, "--project", str(sidecar_project)]
|
||||
print(f"\n> Publishing Sidecar\n$ {py} {' '.join(cmd)}")
|
||||
if not args.dry_run:
|
||||
code = run(py, cmd, scripts_dir)
|
||||
if code != 0: return code
|
||||
|
||||
if not args.skip_web:
|
||||
if app_root is None:
|
||||
print("Skipping web: no app root with package.json detected.")
|
||||
print("Skipping web: no app root detected.")
|
||||
elif not has_package_script(app_root, "build"):
|
||||
print("Skipping web: package.json has no 'build' script.")
|
||||
else:
|
||||
cmd = [py, "scripts/publish-app.py", "--target", "web", "--configuration", args.configuration, "--app-root", str(app_root)]
|
||||
code = run_step("Build web", cmd, repo_root, args.dry_run)
|
||||
if code != 0:
|
||||
return code
|
||||
cmd = ["-m", "scripts.publish-app" if __package__ else "publish-app",
|
||||
"--target", "web", "--configuration", args.configuration, "--app-root", str(app_root)]
|
||||
print(f"\n> Building Web\n$ {py} {' '.join(cmd)}")
|
||||
if not args.dry_run:
|
||||
code = run(py, cmd, scripts_dir)
|
||||
if code != 0: return code
|
||||
|
||||
if not args.skip_webgateway:
|
||||
if gateway_project is None:
|
||||
print("Skipping web gateway: no gateway csproj detected.")
|
||||
else:
|
||||
cmd = [py, "scripts/publish-webgateway.py", "--configuration", args.configuration, "--runtime", args.runtime, "--project", str(gateway_project)]
|
||||
code = run_step("Publish web gateway", cmd, repo_root, args.dry_run)
|
||||
if code != 0:
|
||||
return code
|
||||
cmd = ["-m", "scripts.publish-webgateway" if __package__ else "publish-webgateway",
|
||||
"--configuration", args.configuration, "--runtime", args.runtime, "--project", str(gateway_project)]
|
||||
print(f"\n> Publishing Web Gateway\n$ {py} {' '.join(cmd)}")
|
||||
if not args.dry_run:
|
||||
code = run(py, cmd, scripts_dir)
|
||||
if code != 0: return code
|
||||
|
||||
if not args.skip_tauri:
|
||||
if app_root is None or tauri_conf is None:
|
||||
@ -102,17 +94,20 @@ def main() -> int:
|
||||
elif not has_package_script(app_root, "tauri"):
|
||||
print("Skipping tauri: package.json has no 'tauri' script.")
|
||||
else:
|
||||
cmd = [py, "scripts/publish-app.py", "--target", "tauri", "--configuration", args.configuration, "--tauri-bundles", "none", "--app-root", str(app_root)]
|
||||
code = run_step("Build tauri", cmd, repo_root, args.dry_run)
|
||||
if code != 0:
|
||||
return code
|
||||
cmd = ["-m", "scripts.publish-app" if __package__ else "publish-app",
|
||||
"--target", "tauri", "--configuration", args.configuration, "--tauri-bundles", "none", "--app-root", str(app_root)]
|
||||
print(f"\n> Building Tauri\n$ {py} {' '.join(cmd)}")
|
||||
if not args.dry_run:
|
||||
code = run(py, cmd, scripts_dir)
|
||||
if code != 0: return code
|
||||
|
||||
target_dir = app_root / "src-tauri" / "target" / ("debug" if args.configuration == "Debug" else "release")
|
||||
exes = sorted(target_dir.glob("*.exe"), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
pattern = "*.exe" if os.name == "nt" else "*"
|
||||
exes = sorted((p for p in target_dir.glob(pattern) if p.is_file() and (os.name == "nt" or not p.suffix)), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
|
||||
if exes:
|
||||
staged = output_root / exes[0].name
|
||||
if args.dry_run:
|
||||
print(f"Would copy: {exes[0]} -> {staged}")
|
||||
if args.dry_run: print(f"Would copy: {exes[0]} -> {staged}")
|
||||
else:
|
||||
shutil.copy2(exes[0], staged)
|
||||
print(f"Staged desktop executable: {staged}")
|
||||
|
||||
10
Journal.DevTool/scripts/publish-sidecar.ps1
Normal file
10
Journal.DevTool/scripts/publish-sidecar.ps1
Normal file
@ -0,0 +1,10 @@
|
||||
param(
|
||||
[Parameter(ValueFromRemainingArguments = $($true))]
|
||||
[string[]]$ForwardArgs
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
. (Join-Path $PSScriptRoot '_pwsh-python-shim.ps1')
|
||||
Invoke-SdtPythonScript -ScriptName 'publish-sidecar.py' -ForwardArgs $ForwardArgs
|
||||
@ -1,51 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
|
||||
|
||||
from script_common import dotnet_env, find_csproj_by_keyword, resolve_repo_root, run
|
||||
from typing import cast
|
||||
from script_common import ensure_dotnet_publish, find_csproj_by_keyword, resolve_repo_root, SdtResult
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Cross-platform .NET sidecar publish helper")
|
||||
parser.add_argument("--configuration", default="Release")
|
||||
parser.add_argument("--runtime", default="win-x64")
|
||||
parser.add_argument("--repo-root", default=None)
|
||||
parser.add_argument("--project", default=None, help="Relative/absolute path to sidecar csproj")
|
||||
parser.add_argument("--output-dir", default="output")
|
||||
_ = parser.add_argument("--configuration", default="Release")
|
||||
_ = parser.add_argument("--runtime", default="win-x64")
|
||||
_ = parser.add_argument("--repo-root", default=None)
|
||||
_ = parser.add_argument("--project", default=None, help="Relative/absolute path to sidecar csproj")
|
||||
_ = parser.add_argument("--output-dir", default="output")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = resolve_repo_root(args.repo_root)
|
||||
output_dir = (repo_root / args.output_dir).resolve()
|
||||
repo_root_val = cast(str | None, args.repo_root)
|
||||
repo_root = resolve_repo_root(repo_root_val)
|
||||
output_dir_val = cast(str, args.output_dir)
|
||||
output_dir = (repo_root / output_dir_val).resolve()
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if args.project:
|
||||
csproj = (repo_root / args.project).resolve()
|
||||
project_val = cast(str | None, args.project)
|
||||
if project_val:
|
||||
csproj = (repo_root / project_val).resolve()
|
||||
else:
|
||||
csproj = find_csproj_by_keyword(repo_root, ["sidecar"])
|
||||
|
||||
if csproj is None or not csproj.exists():
|
||||
print("Could not locate sidecar project. Pass --project <path/to/project.csproj>.")
|
||||
return 2
|
||||
|
||||
publish_args = [
|
||||
"publish",
|
||||
str(csproj),
|
||||
"-c",
|
||||
args.configuration,
|
||||
"-r",
|
||||
args.runtime,
|
||||
"--self-contained",
|
||||
"-p:PublishSingleFile=true",
|
||||
"-p:IncludeNativeLibrariesForSelfExtract=true",
|
||||
"-p:RestoreIgnoreFailedSources=true",
|
||||
"-p:NuGetAudit=false",
|
||||
"-o",
|
||||
str(output_dir),
|
||||
]
|
||||
code = run("dotnet", publish_args, repo_root, env=dotnet_env(repo_root))
|
||||
if code != 0:
|
||||
return code
|
||||
res: SdtResult = ensure_dotnet_publish(
|
||||
csproj=csproj,
|
||||
output_dir=output_dir,
|
||||
configuration=str(args.configuration),
|
||||
runtime=str(args.runtime),
|
||||
single_file=True,
|
||||
self_contained=True
|
||||
)
|
||||
|
||||
binary_name = csproj.stem + (".exe" if args.runtime.startswith("win-") else "")
|
||||
if res["exit_code"] != 0:
|
||||
return int(res["exit_code"])
|
||||
|
||||
runtime_val = str(args.runtime)
|
||||
binary_name = csproj.stem + (".exe" if runtime_val.startswith("win-") else "")
|
||||
binary_path = output_dir / binary_name
|
||||
if binary_path.exists():
|
||||
print(f"Published executable: {binary_path}")
|
||||
|
||||
@ -1,74 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import shutil
|
||||
|
||||
from script_common import dotnet_env, find_csproj_by_keyword, resolve_repo_root, run
|
||||
from typing import cast
|
||||
from script_common import ensure_dotnet_publish, find_csproj_by_keyword, resolve_repo_root, SdtResult
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Cross-platform ASP.NET gateway publish helper")
|
||||
parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||
parser.add_argument("--runtime", default="win-x64")
|
||||
parser.add_argument("--self-contained", action="store_true")
|
||||
parser.add_argument("--skip-web-assets", action="store_true")
|
||||
parser.add_argument("--repo-root", default=None)
|
||||
parser.add_argument("--project", default=None, help="Relative/absolute path to gateway csproj")
|
||||
parser.add_argument("--web-build-dir", default=None, help="Relative path to web build assets root")
|
||||
parser.add_argument("--output-dir", default="output/webgateway")
|
||||
_ = parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||
_ = parser.add_argument("--runtime", default="win-x64")
|
||||
_ = parser.add_argument("--self-contained", action="store_true")
|
||||
_ = parser.add_argument("--skip-web-assets", action="store_true")
|
||||
_ = parser.add_argument("--repo-root", default=None)
|
||||
_ = parser.add_argument("--project", default=None, help="Relative/absolute path to gateway csproj")
|
||||
_ = parser.add_argument("--web-build-dir", default=None, help="Relative path to web build assets root")
|
||||
_ = parser.add_argument("--output-dir", default="output/webgateway")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = resolve_repo_root(args.repo_root)
|
||||
output_dir = (repo_root / args.output_dir).resolve()
|
||||
repo_root_val = cast(str | None, args.repo_root)
|
||||
repo_root = resolve_repo_root(repo_root_val)
|
||||
output_dir_val = cast(str, args.output_dir)
|
||||
output_dir = (repo_root / output_dir_val).resolve()
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if args.project:
|
||||
csproj = (repo_root / args.project).resolve()
|
||||
project_val = cast(str | None, args.project)
|
||||
if project_val:
|
||||
csproj = (repo_root / project_val).resolve()
|
||||
else:
|
||||
csproj = find_csproj_by_keyword(repo_root, ["webgateway", "gateway"])
|
||||
|
||||
if csproj is None or not csproj.exists():
|
||||
print("Could not locate web gateway project. Pass --project <path/to/project.csproj>.")
|
||||
return 2
|
||||
|
||||
publish_args = [
|
||||
"publish",
|
||||
str(csproj),
|
||||
"-c",
|
||||
args.configuration,
|
||||
"-r",
|
||||
args.runtime,
|
||||
"--self-contained",
|
||||
"true" if args.self_contained else "false",
|
||||
"-p:RestoreIgnoreFailedSources=true",
|
||||
"-p:NuGetAudit=false",
|
||||
"-o",
|
||||
str(output_dir),
|
||||
]
|
||||
code = run("dotnet", publish_args, repo_root, env=dotnet_env(repo_root))
|
||||
if code != 0:
|
||||
return code
|
||||
res: SdtResult = ensure_dotnet_publish(
|
||||
csproj=csproj,
|
||||
output_dir=output_dir,
|
||||
configuration=str(args.configuration),
|
||||
runtime=str(args.runtime),
|
||||
self_contained=bool(args.self_contained),
|
||||
single_file=False
|
||||
)
|
||||
|
||||
if res["exit_code"] != 0:
|
||||
return int(res["exit_code"])
|
||||
|
||||
if not args.skip_web_assets:
|
||||
if args.web_build_dir:
|
||||
web_build_dir = (repo_root / args.web_build_dir).resolve()
|
||||
web_build_dir_val = cast(str | None, args.web_build_dir)
|
||||
if web_build_dir_val:
|
||||
web_build_dir = (repo_root / web_build_dir_val).resolve()
|
||||
else:
|
||||
web_build_dir = next((p.parent for p in repo_root.rglob("package.json") if (p.parent / "build").exists()), None)
|
||||
if web_build_dir is not None:
|
||||
web_build_dir = web_build_dir / "build"
|
||||
# Look for recent web build output
|
||||
# (Note: rglob is costly but necessary for discovery here)
|
||||
web_pj = next((p.parent for p in repo_root.rglob("package.json") if (p.parent / "build").exists()), None)
|
||||
web_build_dir = web_pj / "build" if web_pj else None
|
||||
|
||||
if web_build_dir is None or not web_build_dir.exists():
|
||||
print("Web assets not found. Skip with --skip-web-assets or pass --web-build-dir.")
|
||||
else:
|
||||
web_out = output_dir / "wwwroot"
|
||||
web_out.mkdir(parents=True, exist_ok=True)
|
||||
for item in web_build_dir.iterdir():
|
||||
dst = web_out / item.name
|
||||
if item.is_dir():
|
||||
if dst.exists():
|
||||
shutil.rmtree(dst)
|
||||
shutil.copytree(item, dst)
|
||||
else:
|
||||
shutil.copy2(item, dst)
|
||||
print(f"Copied web assets: {web_out}")
|
||||
print(f"Copying web assets: {web_build_dir} -> {web_out}")
|
||||
shutil.copytree(web_build_dir, web_out, dirs_exist_ok=True)
|
||||
print(f"Copied web assets to {web_out}")
|
||||
|
||||
print(f"Publish completed: {output_dir}")
|
||||
return 0
|
||||
|
||||
133
Journal.DevTool/scripts/script-common.ps1
Normal file
133
Journal.DevTool/scripts/script-common.ps1
Normal file
@ -0,0 +1,133 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Legacy compatibility helper only.
|
||||
# Active SDT workflows and shell bootstrap now use Python scripts.
|
||||
|
||||
function Clear-SdtProxyEnv {
|
||||
Remove-Item Env:HTTP_PROXY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:HTTPS_PROXY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:ALL_PROXY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:http_proxy -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:https_proxy -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:all_proxy -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:GIT_HTTP_PROXY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:GIT_HTTPS_PROXY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:PIP_NO_INDEX -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function Test-SdtConfigExists {
|
||||
param([string]$Path)
|
||||
if (Test-Path (Join-Path $Path "devtool.json")) {
|
||||
return $true
|
||||
}
|
||||
$sdtConfigs = Get-ChildItem -Path $Path -Filter "sdtconfig-*.json" -File -ErrorAction SilentlyContinue
|
||||
return ($null -ne $sdtConfigs) -and ($sdtConfigs.Count -gt 0)
|
||||
}
|
||||
|
||||
function Resolve-SdtRepoRoot {
|
||||
param([string]$StartPath)
|
||||
|
||||
$candidateStarts = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($StartPath)) {
|
||||
$candidateStarts += $StartPath
|
||||
}
|
||||
$cwd = (Get-Location).Path
|
||||
if (-not [string]::IsNullOrWhiteSpace($cwd) -and ($candidateStarts -notcontains $cwd)) {
|
||||
$candidateStarts += $cwd
|
||||
}
|
||||
|
||||
$override = $env:SDT_REPO_ROOT
|
||||
if ([string]::IsNullOrWhiteSpace($override)) {
|
||||
$override = $env:JOURNAL_REPO_ROOT # backward compatibility
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($override)) {
|
||||
$overridePath = [System.IO.Path]::GetFullPath($override)
|
||||
if (Test-SdtConfigExists -Path $overridePath) {
|
||||
return $overridePath
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($start in $candidateStarts) {
|
||||
$cursor = [System.IO.Path]::GetFullPath($start)
|
||||
while (-not [string]::IsNullOrWhiteSpace($cursor)) {
|
||||
if (Test-SdtConfigExists -Path $cursor) {
|
||||
return $cursor
|
||||
}
|
||||
$parent = [System.IO.Directory]::GetParent($cursor)
|
||||
if ($null -eq $parent -or $parent.FullName -eq $cursor) {
|
||||
break
|
||||
}
|
||||
$cursor = $parent.FullName
|
||||
}
|
||||
}
|
||||
|
||||
if (Get-Command git -ErrorAction SilentlyContinue) {
|
||||
foreach ($start in $candidateStarts) {
|
||||
try {
|
||||
$gitRoot = & git -C $start rev-parse --show-toplevel 2>$null
|
||||
if ($? -and -not [string]::IsNullOrWhiteSpace($gitRoot)) {
|
||||
return [System.IO.Path]::GetFullPath($gitRoot.Trim())
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
|
||||
throw "Could not locate repository root. Ensure a project config (sdtconfig-*.json or devtool.json) exists in the project root."
|
||||
}
|
||||
|
||||
function Initialize-SdtDotnetEnv {
|
||||
param([Parameter(Mandatory = $true)][string]$RepoRoot)
|
||||
|
||||
$dotnetCliHome = Join-Path $RepoRoot ".dotnet_home"
|
||||
$nugetPackages = Join-Path $RepoRoot ".nuget\packages"
|
||||
$nugetHttpCachePath = Join-Path $RepoRoot ".nuget\http-cache"
|
||||
|
||||
$env:DOTNET_CLI_HOME = $dotnetCliHome
|
||||
$env:NUGET_PACKAGES = $nugetPackages
|
||||
$env:NUGET_HTTP_CACHE_PATH = $nugetHttpCachePath
|
||||
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = "1"
|
||||
$env:DOTNET_ADD_GLOBAL_TOOLS_TO_PATH = "0"
|
||||
$env:DOTNET_GENERATE_ASPNET_CERTIFICATE = "0"
|
||||
$env:DOTNET_CLI_TELEMETRY_OPTOUT = "1"
|
||||
$env:NUGET_CERT_REVOCATION_MODE = "offline"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $dotnetCliHome, $nugetPackages, $nugetHttpCachePath | Out-Null
|
||||
}
|
||||
|
||||
function Initialize-SdtPipEnv {
|
||||
param([Parameter(Mandatory = $true)][string]$RepoRoot)
|
||||
|
||||
$pipCacheDir = Join-Path $RepoRoot ".pip\cache"
|
||||
$pipTempDir = Join-Path $RepoRoot ".tmp\pip-temp"
|
||||
|
||||
$env:PIP_CACHE_DIR = $pipCacheDir
|
||||
$env:TEMP = $pipTempDir
|
||||
$env:TMP = $pipTempDir
|
||||
$env:PIP_DISABLE_PIP_VERSION_CHECK = "1"
|
||||
$env:PIP_DEFAULT_TIMEOUT = "30"
|
||||
$env:PIP_RETRIES = "2"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $pipCacheDir, $pipTempDir | Out-Null
|
||||
}
|
||||
|
||||
function Initialize-SdtHuggingFaceEnv {
|
||||
param([Parameter(Mandatory = $true)][string]$RepoRoot)
|
||||
|
||||
$hfHome = Join-Path $RepoRoot ".cache\huggingface"
|
||||
$hfHubCache = Join-Path $hfHome "hub"
|
||||
|
||||
$env:HF_HOME = $hfHome
|
||||
$env:HUGGINGFACE_HUB_CACHE = $hfHubCache
|
||||
$env:HF_HUB_DISABLE_SYMLINKS_WARNING = "1"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $hfHubCache | Out-Null
|
||||
}
|
||||
|
||||
# Backward-compatible aliases (legacy script calls)
|
||||
Set-Alias -Name Clear-JournalProxyEnv -Value Clear-SdtProxyEnv -Scope Script
|
||||
Set-Alias -Name Resolve-JournalRepoRoot -Value Resolve-SdtRepoRoot -Scope Script
|
||||
Set-Alias -Name Initialize-JournalDotnetEnv -Value Initialize-SdtDotnetEnv -Scope Script
|
||||
Set-Alias -Name Initialize-JournalPipEnv -Value Initialize-SdtPipEnv -Scope Script
|
||||
Set-Alias -Name Initialize-JournalHuggingFaceEnv -Value Initialize-SdtHuggingFaceEnv -Scope Script
|
||||
@ -6,184 +6,50 @@ import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Dict, Iterable, List, Sequence
|
||||
import time
|
||||
from typing import Any, Iterable, Optional, Sequence
|
||||
|
||||
# --- Domain: SDT Types ---
|
||||
# SDT Normalized Result Object
|
||||
# result["exit_code"]: int
|
||||
# result["status"]: "ok" | "failed" | "skipped"
|
||||
# result["elapsed_seconds"]: float
|
||||
# result["failure_reason"]: Optional[str]
|
||||
# result["skip_reason"]: Optional[str]
|
||||
SdtResult = dict[str, Any]
|
||||
|
||||
PROXY_VARS = [
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"ALL_PROXY",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"all_proxy",
|
||||
"GIT_HTTP_PROXY",
|
||||
"GIT_HTTPS_PROXY",
|
||||
"PIP_NO_INDEX",
|
||||
"HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy",
|
||||
"GIT_HTTP_PROXY", "GIT_HTTPS_PROXY", "PIP_NO_INDEX"
|
||||
]
|
||||
|
||||
# --- Domain: FS Utilities ---
|
||||
|
||||
def resolve_repo_root(start: str | None = None) -> pathlib.Path:
|
||||
base = pathlib.Path(start or os.getcwd()).resolve()
|
||||
def sha256_files(paths: Iterable[pathlib.Path]) -> str:
|
||||
h = hashlib.sha256()
|
||||
for p in sorted(paths):
|
||||
if p.exists(): h.update(p.read_bytes())
|
||||
return h.hexdigest()
|
||||
|
||||
# Preferred marker for SDT-managed projects.
|
||||
for cur in [base, *base.parents]:
|
||||
cfg = cur / "devtool.json"
|
||||
if cfg.exists():
|
||||
hints = load_project_root_hints(cur)
|
||||
if not hints:
|
||||
return cur
|
||||
if any(_hint_matches(cur, hint) for hint in hints):
|
||||
return cur
|
||||
|
||||
# Fall back to git root when available.
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
["git", "-C", str(base), "rev-parse", "--show-toplevel"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if proc.returncode == 0:
|
||||
git_root = proc.stdout.strip()
|
||||
if git_root:
|
||||
return pathlib.Path(git_root).resolve()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return base
|
||||
|
||||
|
||||
def load_project_root_hints(repo_root: pathlib.Path) -> list[str]:
|
||||
cfg = repo_root / "devtool.json"
|
||||
if not cfg.exists():
|
||||
return []
|
||||
try:
|
||||
data = json.loads(cfg.read_text(encoding="utf-8"))
|
||||
hints = data.get("project", {}).get("rootHints", [])
|
||||
return [str(x) for x in hints if isinstance(x, str) and x.strip()]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def ensure_dirs(paths: List[pathlib.Path]) -> None:
|
||||
def first_existing(paths: Iterable[pathlib.Path]) -> Optional[pathlib.Path]:
|
||||
for p in paths:
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
if p.exists(): return p
|
||||
return None
|
||||
|
||||
def newest_file(search_root: pathlib.Path, pattern: str) -> Optional[pathlib.Path]:
|
||||
hits = sorted(search_root.glob(pattern), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
return hits[0] if hits else None
|
||||
|
||||
def clean_proxy_env(env: Dict[str, str]) -> None:
|
||||
for k in PROXY_VARS:
|
||||
env.pop(k, None)
|
||||
|
||||
|
||||
def dotnet_env(repo_root: pathlib.Path) -> Dict[str, str]:
|
||||
env = dict(os.environ)
|
||||
clean_proxy_env(env)
|
||||
dotnet_cli_home = repo_root / ".dotnet_home"
|
||||
nuget_packages = repo_root / ".nuget" / "packages"
|
||||
nuget_http_cache = repo_root / ".nuget" / "http-cache"
|
||||
ensure_dirs([dotnet_cli_home, nuget_packages, nuget_http_cache])
|
||||
env["DOTNET_CLI_HOME"] = str(dotnet_cli_home)
|
||||
env["NUGET_PACKAGES"] = str(nuget_packages)
|
||||
env["NUGET_HTTP_CACHE_PATH"] = str(nuget_http_cache)
|
||||
env["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1"
|
||||
env["DOTNET_ADD_GLOBAL_TOOLS_TO_PATH"] = "0"
|
||||
env["DOTNET_GENERATE_ASPNET_CERTIFICATE"] = "0"
|
||||
env["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1"
|
||||
env["NUGET_CERT_REVOCATION_MODE"] = "offline"
|
||||
return env
|
||||
|
||||
|
||||
def pip_env(repo_root: pathlib.Path) -> Dict[str, str]:
|
||||
env = dict(os.environ)
|
||||
clean_proxy_env(env)
|
||||
pip_cache = repo_root / ".pip" / "cache"
|
||||
pip_tmp = repo_root / ".tmp" / "pip-temp"
|
||||
ensure_dirs([pip_cache, pip_tmp])
|
||||
env["PIP_CACHE_DIR"] = str(pip_cache)
|
||||
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
||||
env["PIP_DEFAULT_TIMEOUT"] = "30"
|
||||
env["PIP_RETRIES"] = "2"
|
||||
env["TEMP"] = str(pip_tmp)
|
||||
env["TMP"] = str(pip_tmp)
|
||||
return env
|
||||
|
||||
|
||||
def run(command: str, args: List[str], cwd: pathlib.Path, env: Dict[str, str] | None = None) -> int:
|
||||
resolved = resolve_command(command)
|
||||
try:
|
||||
proc = subprocess.run([resolved, *args], cwd=str(cwd), env=env, check=False)
|
||||
return proc.returncode
|
||||
except FileNotFoundError:
|
||||
print(f"Command not found: {resolved}", file=sys.stderr)
|
||||
return 127
|
||||
|
||||
|
||||
def run_capture(command: str, args: Sequence[str], cwd: pathlib.Path, env: Dict[str, str] | None = None) -> tuple[int, str, str]:
|
||||
resolved = resolve_command(command)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[resolved, *args],
|
||||
cwd=str(cwd),
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
return proc.returncode, proc.stdout, proc.stderr
|
||||
except FileNotFoundError:
|
||||
return 127, "", f"Command not found: {resolved}"
|
||||
|
||||
|
||||
def resolve_command(command: str) -> str:
|
||||
if not command:
|
||||
return command
|
||||
|
||||
if os.name != "nt":
|
||||
return command
|
||||
|
||||
if any(sep in command for sep in ("\\", "/")):
|
||||
return command
|
||||
|
||||
if pathlib.Path(command).suffix:
|
||||
found = shutil.which(command)
|
||||
return found or command
|
||||
|
||||
candidates = []
|
||||
lowered = command.lower()
|
||||
if lowered in ("npm", "npx", "pnpm", "yarn", "tauri"):
|
||||
candidates.extend([f"{command}.cmd", f"{command}.exe", f"{command}.bat", command])
|
||||
else:
|
||||
candidates.append(command)
|
||||
|
||||
for c in candidates:
|
||||
found = _which_windows(c)
|
||||
if found:
|
||||
name = pathlib.Path(found).name.lower()
|
||||
if name in ("npm", "npx", "pnpm", "yarn", "tauri"):
|
||||
shim = pathlib.Path(found).with_name(name + ".cmd")
|
||||
if shim.exists():
|
||||
return str(shim)
|
||||
return found
|
||||
|
||||
if lowered in ("npm", "npx", "pnpm", "yarn"):
|
||||
node = _which_windows("node.exe") or _which_windows("node")
|
||||
if node:
|
||||
node_dir = pathlib.Path(node).parent
|
||||
shim = node_dir / f"{lowered}.cmd"
|
||||
if shim.exists():
|
||||
return str(shim)
|
||||
|
||||
return candidates[-1]
|
||||
|
||||
def ensure_dirs(paths: list[pathlib.Path]) -> None:
|
||||
for p in paths: p.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _hint_matches(root: pathlib.Path, hint: str) -> bool:
|
||||
h = hint.strip()
|
||||
if not h:
|
||||
return False
|
||||
|
||||
try:
|
||||
has_glob = any(ch in h for ch in ("*", "?", "["))
|
||||
if has_glob:
|
||||
# Match both anywhere in root and directly at root-level for common hints like "*.sln".
|
||||
if any(root.glob(h)):
|
||||
return True
|
||||
return any(root.rglob(h))
|
||||
@ -192,171 +58,246 @@ def _hint_matches(root: pathlib.Path, hint: str) -> bool:
|
||||
if marker.exists():
|
||||
return True
|
||||
|
||||
# If hint is just a filename marker, look bounded in tree.
|
||||
if not any(sep in h for sep in ("\\", "/")):
|
||||
# If hint is a plain filename marker, allow bounded search in root tree.
|
||||
if not any(sep in h for sep in ("/", "\\")):
|
||||
return any(p.name == h for p in root.rglob(h))
|
||||
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
# --- Domain: Project Discovery ---
|
||||
|
||||
def _expand_windows_path_segment(segment: str) -> str:
|
||||
expanded = segment
|
||||
# Expand %VAR% tokens repeatedly for nested references.
|
||||
for _ in range(4):
|
||||
next_value = os.path.expandvars(expanded)
|
||||
if next_value == expanded:
|
||||
break
|
||||
expanded = next_value
|
||||
return expanded
|
||||
def resolve_repo_root(start: str | None = None) -> pathlib.Path:
|
||||
base = pathlib.Path(start or os.getcwd()).resolve()
|
||||
for cur in [base, *base.parents]:
|
||||
sdt_configs = list(cur.glob("sdtconfig-*.json"))
|
||||
cfg = sdt_configs[0] if sdt_configs else (cur / "devtool.json")
|
||||
if cfg.exists():
|
||||
hints = load_project_root_hints(cur, cfg)
|
||||
if not hints or any(_hint_matches(cur, hint) for hint in hints): return cur
|
||||
try:
|
||||
proc = subprocess.run(["git", "-C", str(base), "rev-parse", "--show-toplevel"], capture_output=True, text=True, check=False)
|
||||
if proc.returncode == 0 and proc.stdout.strip(): return pathlib.Path(proc.stdout.strip()).resolve()
|
||||
except: pass
|
||||
return base
|
||||
|
||||
def load_project_root_hints(repo_root: pathlib.Path, cfg: pathlib.Path | None = None) -> list[str]:
|
||||
if cfg is None:
|
||||
sdt_configs = list(repo_root.glob("sdtconfig-*.json"))
|
||||
cfg = sdt_configs[0] if sdt_configs else (repo_root / "devtool.json")
|
||||
if not cfg.exists(): return []
|
||||
try:
|
||||
data = json.loads(cfg.read_text(encoding="utf-8"))
|
||||
hints = data.get("project", {}).get("rootHints", [])
|
||||
return [str(x) for x in hints if isinstance(x, str) and x.strip()]
|
||||
except: return []
|
||||
|
||||
def _which_windows(command: str) -> str | None:
|
||||
found = shutil.which(command)
|
||||
if found:
|
||||
return found
|
||||
|
||||
if os.name != "nt":
|
||||
def _resolve_project_config_path(repo_root: pathlib.Path) -> Optional[pathlib.Path]:
|
||||
sdt_configs = sorted(repo_root.glob("sdtconfig-*.json"), key=lambda p: p.name.lower())
|
||||
if sdt_configs:
|
||||
return sdt_configs[0]
|
||||
legacy = repo_root / "devtool.json"
|
||||
if legacy.exists():
|
||||
return legacy
|
||||
return None
|
||||
|
||||
path_value = os.environ.get("PATH", "")
|
||||
pathext = os.environ.get("PATHEXT", ".COM;.EXE;.BAT;.CMD")
|
||||
exts = [e.lower() for e in pathext.split(";") if e]
|
||||
|
||||
has_ext = pathlib.Path(command).suffix != ""
|
||||
names = [command] if has_ext else [command, *(command + e.lower() for e in exts)]
|
||||
|
||||
for raw_segment in path_value.split(os.pathsep):
|
||||
segment = _expand_windows_path_segment(raw_segment.strip())
|
||||
if not segment:
|
||||
continue
|
||||
base = pathlib.Path(segment)
|
||||
for name in names:
|
||||
candidate = base / name
|
||||
if candidate.exists():
|
||||
return str(candidate)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def sha256_files(paths: Iterable[pathlib.Path]) -> str:
|
||||
h = hashlib.sha256()
|
||||
for p in paths:
|
||||
if not p.exists():
|
||||
continue
|
||||
h.update(p.read_bytes())
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def first_existing(paths: Iterable[pathlib.Path]) -> pathlib.Path | None:
|
||||
for p in paths:
|
||||
if p.exists():
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def find_csproj(repo_root: pathlib.Path, hints: Sequence[str] | None = None) -> pathlib.Path | None:
|
||||
if hints:
|
||||
for hint in hints:
|
||||
candidate = (repo_root / hint).resolve()
|
||||
if candidate.exists() and candidate.suffix.lower() == ".csproj":
|
||||
return candidate
|
||||
|
||||
csprojs = sorted(repo_root.rglob("*.csproj"))
|
||||
if not csprojs:
|
||||
return None
|
||||
if len(csprojs) == 1:
|
||||
return csprojs[0]
|
||||
return None
|
||||
|
||||
|
||||
def find_csproj_by_keyword(repo_root: pathlib.Path, keywords: Sequence[str]) -> pathlib.Path | None:
|
||||
kws = [k.lower() for k in keywords]
|
||||
matches: list[pathlib.Path] = []
|
||||
for p in repo_root.rglob("*.csproj"):
|
||||
text = str(p).lower()
|
||||
if any(k in text for k in kws):
|
||||
matches.append(p)
|
||||
if len(matches) == 1:
|
||||
return matches[0]
|
||||
return None
|
||||
|
||||
|
||||
def find_node_app_root(repo_root: pathlib.Path, preferred: str | None = None) -> pathlib.Path | None:
|
||||
def _read_package_json(package_json: pathlib.Path) -> dict | None:
|
||||
if not package_json.exists():
|
||||
def load_node_working_dir(repo_root: pathlib.Path) -> Optional[str]:
|
||||
cfg = _resolve_project_config_path(repo_root)
|
||||
if cfg is None:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(package_json.read_text(encoding="utf-8"))
|
||||
return data if isinstance(data, dict) else None
|
||||
except Exception:
|
||||
data = json.loads(cfg.read_text(encoding="utf-8"))
|
||||
node = data.get("toolchains", {}).get("node", {})
|
||||
value = node.get("workingDir")
|
||||
if isinstance(value, str) and value.strip():
|
||||
return value.strip()
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _has_scripts(package_json: pathlib.Path, names: Sequence[str]) -> bool:
|
||||
data = _read_package_json(package_json)
|
||||
if not data:
|
||||
return False
|
||||
scripts = data.get("scripts")
|
||||
if not isinstance(scripts, dict):
|
||||
return False
|
||||
for name in names:
|
||||
value = scripts.get(name)
|
||||
if isinstance(value, str) and value.strip():
|
||||
return True
|
||||
return False
|
||||
def find_csproj(repo_root: pathlib.Path, hints: Sequence[str] | None = None) -> Optional[pathlib.Path]:
|
||||
if hints:
|
||||
for h in hints:
|
||||
p = (repo_root / h).resolve()
|
||||
if p.exists() and p.suffix == ".csproj": return p
|
||||
hits = [h for h in repo_root.rglob("*.csproj") if not any(x in h.parts for x in [".git", "node_modules", "bin", "obj"])]
|
||||
return hits[0] if len(hits) == 1 else None
|
||||
|
||||
def _is_tauri_root(candidate_dir: pathlib.Path) -> bool:
|
||||
return (candidate_dir / "src-tauri" / "tauri.conf.json").exists() or (candidate_dir / "tauri.conf.json").exists()
|
||||
def find_csproj_by_keyword(repo_root: pathlib.Path, keywords: Sequence[str]) -> Optional[pathlib.Path]:
|
||||
hits = [h for h in repo_root.rglob("*.csproj") if not any(x in h.parts for x in [".git", "node_modules", "bin", "obj"])]
|
||||
for kw in keywords:
|
||||
matches = [h for h in hits if kw.lower() in h.name.lower()]
|
||||
if len(matches) == 1: return matches[0]
|
||||
return None
|
||||
|
||||
def _iter_package_jsons() -> list[pathlib.Path]:
|
||||
excluded = {"node_modules", "dist", "build", ".git", ".sdt", ".venv", "venv", "bin", "obj"}
|
||||
found: list[pathlib.Path] = []
|
||||
for current_root, dirs, files in os.walk(repo_root):
|
||||
dirs[:] = [d for d in dirs if d not in excluded]
|
||||
if "package.json" in files:
|
||||
found.append(pathlib.Path(current_root) / "package.json")
|
||||
found.sort(key=lambda p: len(p.parts))
|
||||
return found
|
||||
def find_node_app_root(repo_root: pathlib.Path, preferred: str | None = None) -> Optional[pathlib.Path]:
|
||||
def _read_pj(p: pathlib.Path):
|
||||
try: return json.loads(p.read_text(encoding="utf-8"))
|
||||
except: return None
|
||||
def _has_scr(p: pathlib.Path, names: Sequence[str]):
|
||||
src = _read_pj(p)
|
||||
return any(src.get("scripts", {}).get(n) for n in names) if src else False
|
||||
def _is_tauri(d: pathlib.Path): return (d / "src-tauri" / "tauri.conf.json").exists() or (d / "tauri.conf.json").exists()
|
||||
def _iter_pj():
|
||||
excluded = {".git", "node_modules", ".sdt", "dist", "build", ".venv", "venv", "bin", "obj"}
|
||||
for p in repo_root.rglob("package.json"):
|
||||
if any(x in p.parts for x in excluded):
|
||||
continue
|
||||
yield p
|
||||
|
||||
if not preferred:
|
||||
preferred = load_node_working_dir(repo_root)
|
||||
if preferred:
|
||||
p = (repo_root / preferred).resolve()
|
||||
package_json = p / "package.json"
|
||||
if package_json.exists():
|
||||
# Keep explicit preferred root only when it appears runnable for node workflows.
|
||||
if _is_tauri_root(p) or _has_scripts(package_json, ("build", "dev", "start", "test", "tauri")):
|
||||
p = p.parent if p.is_file() else p
|
||||
if (p / "package.json").exists():
|
||||
return p
|
||||
|
||||
package_files = _iter_package_jsons()
|
||||
if not package_files:
|
||||
tauri = [p.parent for p in _iter_pj() if _is_tauri(p.parent)]
|
||||
if len(tauri) == 1: return tauri[0]
|
||||
if len(tauri) > 1:
|
||||
tauri = sorted(set(tauri), key=lambda d: (len(d.parts), str(d).lower()))
|
||||
return tauri[0]
|
||||
scripts = [p.parent for p in _iter_pj() if _has_scr(p, ["tauri", "build"])]
|
||||
if len(scripts) == 1: return scripts[0]
|
||||
if len(scripts) > 1:
|
||||
scripts = sorted(set(scripts), key=lambda d: (len(d.parts), str(d).lower()))
|
||||
return scripts[0]
|
||||
|
||||
all_pj = [p.parent for p in _iter_pj()]
|
||||
return all_pj[0] if len(all_pj) == 1 else None
|
||||
|
||||
# --- Domain: Environment Setup ---
|
||||
|
||||
def clean_proxy_env(env: dict[str, str]) -> None:
|
||||
for k in PROXY_VARS: env.pop(k, None)
|
||||
|
||||
def dotnet_env(repo_root: pathlib.Path) -> dict[str, str]:
|
||||
env = dict(os.environ)
|
||||
clean_proxy_env(env)
|
||||
h, p, c = repo_root/".dotnet_home", repo_root/".nuget"/"packages", repo_root/".nuget"/"http-cache"
|
||||
ensure_dirs([h, p, c])
|
||||
env.update({"DOTNET_CLI_HOME":str(h), "NUGET_PACKAGES":str(p), "NUGET_HTTP_CACHE_PATH":str(c),
|
||||
"DOTNET_SKIP_FIRST_TIME_EXPERIENCE":"1", "DOTNET_ADD_GLOBAL_TOOLS_TO_PATH":"0",
|
||||
"DOTNET_GENERATE_ASPNET_CERTIFICATE":"0",
|
||||
"DOTNET_CLI_TELEMETRY_OPTOUT":"1", "NUGET_CERT_REVOCATION_MODE":"offline"})
|
||||
return env
|
||||
|
||||
def pip_env(repo_root: pathlib.Path) -> dict[str, str]:
|
||||
env = dict(os.environ)
|
||||
clean_proxy_env(env)
|
||||
c, t = repo_root/".pip"/"cache", repo_root/".tmp"/"pip-temp"
|
||||
ensure_dirs([c, t])
|
||||
env.update({"PIP_CACHE_DIR":str(c), "PIP_DISABLE_PIP_VERSION_CHECK":"1", "PIP_DEFAULT_TIMEOUT":"30", "PIP_RETRIES":"2", "TEMP":str(t), "TMP":str(t)})
|
||||
return env
|
||||
|
||||
# --- Domain: Process Execution ---
|
||||
|
||||
def resolve_command(command: str) -> str:
|
||||
if not command or os.name != "nt" or any(s in command for s in ("\\", "/")): return command
|
||||
if pathlib.Path(command).suffix: return shutil.which(command) or command
|
||||
low = command.lower()
|
||||
cands = [f"{command}.cmd", f"{command}.exe", f"{command}.bat", command] if low in ("npm", "npx", "pnpm", "yarn", "tauri") else [command]
|
||||
for c in cands:
|
||||
found = _which_windows(c)
|
||||
if found:
|
||||
if pathlib.Path(found).name.lower() in ("npm", "npx", "pnpm", "yarn", "tauri"):
|
||||
shim = pathlib.Path(found).with_name(pathlib.Path(found).name.lower() + ".cmd")
|
||||
if shim.exists(): return str(shim)
|
||||
return found
|
||||
if low in ("npm", "npx", "pnpm", "yarn"):
|
||||
node = _which_windows("node.exe") or _which_windows("node")
|
||||
if node and (pathlib.Path(node).parent / f"{low}.cmd").exists(): return str(pathlib.Path(node).parent / f"{low}.cmd")
|
||||
return cands[-1]
|
||||
|
||||
def _which_windows(command: str) -> Optional[str]:
|
||||
found = shutil.which(command)
|
||||
if found: return str(pathlib.Path(found).resolve())
|
||||
for p in ["C:\\Program Files\\dotnet", "C:\\Program Files\\nodejs", "C:\\Program Files\\Git\\bin", "C:\\Program Files\\Git\\usr\\bin"]:
|
||||
if (pathlib.Path(p) / command).exists(): return str((pathlib.Path(p) / command).resolve())
|
||||
return None
|
||||
|
||||
# Strong preference: a tauri app root with tauri config and package.json.
|
||||
tauri_candidates = [p.parent for p in package_files if _is_tauri_root(p.parent)]
|
||||
if len(tauri_candidates) == 1:
|
||||
return tauri_candidates[0]
|
||||
if len(tauri_candidates) > 1:
|
||||
tauri_candidates.sort(key=lambda p: len(p.parts))
|
||||
return tauri_candidates[0]
|
||||
def run(command: str, args: list[str], cwd: pathlib.Path, env: dict[str, str] | None = None) -> int:
|
||||
try:
|
||||
proc = subprocess.run([resolve_command(command), *args], cwd=str(cwd), env=env, check=False)
|
||||
return proc.returncode
|
||||
except: return 127
|
||||
|
||||
runnable_candidates = [
|
||||
p.parent for p in package_files if _has_scripts(p, ("build", "dev", "start", "test", "tauri"))
|
||||
]
|
||||
if len(runnable_candidates) == 1:
|
||||
return runnable_candidates[0]
|
||||
if len(runnable_candidates) > 1:
|
||||
runnable_candidates.sort(key=lambda p: len(p.parts))
|
||||
return runnable_candidates[0]
|
||||
def run_capture(command: str, args: Sequence[str], cwd: pathlib.Path, env: dict[str, str] | None = None) -> tuple[int, str, str]:
|
||||
try:
|
||||
proc = subprocess.run([resolve_command(command), *args], cwd=str(cwd), env=env, capture_output=True, text=True, check=False, encoding="utf-8", errors="replace")
|
||||
return proc.returncode, proc.stdout, proc.stderr
|
||||
except: return 127, "", "Command not found"
|
||||
|
||||
# As a last fallback, return unique package root only.
|
||||
if len(package_files) == 1:
|
||||
return package_files[0].parent
|
||||
return None
|
||||
def _safe_stream_write(stream: Any, text: str) -> None:
|
||||
if not text:
|
||||
return
|
||||
try:
|
||||
stream.write(text)
|
||||
return
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
|
||||
encoding = getattr(stream, "encoding", None) or "utf-8"
|
||||
data = text.encode(encoding, errors="replace")
|
||||
buffer = getattr(stream, "buffer", None)
|
||||
if buffer is not None:
|
||||
buffer.write(data)
|
||||
buffer.flush()
|
||||
return
|
||||
|
||||
def newest_file(search_root: pathlib.Path, pattern: str) -> pathlib.Path | None:
|
||||
if not search_root.exists():
|
||||
return None
|
||||
files = [p for p in search_root.rglob(pattern) if p.is_file() and "\\obj\\" not in str(p).replace("/", "\\")]
|
||||
if not files:
|
||||
return None
|
||||
files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
return files[0]
|
||||
# Last resort when no binary buffer is exposed.
|
||||
stream.write(data.decode(encoding, errors="replace"))
|
||||
|
||||
def run_step_with_summary(label: str, command: str, args: Sequence[str], cwd: pathlib.Path, env: dict[str, str] | None = None) -> SdtResult:
|
||||
print(f"\n> {label}\n$ {command} {' '.join(args)}")
|
||||
start = time.time()
|
||||
code, out, err = run_capture(command, args, cwd, env)
|
||||
elapsed = round(time.time() - start, 3)
|
||||
if out: _safe_stream_write(sys.stdout, out)
|
||||
if err: _safe_stream_write(sys.stderr, err)
|
||||
return {"exit_code": code, "status": "ok" if code == 0 else "failed", "elapsed_seconds": elapsed, "stdout": out, "stderr": err, "failure_reason": None, "skip_reason": None}
|
||||
|
||||
# --- Domain: High-Level Build Logic ---
|
||||
|
||||
def ensure_dotnet_publish(csproj: pathlib.Path, output_dir: pathlib.Path, configuration: str = "Release", runtime: str = "win-x64", single_file: bool = False, self_contained: bool = True) -> SdtResult:
|
||||
repo_root = resolve_repo_root(str(csproj.parent))
|
||||
env = dotnet_env(repo_root)
|
||||
args = ["publish", str(csproj), "-c", configuration, "-r", runtime, "--self-contained", "true" if self_contained else "false",
|
||||
f"-p:PublishSingleFile={'true' if single_file else 'false'}", "-p:RestoreIgnoreFailedSources=true", "-p:NuGetAudit=false", "-o", str(output_dir)]
|
||||
if single_file: args.append("-p:IncludeNativeLibrariesForSelfExtract=true")
|
||||
res = run_step_with_summary(f"Publishing {csproj.name}", "dotnet", args, repo_root, env)
|
||||
if res["exit_code"] != 0 and single_file:
|
||||
comb = (res["stdout"] + res["stderr"]).lower()
|
||||
if "generatebundle" in comb and "same bundlerelativepath" in comb:
|
||||
print("Duplicate bundle entries detected; retrying without single-file optimization...")
|
||||
retry = [a if "PublishSingleFile=true" not in a else "-p:PublishSingleFile=false" for a in args]
|
||||
res = run_step_with_summary(f"Publishing {csproj.name} (retry)", "dotnet", retry, repo_root, env)
|
||||
if res["exit_code"] != 0:
|
||||
comb = (res["stdout"] + res["stderr"]).lower()
|
||||
if "netsdk1152" in comb and "multiple publish output files with the same relative path" in comb:
|
||||
print("Duplicate publish output files detected (NETSDK1152); retrying with ErrorOnDuplicatePublishOutputFiles=false...")
|
||||
retry = list(args)
|
||||
if not any("ErrorOnDuplicatePublishOutputFiles" in a for a in retry):
|
||||
retry.append("-p:ErrorOnDuplicatePublishOutputFiles=false")
|
||||
res = run_step_with_summary(f"Publishing {csproj.name} (dedupe retry)", "dotnet", retry, repo_root, env)
|
||||
return res
|
||||
|
||||
def ensure_npm_build(app_root: pathlib.Path, target: str = "web", configuration: str = "Release", tauri_bundles: str = "none") -> SdtResult:
|
||||
pj, lock = app_root / "package.json", first_existing([app_root / "package-lock.json", app_root / "npm-shrinkwrap.json"])
|
||||
nm = app_root / "node_modules"
|
||||
h_file, exp_h = nm / ".sdt-deps.sha256", sha256_files([pj, lock] if lock else [pj])
|
||||
should_inst = not nm.exists() or not h_file.exists() or h_file.read_text(encoding="utf-8").strip() != exp_h
|
||||
if should_inst:
|
||||
args = ["ci", "--no-audit", "--fund=false"] if lock else ["install", "--no-audit", "--fund=false"]
|
||||
res = run_step_with_summary("Installing NPM dependencies", "npm", args, app_root)
|
||||
if res["exit_code"] != 0 and lock and args[0] == "ci":
|
||||
res = run_step_with_summary("Installing NPM dependencies (retry)", "npm", ["install", "--no-audit", "--fund=false"], app_root)
|
||||
if res["exit_code"] != 0: return res
|
||||
ensure_dirs([nm]); h_file.write_text(exp_h, encoding="utf-8")
|
||||
if target == "web": return run_step_with_summary(f"Building Web ({configuration})", "npm", ["run", "build"], app_root)
|
||||
t_cmd = ["run", "tauri", "build"]
|
||||
tail = (["--no-bundle"] if tauri_bundles == "none" else ["--bundles", tauri_bundles]) + (["--debug"] if configuration == "Debug" else [])
|
||||
if tail: t_cmd.extend(["--", *tail])
|
||||
return run_step_with_summary(f"Building Tauri ({configuration})", "npm", t_cmd, app_root)
|
||||
|
||||
@ -4,7 +4,7 @@ import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from script_common import newest_file, resolve_repo_root
|
||||
from script_common import newest_file, resolve_repo_root # type: ignore
|
||||
|
||||
|
||||
def copy_tree_contents(src: Path, dst: Path) -> None:
|
||||
@ -21,55 +21,79 @@ def copy_tree_contents(src: Path, dst: Path) -> None:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Sync newest built assets into output folder")
|
||||
parser.add_argument("--repo-root", default=None)
|
||||
parser.add_argument("--output-dir", default="output")
|
||||
parser.add_argument("--web-build-dir", default=None, help="Path to web build output")
|
||||
parser.add_argument("--sidecar-bin-dir", default=None, help="Path to sidecar bin root")
|
||||
parser.add_argument("--gateway-bin-dir", default=None, help="Path to gateway bin root")
|
||||
parser.add_argument("--tauri-target-dir", default=None, help="Path to tauri target root")
|
||||
_ = parser.add_argument("--repo-root", default=None)
|
||||
_ = parser.add_argument("--output-dir", default="output")
|
||||
_ = parser.add_argument("--web-build-dir", default=None, help="Path to web build output")
|
||||
_ = parser.add_argument("--sidecar-bin-dir", default=None, help="Path to sidecar bin root")
|
||||
_ = parser.add_argument("--gateway-bin-dir", default=None, help="Path to gateway bin root")
|
||||
_ = parser.add_argument("--tauri-target-dir", default=None, help="Path to tauri target root")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = resolve_repo_root(args.repo_root)
|
||||
output_dir = (repo_root / args.output_dir).resolve()
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
web_build = (repo_root / args.web_build_dir).resolve() if args.web_build_dir else None
|
||||
web_build_dir_val = args.web_build_dir
|
||||
web_build = (repo_root / web_build_dir_val).resolve() if web_build_dir_val else None
|
||||
if web_build is None:
|
||||
web_build = next((p for p in repo_root.rglob("build") if (p.parent / "package.json").exists()), None)
|
||||
|
||||
if web_build is not None and web_build.exists():
|
||||
web_out = output_dir / "webgateway" / "wwwroot"
|
||||
copy_tree_contents(web_build, web_out)
|
||||
print(f"Synced web assets -> {web_out}")
|
||||
|
||||
sidecar_bin = (repo_root / args.sidecar_bin_dir).resolve() if args.sidecar_bin_dir else None
|
||||
sidecar_bin_dir_val = args.sidecar_bin_dir
|
||||
sidecar_bin = (repo_root / sidecar_bin_dir_val).resolve() if sidecar_bin_dir_val else None
|
||||
if sidecar_bin is None:
|
||||
sidecar_proj = next((p.parent for p in repo_root.rglob("*.csproj") if "sidecar" in str(p).lower()), None)
|
||||
sidecar_bin = sidecar_proj / "bin" if sidecar_proj else None
|
||||
|
||||
if sidecar_bin is not None:
|
||||
sidecar_pattern = "*.exe" if os.name == "nt" else "*"
|
||||
sidecar_exe = newest_file(sidecar_bin, sidecar_pattern)
|
||||
sidecar_exe = None
|
||||
if os.name == "nt":
|
||||
sidecar_exe = newest_file(sidecar_bin, "*.exe")
|
||||
else:
|
||||
candidates = [p for p in sidecar_bin.rglob("*") if p.is_file() and not p.suffix and os.access(p, os.X_OK)]
|
||||
sidecar_exe = max(candidates, key=lambda p: p.stat().st_mtime) if candidates else None
|
||||
|
||||
if sidecar_exe is not None:
|
||||
copy_tree_contents(sidecar_exe.parent, output_dir)
|
||||
print(f"Synced sidecar -> {output_dir}")
|
||||
|
||||
gateway_bin = (repo_root / args.gateway_bin_dir).resolve() if args.gateway_bin_dir else None
|
||||
gateway_bin_dir_val = args.gateway_bin_dir
|
||||
gateway_bin = (repo_root / gateway_bin_dir_val).resolve() if gateway_bin_dir_val else None
|
||||
if gateway_bin is None:
|
||||
gateway_proj = next((p.parent for p in repo_root.rglob("*.csproj") if "gateway" in str(p).lower()), None)
|
||||
gateway_bin = gateway_proj / "bin" if gateway_proj else None
|
||||
|
||||
if gateway_bin is not None:
|
||||
gateway_pattern = "*.exe" if os.name == "nt" else "*"
|
||||
gw_exe = newest_file(gateway_bin, gateway_pattern)
|
||||
gw_exe = None
|
||||
if os.name == "nt":
|
||||
gw_exe = newest_file(gateway_bin, "*.exe")
|
||||
else:
|
||||
candidates = [p for p in gateway_bin.rglob("*") if p.is_file() and not p.suffix and os.access(p, os.X_OK)]
|
||||
gw_exe = max(candidates, key=lambda p: p.stat().st_mtime) if candidates else None
|
||||
|
||||
if gw_exe is not None:
|
||||
gw_out = output_dir / "webgateway"
|
||||
copy_tree_contents(gw_exe.parent, gw_out)
|
||||
print(f"Synced gateway -> {gw_out}")
|
||||
|
||||
tauri_target = (repo_root / args.tauri_target_dir).resolve() if args.tauri_target_dir else None
|
||||
tauri_target_dir_val = args.tauri_target_dir
|
||||
tauri_target = (repo_root / tauri_target_dir_val).resolve() if tauri_target_dir_val else None
|
||||
if tauri_target is None:
|
||||
tauri_target = next((p for p in repo_root.rglob("src-tauri") if (p / "target").exists()), None)
|
||||
tauri_target = tauri_target / "target" if tauri_target else None
|
||||
tauri_src = next((p for p in repo_root.rglob("src-tauri") if (p / "target").exists()), None)
|
||||
tauri_target = tauri_src / "target" if tauri_src else None
|
||||
|
||||
if tauri_target is not None:
|
||||
app_exe = None
|
||||
if os.name == "nt":
|
||||
app_exe = newest_file(tauri_target, "*.exe")
|
||||
else:
|
||||
candidates = [p for p in tauri_target.rglob("*") if p.is_file() and not p.suffix and os.access(p, os.X_OK)]
|
||||
app_exe = max(candidates, key=lambda p: p.stat().st_mtime) if candidates else None
|
||||
|
||||
if app_exe is not None:
|
||||
shutil.copy2(app_exe, output_dir / app_exe.name)
|
||||
print(f"Synced desktop app ({app_exe.name}) -> {output_dir}")
|
||||
|
||||
@ -5,23 +5,23 @@ import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
from typing import Any, Optional, Sequence
|
||||
from script_common import resolve_command, resolve_repo_root
|
||||
|
||||
|
||||
def load_config(project_root: pathlib.Path) -> dict:
|
||||
config_path = project_root / "devtool.json"
|
||||
def load_config(project_root: pathlib.Path) -> dict[str, Any]:
|
||||
sdt_configs = list(project_root.glob("sdtconfig-*.json"))
|
||||
config_path = sdt_configs[0] if sdt_configs else (project_root / "devtool.json")
|
||||
if not config_path.exists():
|
||||
raise FileNotFoundError(f"devtool.json not found at: {config_path}")
|
||||
raise FileNotFoundError(f"Project config not found at: {config_path}")
|
||||
return json.loads(config_path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def iter_workflows(config: dict, selected: Optional[set[str]]) -> List[dict]:
|
||||
def iter_workflows(config: dict[str, Any], selected: Optional[set[str]]) -> list[dict[str, Any]]:
|
||||
workflows = config.get("workflows", [])
|
||||
if not isinstance(workflows, list):
|
||||
return []
|
||||
normalized: List[dict] = [w for w in workflows if isinstance(w, dict) and isinstance(w.get("id"), str)]
|
||||
normalized: list[dict[str, Any]] = [w for w in workflows if isinstance(w, dict) and isinstance(w.get("id"), str)]
|
||||
if selected:
|
||||
normalized = [w for w in normalized if w["id"] in selected]
|
||||
return normalized
|
||||
@ -45,21 +45,25 @@ def resolve_script_arg(project_root: pathlib.Path, working_dir: pathlib.Path, ar
|
||||
return b
|
||||
|
||||
|
||||
def static_check_workflow(project_root: pathlib.Path, workflow: dict) -> dict:
|
||||
result = {
|
||||
def static_check_workflow(project_root: pathlib.Path, workflow: dict[str, Any]) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {
|
||||
"workflowId": workflow.get("id"),
|
||||
"ok": True,
|
||||
"issues": [],
|
||||
"steps": [],
|
||||
}
|
||||
|
||||
for step in workflow.get("steps", []):
|
||||
steps = workflow.get("steps", [])
|
||||
if not isinstance(steps, list):
|
||||
return result
|
||||
|
||||
for step in steps:
|
||||
if not isinstance(step, dict):
|
||||
continue
|
||||
step_id = step.get("id", "<unknown>")
|
||||
step_result = {"stepId": step_id, "ok": True, "issues": []}
|
||||
step_result: dict[str, Any] = {"stepId": step_id, "ok": True, "issues": []}
|
||||
|
||||
working_dir_rel = step.get("workingDir") or "."
|
||||
working_dir_rel = str(step.get("workingDir") or ".")
|
||||
working_dir = (project_root / working_dir_rel).resolve()
|
||||
if not working_dir.exists():
|
||||
step_result["ok"] = False
|
||||
@ -75,14 +79,13 @@ def static_check_workflow(project_root: pathlib.Path, workflow: dict) -> dict:
|
||||
step_result["issues"].append(f"command_not_found:{command}")
|
||||
|
||||
if command.lower() in ("python", "py", "python3", "python.exe", "py.exe"):
|
||||
if args and isinstance(args[0], str) and args[0].endswith(".py"):
|
||||
if isinstance(args, list) and args and isinstance(args[0], str) and args[0].endswith(".py"):
|
||||
script_path = resolve_script_arg(project_root, working_dir, args[0])
|
||||
if not script_path.exists():
|
||||
step_result["ok"] = False
|
||||
step_result["issues"].append(f"python_script_not_found:{script_path}")
|
||||
|
||||
if isinstance(action, str) and action.strip():
|
||||
# Action-based steps still require workingDir existence for reliable execution.
|
||||
if not working_dir.exists():
|
||||
step_result["ok"] = False
|
||||
step_result["issues"].append("action_working_dir_not_found")
|
||||
@ -96,8 +99,8 @@ def static_check_workflow(project_root: pathlib.Path, workflow: dict) -> dict:
|
||||
return result
|
||||
|
||||
|
||||
def sdt_attempts(repo_root: pathlib.Path) -> List[List[str]]:
|
||||
attempts: List[List[str]] = []
|
||||
def sdt_attempts(repo_root: pathlib.Path) -> list[list[str]]:
|
||||
attempts: list[list[str]] = []
|
||||
attempts.append(["sdt"])
|
||||
if sys.platform.startswith("win"):
|
||||
attempts.append(["sdt.exe"])
|
||||
@ -110,9 +113,8 @@ def sdt_attempts(repo_root: pathlib.Path) -> List[List[str]]:
|
||||
if devtool_csproj.exists():
|
||||
attempts.append(["dotnet", "run", "--project", str(devtool_csproj), "--"])
|
||||
|
||||
# Preserve order but dedupe exact attempts.
|
||||
seen = set()
|
||||
unique: List[List[str]] = []
|
||||
seen: set[tuple[str, ...]] = set()
|
||||
unique: list[list[str]] = []
|
||||
for a in attempts:
|
||||
key = tuple(a)
|
||||
if key in seen:
|
||||
@ -126,8 +128,8 @@ def try_run_sdt(
|
||||
repo_root: pathlib.Path,
|
||||
command_args: Sequence[str],
|
||||
timeout_seconds: int,
|
||||
) -> Tuple[Optional[subprocess.CompletedProcess], Optional[str]]:
|
||||
errors: List[str] = []
|
||||
) -> tuple[Optional[subprocess.CompletedProcess[str]], Optional[str]]:
|
||||
errors: list[str] = []
|
||||
for base in sdt_attempts(repo_root):
|
||||
cmd = [*base, *command_args]
|
||||
try:
|
||||
@ -147,7 +149,7 @@ def try_run_sdt(
|
||||
return None, "; ".join(errors) if errors else "no_sdt_attempts"
|
||||
|
||||
|
||||
def parse_headless_summary(stdout: str) -> Optional[Dict[str, Any]]:
|
||||
def parse_headless_summary(stdout: str) -> Optional[dict[str, Any]]:
|
||||
lines = [line.strip() for line in stdout.splitlines() if line.strip()]
|
||||
for line in reversed(lines):
|
||||
if not line.startswith("{"):
|
||||
@ -167,8 +169,8 @@ def execute_check_workflow(
|
||||
workflow_id: str,
|
||||
env_profile: Optional[str],
|
||||
timeout_seconds: int,
|
||||
) -> dict:
|
||||
args = [
|
||||
) -> dict[str, Any]:
|
||||
run_args = [
|
||||
"run",
|
||||
workflow_id,
|
||||
"--json",
|
||||
@ -177,9 +179,9 @@ def execute_check_workflow(
|
||||
"--non-interactive",
|
||||
]
|
||||
if env_profile:
|
||||
args.extend(["--env-profile", env_profile])
|
||||
run_args.extend(["--env-profile", env_profile])
|
||||
|
||||
proc, attempted = try_run_sdt(repo_root, args, timeout_seconds)
|
||||
proc, attempted = try_run_sdt(repo_root, run_args, timeout_seconds)
|
||||
if proc is None:
|
||||
return {
|
||||
"workflowId": workflow_id,
|
||||
@ -215,13 +217,13 @@ def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Verify SDT workflow routes (static path checks + optional headless execution)."
|
||||
)
|
||||
parser.add_argument("--repo-root", default=None, help="Repo root containing DevTool.csproj")
|
||||
parser.add_argument("--project-root", default=".", help="Project root containing devtool.json")
|
||||
parser.add_argument("--workflow", action="append", default=[], help="Workflow id to verify (repeatable)")
|
||||
parser.add_argument("--execute", action="store_true", help="Also run each workflow via `sdt run ... --json`")
|
||||
parser.add_argument("--env-profile", default=None)
|
||||
parser.add_argument("--timeout-seconds", type=int, default=600)
|
||||
parser.add_argument("--output-json", default=None, help="Write full report JSON to file")
|
||||
_ = parser.add_argument("--repo-root", default=None, help="Repo root containing DevTool.csproj")
|
||||
_ = parser.add_argument("--project-root", default=".", help="Project root containing devtool.json")
|
||||
_ = parser.add_argument("--workflow", action="append", default=[], help="Workflow id to verify (repeatable)")
|
||||
_ = parser.add_argument("--execute", action="store_true", help="Also run each workflow via `sdt run ... --json`")
|
||||
_ = parser.add_argument("--env-profile", default=None)
|
||||
_ = parser.add_argument("--timeout-seconds", type=int, default=600)
|
||||
_ = parser.add_argument("--output-json", default=None, help="Write full report JSON to file")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = resolve_repo_root(args.repo_root)
|
||||
@ -235,10 +237,10 @@ def main() -> int:
|
||||
return 2
|
||||
|
||||
static_results = [static_check_workflow(project_root, w) for w in workflows]
|
||||
execute_results: List[dict] = []
|
||||
execute_results: list[dict[str, Any]] = []
|
||||
if args.execute:
|
||||
for w in workflows:
|
||||
wid = w["id"]
|
||||
wid = str(w["id"])
|
||||
execute_results.append(
|
||||
execute_check_workflow(
|
||||
repo_root=repo_root,
|
||||
@ -252,7 +254,7 @@ def main() -> int:
|
||||
static_failures = [r for r in static_results if not r["ok"]]
|
||||
exec_failures = [r for r in execute_results if not r["ok"]]
|
||||
|
||||
report = {
|
||||
report: dict[str, Any] = {
|
||||
"repoRoot": str(repo_root),
|
||||
"projectRoot": str(project_root),
|
||||
"totalWorkflows": len(workflows),
|
||||
@ -283,13 +285,13 @@ def main() -> int:
|
||||
|
||||
if static_failures:
|
||||
print("\nStatic failures:")
|
||||
for f in static_failures:
|
||||
print(f"- {f['workflowId']}: {', '.join(f['issues'])}")
|
||||
for sf in static_failures:
|
||||
print(f"- {sf['workflowId']}: {', '.join(sf['issues'])}")
|
||||
|
||||
if exec_failures:
|
||||
print("\nExecution failures:")
|
||||
for f in exec_failures:
|
||||
print(f"- {f['workflowId']}: stopReason={f.get('stopReason')} message={f.get('message')}")
|
||||
for ef in exec_failures:
|
||||
print(f"- {ef['workflowId']}: stopReason={ef.get('stopReason')} message={ef.get('message')}")
|
||||
|
||||
return 1 if static_failures or exec_failures else 0
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
786
devtool.json
786
devtool.json
@ -1,786 +0,0 @@
|
||||
{
|
||||
"name": "Project Journal",
|
||||
"version": "0.1.0",
|
||||
"targets": [],
|
||||
"workflows": [
|
||||
{
|
||||
"id": "sidecar",
|
||||
"label": "Publish Sidecar",
|
||||
"description": "Build Journal.Sidecar as self-contained exe \u2192 output/",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "sidecar:run",
|
||||
"label": "Publish Sidecar",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-sidecar.ps1",
|
||||
"-Configuration",
|
||||
"Release",
|
||||
"-Runtime",
|
||||
"win-x64"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "web",
|
||||
"label": "Build Web UI",
|
||||
"description": "Build SvelteKit bundle \u2192 Journal.App/build/",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "web:run",
|
||||
"label": "Build Web UI",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-app.ps1",
|
||||
"-Target",
|
||||
"web"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sync-output",
|
||||
"label": "Sync Build Assets to Output",
|
||||
"description": "Sweep repo for newest builds (Web/Sidecar/Gateway/Tauri) and copy them to the output dir",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "sync-output:run",
|
||||
"label": "Sync Build Assets to Output",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/sync-output.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "webgateway",
|
||||
"label": "Publish WebGateway",
|
||||
"description": "Publish ASP.NET host with embedded web UI \u2192 output/webgateway/",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"web"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "webgateway:run",
|
||||
"label": "Publish WebGateway",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-webgateway.ps1",
|
||||
"-Configuration",
|
||||
"Release",
|
||||
"-Runtime",
|
||||
"win-x64"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tauri",
|
||||
"label": "Build Tauri Desktop App",
|
||||
"description": "Build desktop exe (no installer) \u2192 Journal.App/src-tauri/target/release/",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"sidecar"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "tauri:run",
|
||||
"label": "Build Tauri Desktop App",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-app.ps1",
|
||||
"-Target",
|
||||
"tauri",
|
||||
"-TauriBundles",
|
||||
"none"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "cargo",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tauri-nsis",
|
||||
"label": "Build Tauri \u002B NSIS Installer",
|
||||
"description": "Build desktop exe with NSIS installer package",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"sidecar"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "tauri-nsis:run",
|
||||
"label": "Build Tauri \u002B NSIS Installer",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-app.ps1",
|
||||
"-Target",
|
||||
"tauri",
|
||||
"-TauriBundles",
|
||||
"nsis"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "cargo",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "build-dotnet",
|
||||
"label": "Build .NET Projects",
|
||||
"description": "dotnet build \u2014 all C# projects in solution",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "build-dotnet:run",
|
||||
"label": "Build .NET Projects",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "all",
|
||||
"label": "Full Release Build \u2726",
|
||||
"description": "Sidecar \u2192 Web \u2192 WebGateway \u2192 Tauri, in dependency order",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"sidecar",
|
||||
"web",
|
||||
"webgateway",
|
||||
"tauri"
|
||||
],
|
||||
"steps": []
|
||||
},
|
||||
{
|
||||
"id": "run-gateway-dev",
|
||||
"label": "Run WebGateway Server (Dev)",
|
||||
"description": "Start HTTP gateway via \u0027dotnet run\u0027 at http://localhost:5180",
|
||||
"group": "Dev",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "run-gateway-dev:run",
|
||||
"label": "Run WebGateway Server (Dev)",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/run-webgateway.ps1",
|
||||
"-Mode",
|
||||
"Dev"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "run-gateway-prod",
|
||||
"label": "Run WebGateway Server (Output)",
|
||||
"description": "Start compiled gateway from output/webgateway at http://localhost:5180",
|
||||
"group": "Dev",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "run-gateway-prod:run",
|
||||
"label": "Run WebGateway Server (Output)",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/run-webgateway.ps1",
|
||||
"-Mode",
|
||||
"Output"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "test",
|
||||
"label": "Run Smoke Tests",
|
||||
"description": "Run all ~80 integration tests in Journal.SmokeTests",
|
||||
"group": "Test",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "test:run",
|
||||
"label": "Run Smoke Tests",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"run",
|
||||
"--project",
|
||||
"Journal.SmokeTests/Journal.SmokeTests.csproj"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gate",
|
||||
"label": "Run Migration Gate",
|
||||
"description": "Full build \u002B smoke tests \u002B parity check",
|
||||
"group": "Test",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "gate:run",
|
||||
"label": "Run Migration Gate",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/migration-gate.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "nuget-export",
|
||||
"label": "Export NuGet Cache",
|
||||
"description": "Prime and export .nuget cache to zip for offline use",
|
||||
"group": "Cache",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "nuget-export:run",
|
||||
"label": "Export NuGet Cache",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/nuget-export-cache.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "nuget-import",
|
||||
"label": "Import NuGet Cache",
|
||||
"description": "Import cache zip and validate restore",
|
||||
"group": "Cache",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "nuget-import:run",
|
||||
"label": "Import NuGet Cache",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/nuget-import-cache.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "npm-clean",
|
||||
"label": "Clean Node Modules",
|
||||
"description": "Remove Journal.App node_modules (kills node/tauri first)",
|
||||
"group": "System",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "npm-clean:run",
|
||||
"label": "Clean Node Modules",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/npm-clean.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stage-output",
|
||||
"label": "Stage Output Bundle",
|
||||
"description": "Publish sidecar \u002B web \u002B webgateway \u002B tauri, then stage journalapp.exe into output/",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "stage-output:run",
|
||||
"label": "Stage Output Bundle",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-output.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "cargo",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"key": "JOURNAL_AI_PROVIDER",
|
||||
"description": "AI provider bridge mode",
|
||||
"default": "none",
|
||||
"options": [
|
||||
"none",
|
||||
"python-sidecar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_LOG_LEVEL",
|
||||
"description": "Log verbosity for C# backend",
|
||||
"default": "warning",
|
||||
"options": [
|
||||
"trace",
|
||||
"debug",
|
||||
"information",
|
||||
"warning",
|
||||
"error",
|
||||
"critical"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_NLP_BACKEND",
|
||||
"description": "Python NLP backend selection",
|
||||
"default": "auto",
|
||||
"options": [
|
||||
"auto",
|
||||
"spacy",
|
||||
"fallback"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_PROJECT_ROOT",
|
||||
"description": "Override project root path (blank = auto-detect)",
|
||||
"default": "",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_VAULT_DIR",
|
||||
"description": "Override vault directory path",
|
||||
"default": "",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_DATA_DIR",
|
||||
"description": "Override decrypted data directory path",
|
||||
"default": "",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"key": "SDT_ENV_PROFILE",
|
||||
"description": "Active SDT runtime environment profile",
|
||||
"default": "dev",
|
||||
"options": [
|
||||
"dev",
|
||||
"ci",
|
||||
"release"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "SDT_LOG_LEVEL",
|
||||
"description": "CLI log verbosity",
|
||||
"default": "information",
|
||||
"options": [
|
||||
"trace",
|
||||
"debug",
|
||||
"information",
|
||||
"warning",
|
||||
"error",
|
||||
"critical"
|
||||
]
|
||||
}
|
||||
],
|
||||
"envProfiles": {
|
||||
"active": "dev",
|
||||
"profiles": [
|
||||
{
|
||||
"id": "dev",
|
||||
"description": "Local development defaults",
|
||||
"inherits": [],
|
||||
"values": {
|
||||
"SDT_ENV_PROFILE": "dev",
|
||||
"SDT_LOG_LEVEL": "information"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ci",
|
||||
"description": "Continuous integration defaults",
|
||||
"inherits": [
|
||||
"dev"
|
||||
],
|
||||
"values": {
|
||||
"SDT_ENV_PROFILE": "ci",
|
||||
"CI": "false",
|
||||
"SDT_LOG_LEVEL": "warning"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "release",
|
||||
"description": "Release build defaults",
|
||||
"inherits": [
|
||||
"dev"
|
||||
],
|
||||
"values": {
|
||||
"SDT_ENV_PROFILE": "release",
|
||||
"SDT_LOG_LEVEL": "warning"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"toolchains": {
|
||||
"python": {
|
||||
"executable": "python3.14",
|
||||
"windowsExecutable": "py",
|
||||
"launcherVersion": "-3.14",
|
||||
"venvDir": ".venv",
|
||||
"profiles": [
|
||||
{
|
||||
"id": "cpu",
|
||||
"label": "CPU only (default)",
|
||||
"requirementsFile": "requirements_cpu_only.txt",
|
||||
"extraIndexUrl": "https://download.pytorch.org/whl/cpu",
|
||||
"postInstallCommands": []
|
||||
},
|
||||
{
|
||||
"id": "gpu",
|
||||
"label": "GPU / CUDA",
|
||||
"requirementsFile": "requirements_gpu.txt",
|
||||
"extraIndexUrl": null,
|
||||
"postInstallCommands": []
|
||||
},
|
||||
{
|
||||
"id": "nlp",
|
||||
"label": "NLP / spaCy (optional)",
|
||||
"requirementsFile": "requirements_nlp_optional.txt",
|
||||
"extraIndexUrl": null,
|
||||
"postInstallCommands": [
|
||||
"spacy download en_core_web_sm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pipScript": "scripts/pip-min.ps1"
|
||||
},
|
||||
"node": {
|
||||
"packageManager": "npm",
|
||||
"workingDir": "Journal.App"
|
||||
}
|
||||
},
|
||||
"tooling": {
|
||||
"tools": [
|
||||
{
|
||||
"tool": "cargo",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "python",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"project": null,
|
||||
"debug": {
|
||||
"profiles": [],
|
||||
"diagnostics": {
|
||||
"enabled": true,
|
||||
"outputDir": ".sdt/debug",
|
||||
"includeAllEnv": false,
|
||||
"captureEnvKeys": [],
|
||||
"redactSensitive": true,
|
||||
"sensitiveKeyPatterns": [
|
||||
"TOKEN",
|
||||
"SECRET",
|
||||
"PASSWORD",
|
||||
"PWD",
|
||||
"CREDENTIAL",
|
||||
"API_KEY",
|
||||
"ACCESS_KEY",
|
||||
"PRIVATE_KEY"
|
||||
],
|
||||
"redactionAllowKeys": [],
|
||||
"bundleOnFailure": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,677 +0,0 @@
|
||||
{
|
||||
"name": "Project Journal",
|
||||
"version": "0.1.0",
|
||||
"targets": [],
|
||||
"workflows": [
|
||||
{
|
||||
"id": "sidecar",
|
||||
"label": "Publish Sidecar",
|
||||
"description": "Build Journal.Sidecar as self-contained exe \u2192 output/",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "sidecar:run",
|
||||
"label": "Publish Sidecar",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-sidecar.ps1",
|
||||
"-Configuration",
|
||||
"Release",
|
||||
"-Runtime",
|
||||
"win-x64"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "web",
|
||||
"label": "Build Web UI",
|
||||
"description": "Build SvelteKit bundle \u2192 Journal.App/build/",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "web:run",
|
||||
"label": "Build Web UI",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-app.ps1",
|
||||
"-Target",
|
||||
"web"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sync-output",
|
||||
"label": "Sync Build Assets to Output",
|
||||
"description": "Sweep repo for newest builds (Web/Sidecar/Gateway/Tauri) and copy them to the output dir",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "sync-output:run",
|
||||
"label": "Sync Build Assets to Output",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/sync-output.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "webgateway",
|
||||
"label": "Publish WebGateway",
|
||||
"description": "Publish ASP.NET host with embedded web UI \u2192 output/webgateway/",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"web"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "webgateway:run",
|
||||
"label": "Publish WebGateway",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-webgateway.ps1",
|
||||
"-Configuration",
|
||||
"Release",
|
||||
"-Runtime",
|
||||
"win-x64"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tauri",
|
||||
"label": "Build Tauri Desktop App",
|
||||
"description": "Build desktop exe (no installer) \u2192 Journal.App/src-tauri/target/release/",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"sidecar"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "tauri:run",
|
||||
"label": "Build Tauri Desktop App",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-app.ps1",
|
||||
"-Target",
|
||||
"tauri",
|
||||
"-TauriBundles",
|
||||
"none"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "cargo",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tauri-nsis",
|
||||
"label": "Build Tauri \u002B NSIS Installer",
|
||||
"description": "Build desktop exe with NSIS installer package",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"sidecar"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "tauri-nsis:run",
|
||||
"label": "Build Tauri \u002B NSIS Installer",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-app.ps1",
|
||||
"-Target",
|
||||
"tauri",
|
||||
"-TauriBundles",
|
||||
"nsis"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "cargo",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "build-dotnet",
|
||||
"label": "Build .NET Projects",
|
||||
"description": "dotnet build \u2014 all C# projects in solution",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "build-dotnet:run",
|
||||
"label": "Build .NET Projects",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "all",
|
||||
"label": "Full Release Build \u2726",
|
||||
"description": "Sidecar \u2192 Web \u2192 WebGateway \u2192 Tauri, in dependency order",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"sidecar",
|
||||
"web",
|
||||
"webgateway",
|
||||
"tauri"
|
||||
],
|
||||
"steps": []
|
||||
},
|
||||
{
|
||||
"id": "run-gateway-dev",
|
||||
"label": "Run WebGateway Server (Dev)",
|
||||
"description": "Start HTTP gateway via \u0027dotnet run\u0027 at http://localhost:5180",
|
||||
"group": "Dev",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "run-gateway-dev:run",
|
||||
"label": "Run WebGateway Server (Dev)",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/run-webgateway.ps1",
|
||||
"-Mode",
|
||||
"Dev"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "run-gateway-prod",
|
||||
"label": "Run WebGateway Server (Output)",
|
||||
"description": "Start compiled gateway from output/webgateway at http://localhost:5180",
|
||||
"group": "Dev",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "run-gateway-prod:run",
|
||||
"label": "Run WebGateway Server (Output)",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/run-webgateway.ps1",
|
||||
"-Mode",
|
||||
"Output"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "test",
|
||||
"label": "Run Smoke Tests",
|
||||
"description": "Run all ~80 integration tests in Journal.SmokeTests",
|
||||
"group": "Test",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "test:run",
|
||||
"label": "Run Smoke Tests",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"run",
|
||||
"--project",
|
||||
"Journal.SmokeTests/Journal.SmokeTests.csproj"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gate",
|
||||
"label": "Run Migration Gate",
|
||||
"description": "Full build \u002B smoke tests \u002B parity check",
|
||||
"group": "Test",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "gate:run",
|
||||
"label": "Run Migration Gate",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/migration-gate.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "nuget-export",
|
||||
"label": "Export NuGet Cache",
|
||||
"description": "Prime and export .nuget cache to zip for offline use",
|
||||
"group": "Cache",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "nuget-export:run",
|
||||
"label": "Export NuGet Cache",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/nuget-export-cache.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "nuget-import",
|
||||
"label": "Import NuGet Cache",
|
||||
"description": "Import cache zip and validate restore",
|
||||
"group": "Cache",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "nuget-import:run",
|
||||
"label": "Import NuGet Cache",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/nuget-import-cache.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "npm-clean",
|
||||
"label": "Clean Node Modules",
|
||||
"description": "Remove Journal.App node_modules (kills node/tauri first)",
|
||||
"group": "System",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "npm-clean:run",
|
||||
"label": "Clean Node Modules",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/npm-clean.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stage-output",
|
||||
"label": "Stage Output Bundle",
|
||||
"description": "Publish sidecar \u002B web \u002B webgateway \u002B tauri, then stage journalapp.exe into output/",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "stage-output:run",
|
||||
"label": "Stage Output Bundle",
|
||||
"command": "pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"scripts/publish-output.ps1"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "cargo",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"key": "JOURNAL_AI_PROVIDER",
|
||||
"description": "AI provider bridge mode",
|
||||
"default": "none",
|
||||
"options": [
|
||||
"none",
|
||||
"python-sidecar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_LOG_LEVEL",
|
||||
"description": "Log verbosity for C# backend",
|
||||
"default": "warning",
|
||||
"options": [
|
||||
"trace",
|
||||
"debug",
|
||||
"information",
|
||||
"warning",
|
||||
"error",
|
||||
"critical"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_NLP_BACKEND",
|
||||
"description": "Python NLP backend selection",
|
||||
"default": "auto",
|
||||
"options": [
|
||||
"auto",
|
||||
"spacy",
|
||||
"fallback"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_PROJECT_ROOT",
|
||||
"description": "Override project root path (blank = auto-detect)",
|
||||
"default": "",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_VAULT_DIR",
|
||||
"description": "Override vault directory path",
|
||||
"default": "",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"key": "JOURNAL_DATA_DIR",
|
||||
"description": "Override decrypted data directory path",
|
||||
"default": "",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"toolchains": {
|
||||
"python": {
|
||||
"executable": "python3.14",
|
||||
"windowsExecutable": "py",
|
||||
"launcherVersion": "-3.14",
|
||||
"venvDir": ".venv",
|
||||
"profiles": [
|
||||
{
|
||||
"id": "cpu",
|
||||
"label": "CPU only (default)",
|
||||
"requirementsFile": "requirements_cpu_only.txt",
|
||||
"extraIndexUrl": "https://download.pytorch.org/whl/cpu",
|
||||
"postInstallCommands": []
|
||||
},
|
||||
{
|
||||
"id": "gpu",
|
||||
"label": "GPU / CUDA",
|
||||
"requirementsFile": "requirements_gpu.txt",
|
||||
"extraIndexUrl": null,
|
||||
"postInstallCommands": []
|
||||
},
|
||||
{
|
||||
"id": "nlp",
|
||||
"label": "NLP / spaCy (optional)",
|
||||
"requirementsFile": "requirements_nlp_optional.txt",
|
||||
"extraIndexUrl": null,
|
||||
"postInstallCommands": [
|
||||
"spacy download en_core_web_sm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pipScript": "scripts/pip-min.ps1"
|
||||
},
|
||||
"node": {
|
||||
"packageManager": "npm",
|
||||
"workingDir": "Journal.App"
|
||||
}
|
||||
},
|
||||
"tooling": null,
|
||||
"project": null,
|
||||
"debug": null
|
||||
}
|
||||
558
sdtconfig-journal.json
Normal file
558
sdtconfig-journal.json
Normal file
@ -0,0 +1,558 @@
|
||||
{
|
||||
"name": "journal",
|
||||
"version": "0.1.0",
|
||||
"targets": [],
|
||||
"workflows": [
|
||||
{
|
||||
"id": "sidecar",
|
||||
"label": "Publish Sidecar",
|
||||
"description": "Publish sidecar service",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [
|
||||
"Journal.DevTool\\scripts\\publish-sidecar.py"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "sidecar:run",
|
||||
"label": "python Journal.DevTool\\scripts\\publish-sidecar.py",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"Journal.DevTool\\scripts\\publish-sidecar.py"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "web",
|
||||
"label": "Build Web UI",
|
||||
"description": "Build frontend assets",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [
|
||||
"Journal.DevTool\\scripts\\publish-app.py"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "web:run",
|
||||
"label": "python Journal.DevTool\\scripts\\publish-app.py --target web",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"Journal.DevTool\\scripts\\publish-app.py",
|
||||
"--target",
|
||||
"web"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tauri",
|
||||
"label": "Build Tauri Desktop App",
|
||||
"description": "Build desktop binary",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"sidecar"
|
||||
],
|
||||
"requireFiles": [
|
||||
"Journal.DevTool\\scripts\\publish-app.py",
|
||||
"Journal.App/src-tauri/tauri.conf.json"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "tauri:run",
|
||||
"label": "python Journal.DevTool\\scripts\\publish-app.py --target tauri --tauri-bundles none",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"Journal.DevTool\\scripts\\publish-app.py",
|
||||
"--target",
|
||||
"tauri",
|
||||
"--tauri-bundles",
|
||||
"none"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "webgateway",
|
||||
"label": "Publish WebGateway",
|
||||
"description": "Publish ASP.NET gateway",
|
||||
"group": "Build",
|
||||
"dependsOn": [
|
||||
"web"
|
||||
],
|
||||
"requireFiles": [
|
||||
"Journal.DevTool\\scripts\\publish-webgateway.py"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "webgateway:run",
|
||||
"label": "python Journal.DevTool\\scripts\\publish-webgateway.py",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"Journal.DevTool\\scripts\\publish-webgateway.py"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sync-output",
|
||||
"label": "Sync Output",
|
||||
"description": "Sync newest artifacts to output",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [
|
||||
"Journal.DevTool\\scripts\\sync-output.py"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "sync-output:run",
|
||||
"label": "python Journal.DevTool\\scripts\\sync-output.py",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"Journal.DevTool\\scripts\\sync-output.py"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stage-output",
|
||||
"label": "Stage Output Bundle",
|
||||
"description": "Publish and stage distributable output",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [
|
||||
"Journal.DevTool\\scripts\\publish-output.py"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "stage-output:run",
|
||||
"label": "python Journal.DevTool\\scripts\\publish-output.py",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"Journal.DevTool\\scripts\\publish-output.py"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "run-gateway-dev",
|
||||
"label": "Run WebGateway Server (Dev)",
|
||||
"description": "Run gateway in development mode",
|
||||
"group": "Dev",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [
|
||||
"Journal.DevTool\\scripts\\run-webgateway.py"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "run-gateway-dev:run",
|
||||
"label": "python Journal.DevTool\\scripts\\run-webgateway.py --mode Dev",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"Journal.DevTool\\scripts\\run-webgateway.py",
|
||||
"--mode",
|
||||
"Dev"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"action": null,
|
||||
"actionArgs": [],
|
||||
"requires": [
|
||||
{
|
||||
"tool": "python",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "build",
|
||||
"label": "Build",
|
||||
"description": "Build detected project stacks",
|
||||
"group": "Build",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "dotnet-build",
|
||||
"label": "dotnet build",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": ".",
|
||||
"action": "dotnet-build",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
},
|
||||
{
|
||||
"id": "npm-build",
|
||||
"label": "npm run build",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": "Journal.App",
|
||||
"action": "npm-build",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
},
|
||||
{
|
||||
"id": "cargo-build",
|
||||
"label": "cargo build",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": ".",
|
||||
"action": "cargo-build",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
},
|
||||
{
|
||||
"id": "tauri-build",
|
||||
"label": "tauri build",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": "Journal.App",
|
||||
"action": "tauri-build",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "deps-refresh",
|
||||
"label": "Refresh Dependencies",
|
||||
"description": "Restore/install dependency stacks",
|
||||
"group": "Deps",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "dotnet-restore",
|
||||
"label": "dotnet restore",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": ".",
|
||||
"action": "dotnet-restore",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
},
|
||||
{
|
||||
"id": "npm-ci",
|
||||
"label": "npm ci",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": "Journal.App",
|
||||
"action": "npm-ci",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "test",
|
||||
"label": "Run Tests",
|
||||
"description": "Run detected test stacks",
|
||||
"group": "Test",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "dotnet-test",
|
||||
"label": "dotnet test",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": ".",
|
||||
"action": "dotnet-test",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
},
|
||||
{
|
||||
"id": "npm-test",
|
||||
"label": "npm test",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": "Journal.App",
|
||||
"action": "npm-test",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
},
|
||||
{
|
||||
"id": "cargo-test",
|
||||
"label": "cargo test",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": ".",
|
||||
"action": "cargo-test",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repo-health",
|
||||
"label": "Repo Health",
|
||||
"description": "Check repo status and fetch remotes",
|
||||
"group": "Repo",
|
||||
"dependsOn": [],
|
||||
"requireFiles": [],
|
||||
"steps": [
|
||||
{
|
||||
"id": "git-status",
|
||||
"label": "git status",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": ".",
|
||||
"action": "git-status",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
},
|
||||
{
|
||||
"id": "git-fetch",
|
||||
"label": "git fetch",
|
||||
"command": null,
|
||||
"args": [],
|
||||
"workingDir": ".",
|
||||
"action": "git-fetch",
|
||||
"actionArgs": [],
|
||||
"requires": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"key": "SDT_LOG_LEVEL",
|
||||
"description": "CLI log verbosity",
|
||||
"default": "information",
|
||||
"options": [
|
||||
"trace",
|
||||
"debug",
|
||||
"information",
|
||||
"warning",
|
||||
"error",
|
||||
"critical"
|
||||
]
|
||||
}
|
||||
],
|
||||
"envProfiles": {
|
||||
"active": "dev",
|
||||
"profiles": [
|
||||
{
|
||||
"id": "dev",
|
||||
"description": "Local development defaults",
|
||||
"inherits": [],
|
||||
"values": {
|
||||
"SDT_ENV_PROFILE": "dev",
|
||||
"SDT_LOG_LEVEL": "information"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ci",
|
||||
"description": "Continuous integration defaults",
|
||||
"inherits": [
|
||||
"dev"
|
||||
],
|
||||
"values": {
|
||||
"SDT_ENV_PROFILE": "ci",
|
||||
"CI": "true",
|
||||
"SDT_LOG_LEVEL": "warning"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "release",
|
||||
"description": "Release build defaults",
|
||||
"inherits": [
|
||||
"dev"
|
||||
],
|
||||
"values": {
|
||||
"SDT_ENV_PROFILE": "release",
|
||||
"SDT_LOG_LEVEL": "warning"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"toolchains": {
|
||||
"python": null,
|
||||
"node": {
|
||||
"packageManager": "npm",
|
||||
"workingDir": "Journal.App"
|
||||
}
|
||||
},
|
||||
"tooling": {
|
||||
"defaultInstallPolicy": "Prompt",
|
||||
"tools": [
|
||||
{
|
||||
"tool": "cargo",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "git",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "node",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
},
|
||||
{
|
||||
"tool": "tauri",
|
||||
"preferredInstallCommands": [],
|
||||
"executables": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"project": {
|
||||
"type": "tauri",
|
||||
"rootHints": [
|
||||
"*.sln",
|
||||
".git",
|
||||
"Cargo.toml",
|
||||
"package.json",
|
||||
"tauri.conf.json"
|
||||
],
|
||||
"artifacts": [
|
||||
"bin",
|
||||
"obj",
|
||||
".sdt/debug"
|
||||
]
|
||||
},
|
||||
"debug": {
|
||||
"profiles": [
|
||||
{
|
||||
"id": "dotnet-run",
|
||||
"label": "Run .NET app",
|
||||
"type": "dotnet",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"workingDir": ".",
|
||||
"env": {},
|
||||
"requires": [
|
||||
{
|
||||
"tool": "dotnet",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
],
|
||||
"attach": {
|
||||
"kind": "manual",
|
||||
"port": null,
|
||||
"processName": null,
|
||||
"note": "Attach your IDE debugger to the running dotnet process."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "npm-dev",
|
||||
"label": "Run npm dev server",
|
||||
"type": "node",
|
||||
"command": "npm",
|
||||
"args": [
|
||||
"run",
|
||||
"dev"
|
||||
],
|
||||
"workingDir": "Journal.App",
|
||||
"env": {},
|
||||
"requires": [
|
||||
{
|
||||
"tool": "node",
|
||||
"installPolicy": "Prompt"
|
||||
},
|
||||
{
|
||||
"tool": "npm",
|
||||
"installPolicy": "Prompt"
|
||||
}
|
||||
],
|
||||
"attach": null
|
||||
}
|
||||
],
|
||||
"diagnostics": {
|
||||
"enabled": true,
|
||||
"outputDir": ".sdt/debug",
|
||||
"includeAllEnv": false,
|
||||
"captureEnvKeys": [
|
||||
"SDT_LOG_LEVEL",
|
||||
"DOTNET_CLI_HOME",
|
||||
"NUGET_PACKAGES",
|
||||
"PIP_CACHE_DIR",
|
||||
"NVM_HOME",
|
||||
"NVM_SYMLINK"
|
||||
],
|
||||
"redactSensitive": true,
|
||||
"sensitiveKeyPatterns": [
|
||||
"TOKEN",
|
||||
"SECRET",
|
||||
"PASSWORD",
|
||||
"PWD",
|
||||
"CREDENTIAL",
|
||||
"API_KEY",
|
||||
"ACCESS_KEY",
|
||||
"PRIVATE_KEY"
|
||||
],
|
||||
"redactionAllowKeys": [],
|
||||
"bundleOnFailure": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user