Compare commits
No commits in common. "06c0d30aaa0027b99f7c091dc49439325d7257fc" and "d6523c6876e0c9327e60c4dc9e7522406b6c7f7b" have entirely different histories.
06c0d30aaa
...
d6523c6876
4
.gitignore
vendored
4
.gitignore
vendored
@ -55,6 +55,4 @@ journalapp(1).exe
|
|||||||
.cache/
|
.cache/
|
||||||
Journal.DevTool/scripts/__pycache__/
|
Journal.DevTool/scripts/__pycache__/
|
||||||
.sdt/
|
.sdt/
|
||||||
devtool.backup.json
|
devtool.backup.json
|
||||||
Journal.App/node_modules.old/
|
|
||||||
scripts/__pycache__/
|
|
||||||
@ -10,8 +10,7 @@
|
|||||||
<PackageVersion Include="NAudio" Version="2.2.1" />
|
<PackageVersion Include="NAudio" Version="2.2.1" />
|
||||||
<PackageVersion Include="Whisper.net" Version="1.9.0" />
|
<PackageVersion Include="Whisper.net" Version="1.9.0" />
|
||||||
<PackageVersion Include="Whisper.net.Runtime" Version="1.9.0" />
|
<PackageVersion Include="Whisper.net.Runtime" Version="1.9.0" />
|
||||||
<PackageVersion Include="LLamaSharp" Version="0.25.0" />
|
<PackageVersion Include="LLamaSharp" Version="0.26.0" />
|
||||||
<PackageVersion Include="LLamaSharp.Backend.Cpu" Version="0.25.0" />
|
<PackageVersion Include="LLamaSharp.Backend.Cpu" Version="0.26.0" />
|
||||||
<PackageVersion Include="LLamaSharp.Backend.Vulkan" Version="0.25.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -3,7 +3,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LLamaSharp" />
|
<PackageReference Include="LLamaSharp" />
|
||||||
<PackageReference Include="LLamaSharp.Backend.Cpu" />
|
<PackageReference Include="LLamaSharp.Backend.Cpu" />
|
||||||
<PackageReference Include="LLamaSharp.Backend.Vulkan" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ public sealed partial class LlamaSharpAiService(JournalConfig config) : IAiServi
|
|||||||
|
|
||||||
private readonly string _configuredModelPath = config.GgufModelPath;
|
private readonly string _configuredModelPath = config.GgufModelPath;
|
||||||
private readonly uint _contextSize = (uint)Math.Clamp(config.ModelContextTokens, 512, 4096);
|
private readonly uint _contextSize = (uint)Math.Clamp(config.ModelContextTokens, 512, 4096);
|
||||||
private readonly int _gpuLayers = config.GpuLayerCount;
|
private readonly int _gpuLayers = config.LlamaCppTimeout;
|
||||||
|
|
||||||
private readonly Lock _sync = new();
|
private readonly Lock _sync = new();
|
||||||
private string? _resolvedModelPath;
|
private string? _resolvedModelPath;
|
||||||
|
|||||||
@ -13,7 +13,6 @@ public sealed record JournalConfig(
|
|||||||
string LlamaCppUrl,
|
string LlamaCppUrl,
|
||||||
string LlamaCppModel,
|
string LlamaCppModel,
|
||||||
int LlamaCppTimeout,
|
int LlamaCppTimeout,
|
||||||
int GpuLayerCount,
|
|
||||||
string EmbeddingApiUrl,
|
string EmbeddingApiUrl,
|
||||||
string EmbeddingModelName,
|
string EmbeddingModelName,
|
||||||
int ModelContextTokens,
|
int ModelContextTokens,
|
||||||
|
|||||||
@ -38,7 +38,6 @@ public sealed class JournalConfigService : IJournalConfigService
|
|||||||
LlamaCppUrl: Environment.GetEnvironmentVariable("LLAMA_CPP_URL") ?? "http://127.0.0.1:8085/v1/completions",
|
LlamaCppUrl: Environment.GetEnvironmentVariable("LLAMA_CPP_URL") ?? "http://127.0.0.1:8085/v1/completions",
|
||||||
LlamaCppModel: Environment.GetEnvironmentVariable("LLAMA_CPP_MODEL") ?? "qwen/qwen3-4b",
|
LlamaCppModel: Environment.GetEnvironmentVariable("LLAMA_CPP_MODEL") ?? "qwen/qwen3-4b",
|
||||||
LlamaCppTimeout: ParseInt("LLAMA_CPP_TIMEOUT", 6000),
|
LlamaCppTimeout: ParseInt("LLAMA_CPP_TIMEOUT", 6000),
|
||||||
GpuLayerCount: ParseInt("JOURNAL_GPU_LAYERS", -1),
|
|
||||||
EmbeddingApiUrl: Environment.GetEnvironmentVariable("EMBEDDING_API_URL") ?? "http://127.0.0.1:8086/v1/embeddings",
|
EmbeddingApiUrl: Environment.GetEnvironmentVariable("EMBEDDING_API_URL") ?? "http://127.0.0.1:8086/v1/embeddings",
|
||||||
EmbeddingModelName: Environment.GetEnvironmentVariable("EMBEDDING_MODEL_NAME") ?? "text-embedding-nomic-embed-text-v2-moe",
|
EmbeddingModelName: Environment.GetEnvironmentVariable("EMBEDDING_MODEL_NAME") ?? "text-embedding-nomic-embed-text-v2-moe",
|
||||||
ModelContextTokens: ParseInt("MODEL_CONTEXT_TOKENS", 131072),
|
ModelContextTokens: ParseInt("MODEL_CONTEXT_TOKENS", 131072),
|
||||||
|
|||||||
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.
@ -1,63 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
# 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,15 +8,11 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from script_common import resolve_command
|
||||||
|
|
||||||
from script_common import resolve_command, SdtResult # type: ignore
|
|
||||||
|
|
||||||
StepResult = dict[str, Any]
|
|
||||||
|
|
||||||
|
|
||||||
def run_step(command: str, args: list[str], cwd: str) -> StepResult:
|
def run_step(command, args, cwd):
|
||||||
resolved = str(resolve_command(command))
|
resolved = resolve_command(command)
|
||||||
if shutil.which(resolved) is None and not pathlib.Path(resolved).exists():
|
if shutil.which(resolved) is None and not pathlib.Path(resolved).exists():
|
||||||
return {
|
return {
|
||||||
"command": resolved,
|
"command": resolved,
|
||||||
@ -42,7 +38,7 @@ def run_step(command: str, args: list[str], cwd: str) -> StepResult:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def resolve_python_executable() -> str:
|
def resolve_python_executable():
|
||||||
candidates = ["py", "python"] if os.name == "nt" else ["python3", "python"]
|
candidates = ["py", "python"] if os.name == "nt" else ["python3", "python"]
|
||||||
for c in candidates:
|
for c in candidates:
|
||||||
if shutil.which(c):
|
if shutil.which(c):
|
||||||
@ -50,20 +46,20 @@ def resolve_python_executable() -> str:
|
|||||||
return "python"
|
return "python"
|
||||||
|
|
||||||
|
|
||||||
def parse_common(parser: argparse.ArgumentParser) -> None:
|
def parse_common(parser):
|
||||||
_ = parser.add_argument("--project-root", required=True)
|
parser.add_argument("--project-root", required=True)
|
||||||
_ = parser.add_argument("--working-dir", default=".")
|
parser.add_argument("--working-dir", default=".")
|
||||||
_ = parser.add_argument("--json", action="store_true")
|
parser.add_argument("--json", action="store_true")
|
||||||
|
|
||||||
|
|
||||||
def resolve_cwd(project_root: str, working_dir: str) -> str:
|
def resolve_cwd(project_root, working_dir):
|
||||||
return os.path.abspath(os.path.join(project_root, working_dir))
|
return os.path.abspath(os.path.join(project_root, working_dir))
|
||||||
|
|
||||||
|
|
||||||
EXCLUDED_SCAN_DIRS = {".git", "node_modules", "bin", "obj", ".venv", "venv", ".sdt", "dist", "build"}
|
EXCLUDED_SCAN_DIRS = {".git", "node_modules", "bin", "obj", ".venv", "venv", ".sdt", "dist", "build"}
|
||||||
|
|
||||||
|
|
||||||
def discover_dotnet_target(project_root: str, cwd: str) -> str | None:
|
def discover_dotnet_target(project_root: str, cwd: str):
|
||||||
# Prefer local solution first (.slnx, then .sln), then csproj, then bounded scan from project root.
|
# Prefer local solution first (.slnx, then .sln), then csproj, then bounded scan from project root.
|
||||||
local_slnx = sorted(pathlib.Path(cwd).glob("*.slnx"))
|
local_slnx = sorted(pathlib.Path(cwd).glob("*.slnx"))
|
||||||
if len(local_slnx) == 1:
|
if len(local_slnx) == 1:
|
||||||
@ -92,9 +88,9 @@ def discover_dotnet_target(project_root: str, cwd: str) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def bounded_find_files(root: str, extension: str, max_depth: int) -> list[str]:
|
def bounded_find_files(root: str, extension: str, max_depth: int):
|
||||||
root_path = pathlib.Path(root).resolve()
|
root_path = pathlib.Path(root).resolve()
|
||||||
results: list[str] = []
|
results = []
|
||||||
for current_root, dirs, files in os.walk(root_path):
|
for current_root, dirs, files in os.walk(root_path):
|
||||||
rel = pathlib.Path(current_root).resolve().relative_to(root_path)
|
rel = pathlib.Path(current_root).resolve().relative_to(root_path)
|
||||||
depth = len(rel.parts)
|
depth = len(rel.parts)
|
||||||
@ -109,7 +105,7 @@ def bounded_find_files(root: str, extension: str, max_depth: int) -> list[str]:
|
|||||||
return sorted(results)
|
return sorted(results)
|
||||||
|
|
||||||
|
|
||||||
def run_dotnet_action(project_root: str, working_dir: str, verb: str) -> tuple[int, StepResult]:
|
def run_dotnet_action(project_root, working_dir, verb):
|
||||||
cwd = resolve_cwd(project_root, working_dir)
|
cwd = resolve_cwd(project_root, working_dir)
|
||||||
target = discover_dotnet_target(project_root, cwd)
|
target = discover_dotnet_target(project_root, cwd)
|
||||||
if not target:
|
if not target:
|
||||||
@ -128,14 +124,10 @@ def run_dotnet_action(project_root: str, working_dir: str, verb: str) -> tuple[i
|
|||||||
args = [verb, target]
|
args = [verb, target]
|
||||||
step = run_step("dotnet", args, cwd)
|
step = run_step("dotnet", args, cwd)
|
||||||
step["resolved_target"] = target
|
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: str) -> str:
|
def _deps_hash(app_root):
|
||||||
h = hashlib.sha256()
|
h = hashlib.sha256()
|
||||||
for name in ("package.json", "package-lock.json"):
|
for name in ("package.json", "package-lock.json"):
|
||||||
p = pathlib.Path(app_root) / name
|
p = pathlib.Path(app_root) / name
|
||||||
@ -144,7 +136,7 @@ def _deps_hash(app_root: str) -> str:
|
|||||||
return h.hexdigest()
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def ensure_npm_dependencies(app_root: str) -> dict[str, Any]:
|
def ensure_npm_dependencies(app_root):
|
||||||
package_json = pathlib.Path(app_root) / "package.json"
|
package_json = pathlib.Path(app_root) / "package.json"
|
||||||
if not package_json.exists():
|
if not package_json.exists():
|
||||||
return {"installed": False, "reason": "not_applicable"}
|
return {"installed": False, "reason": "not_applicable"}
|
||||||
@ -182,7 +174,7 @@ def ensure_npm_dependencies(app_root: str) -> dict[str, Any]:
|
|||||||
return {"installed": True, "reason": "installed", "step": install_step}
|
return {"installed": True, "reason": "installed", "step": install_step}
|
||||||
|
|
||||||
|
|
||||||
def read_package_json(cwd: str) -> dict[str, Any] | None:
|
def read_package_json(cwd: str):
|
||||||
package_json = pathlib.Path(cwd) / "package.json"
|
package_json = pathlib.Path(cwd) / "package.json"
|
||||||
if not package_json.exists():
|
if not package_json.exists():
|
||||||
return None
|
return None
|
||||||
@ -202,23 +194,23 @@ def has_npm_script(cwd: str, script_name: str) -> bool:
|
|||||||
return script_name in scripts and isinstance(scripts.get(script_name), str)
|
return script_name in scripts and isinstance(scripts.get(script_name), str)
|
||||||
|
|
||||||
|
|
||||||
def action_dotnet_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_dotnet_build(args):
|
||||||
return run_dotnet_action(args.project_root, args.working_dir, "build")
|
return run_dotnet_action(args.project_root, args.working_dir, "build")
|
||||||
|
|
||||||
|
|
||||||
def action_dotnet_restore(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_dotnet_restore(args):
|
||||||
return run_dotnet_action(args.project_root, args.working_dir, "restore")
|
return run_dotnet_action(args.project_root, args.working_dir, "restore")
|
||||||
|
|
||||||
|
|
||||||
def action_dotnet_test(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_dotnet_test(args):
|
||||||
return run_dotnet_action(args.project_root, args.working_dir, "test")
|
return run_dotnet_action(args.project_root, args.working_dir, "test")
|
||||||
|
|
||||||
|
|
||||||
def action_dotnet_publish(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_dotnet_publish(args):
|
||||||
return run_dotnet_action(args.project_root, args.working_dir, "publish")
|
return run_dotnet_action(args.project_root, args.working_dir, "publish")
|
||||||
|
|
||||||
|
|
||||||
def action_npm_install(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_npm_install(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||||
return 0, {
|
return 0, {
|
||||||
@ -232,10 +224,10 @@ def action_npm_install(args: argparse.Namespace) -> tuple[int, StepResult]:
|
|||||||
"skip_reason": "not_applicable_no_package_json",
|
"skip_reason": "not_applicable_no_package_json",
|
||||||
}
|
}
|
||||||
step = run_step("npm", ["install"], cwd)
|
step = run_step("npm", ["install"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_npm_ci(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_npm_ci(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||||
return 0, {
|
return 0, {
|
||||||
@ -249,10 +241,10 @@ def action_npm_ci(args: argparse.Namespace) -> tuple[int, StepResult]:
|
|||||||
"skip_reason": "not_applicable_no_package_json",
|
"skip_reason": "not_applicable_no_package_json",
|
||||||
}
|
}
|
||||||
step = run_step("npm", ["ci"], cwd)
|
step = run_step("npm", ["ci"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_npm_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_npm_build(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||||
return 0, {
|
return 0, {
|
||||||
@ -291,12 +283,12 @@ def action_npm_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
|||||||
if deps.get("reason") == "install_failed":
|
if deps.get("reason") == "install_failed":
|
||||||
step = deps["step"]
|
step = deps["step"]
|
||||||
step["failure_reason"] = "deps_install_failed"
|
step["failure_reason"] = "deps_install_failed"
|
||||||
return int(step["exit_code"]), step
|
return step["exit_code"], step
|
||||||
step = run_step("npm", ["run", "build"], cwd)
|
step = run_step("npm", ["run", "build"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_npm_test(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_npm_test(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||||
return 0, {
|
return 0, {
|
||||||
@ -335,12 +327,12 @@ def action_npm_test(args: argparse.Namespace) -> tuple[int, StepResult]:
|
|||||||
if deps.get("reason") == "install_failed":
|
if deps.get("reason") == "install_failed":
|
||||||
step = deps["step"]
|
step = deps["step"]
|
||||||
step["failure_reason"] = "deps_install_failed"
|
step["failure_reason"] = "deps_install_failed"
|
||||||
return int(step["exit_code"]), step
|
return step["exit_code"], step
|
||||||
step = run_step("npm", ["test"], cwd)
|
step = run_step("npm", ["test"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_npm_audit(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_npm_audit(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
if not (pathlib.Path(cwd) / "package.json").exists():
|
if not (pathlib.Path(cwd) / "package.json").exists():
|
||||||
return 0, {
|
return 0, {
|
||||||
@ -354,37 +346,37 @@ def action_npm_audit(args: argparse.Namespace) -> tuple[int, StepResult]:
|
|||||||
"skip_reason": "not_applicable_no_package_json",
|
"skip_reason": "not_applicable_no_package_json",
|
||||||
}
|
}
|
||||||
step = run_step("npm", ["audit"], cwd)
|
step = run_step("npm", ["audit"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_python_venv_create(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_python_venv_create(args):
|
||||||
cwd = resolve_cwd(args.project_root, ".")
|
cwd = resolve_cwd(args.project_root, ".")
|
||||||
venv_dir = str(args.venv_dir) if hasattr(args, "venv_dir") else ".venv"
|
venv_dir = args.venv_dir or ".venv"
|
||||||
step = run_step(resolve_python_executable(), ["-m", "venv", venv_dir], cwd)
|
step = run_step(resolve_python_executable(), ["-m", "venv", venv_dir], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_python_pip_install(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_python_pip_install(args):
|
||||||
cwd = resolve_cwd(args.project_root, ".")
|
cwd = resolve_cwd(args.project_root, ".")
|
||||||
req = str(args.requirements)
|
req = args.requirements
|
||||||
step = run_step(resolve_python_executable(), ["-m", "pip", "install", "-r", req], cwd)
|
step = run_step(resolve_python_executable(), ["-m", "pip", "install", "-r", req], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_python_pip_sync(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_python_pip_sync(args):
|
||||||
cwd = resolve_cwd(args.project_root, ".")
|
cwd = resolve_cwd(args.project_root, ".")
|
||||||
req = str(args.requirements)
|
req = args.requirements
|
||||||
step = run_step(resolve_python_executable(), ["-m", "pip", "install", "-r", req], cwd)
|
step = run_step(resolve_python_executable(), ["-m", "pip", "install", "-r", req], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_python_pytest(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_python_pytest(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
step = run_step(resolve_python_executable(), ["-m", "pytest"], cwd)
|
step = run_step(resolve_python_executable(), ["-m", "pytest"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_cargo_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_cargo_build(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
if not (pathlib.Path(cwd) / "Cargo.toml").exists():
|
if not (pathlib.Path(cwd) / "Cargo.toml").exists():
|
||||||
return 0, {
|
return 0, {
|
||||||
@ -398,10 +390,10 @@ def action_cargo_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
|||||||
"skip_reason": "not_applicable_no_cargo_toml",
|
"skip_reason": "not_applicable_no_cargo_toml",
|
||||||
}
|
}
|
||||||
step = run_step("cargo", ["build"], cwd)
|
step = run_step("cargo", ["build"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_cargo_test(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_cargo_test(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
if not (pathlib.Path(cwd) / "Cargo.toml").exists():
|
if not (pathlib.Path(cwd) / "Cargo.toml").exists():
|
||||||
return 0, {
|
return 0, {
|
||||||
@ -415,10 +407,10 @@ def action_cargo_test(args: argparse.Namespace) -> tuple[int, StepResult]:
|
|||||||
"skip_reason": "not_applicable_no_cargo_toml",
|
"skip_reason": "not_applicable_no_cargo_toml",
|
||||||
}
|
}
|
||||||
step = run_step("cargo", ["test"], cwd)
|
step = run_step("cargo", ["test"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_tauri_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_tauri_build(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
tauri_conf = pathlib.Path(cwd) / "src-tauri" / "tauri.conf.json"
|
tauri_conf = pathlib.Path(cwd) / "src-tauri" / "tauri.conf.json"
|
||||||
if not tauri_conf.exists():
|
if not tauri_conf.exists():
|
||||||
@ -439,94 +431,133 @@ def action_tauri_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
|||||||
if deps.get("reason") == "install_failed":
|
if deps.get("reason") == "install_failed":
|
||||||
step = deps["step"]
|
step = deps["step"]
|
||||||
step["failure_reason"] = "deps_install_failed"
|
step["failure_reason"] = "deps_install_failed"
|
||||||
return int(step["exit_code"]), step
|
return step["exit_code"], step
|
||||||
|
|
||||||
tauri_args = ["run", "tauri", "build"]
|
tauri_args = ["run", "tauri", "build"]
|
||||||
if hasattr(args, "no_bundle") and args.no_bundle:
|
if args.no_bundle:
|
||||||
tauri_args.extend(["--", "--no-bundle"])
|
tauri_args.extend(["--", "--no-bundle"])
|
||||||
step = run_step("npm", tauri_args, cwd)
|
step = run_step("npm", tauri_args, cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_git_status(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_git_status(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
step = run_step("git", ["status"], cwd)
|
step = run_step("git", ["status"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_git_fetch(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_git_fetch(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
step = run_step("git", ["fetch"], cwd)
|
step = run_step("git", ["fetch"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_git_pull(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_git_pull(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
step = run_step("git", ["pull"], cwd)
|
step = run_step("git", ["pull"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_git_clean(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_git_clean(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
step = run_step("git", ["clean", "-fd"], cwd)
|
step = run_step("git", ["clean", "-fd"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_docker_build(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_docker_build(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
step = run_step("docker", ["build", "."], cwd)
|
step = run_step("docker", ["build", "."], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_docker_compose_up(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_docker_compose_up(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
step = run_step("docker", ["compose", "up", "-d"], cwd)
|
step = run_step("docker", ["compose", "up", "-d"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def action_docker_compose_down(args: argparse.Namespace) -> tuple[int, StepResult]:
|
def action_docker_compose_down(args):
|
||||||
cwd = resolve_cwd(args.project_root, args.working_dir)
|
cwd = resolve_cwd(args.project_root, args.working_dir)
|
||||||
step = run_step("docker", ["compose", "down"], cwd)
|
step = run_step("docker", ["compose", "down"], cwd)
|
||||||
return 0 if step["exit_code"] == 0 else int(step["exit_code"]), step
|
return 0 if step["exit_code"] == 0 else step["exit_code"], step
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="SDT normalized build actions")
|
parser = argparse.ArgumentParser(description="SDT normalized build actions")
|
||||||
sub = parser.add_subparsers(dest="action", required=True)
|
sub = parser.add_subparsers(dest="action", required=True)
|
||||||
|
|
||||||
p0 = sub.add_parser("dotnet-restore"); parse_common(p0)
|
p0 = sub.add_parser("dotnet-restore")
|
||||||
p1 = sub.add_parser("dotnet-build"); parse_common(p1)
|
parse_common(p0)
|
||||||
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)
|
|
||||||
|
|
||||||
p4 = sub.add_parser("python-venv-create"); parse_common(p4)
|
p1 = sub.add_parser("dotnet-build")
|
||||||
_ = p4.add_argument("--venv-dir", default=".venv")
|
parse_common(p1)
|
||||||
|
|
||||||
p5 = sub.add_parser("python-pip-install"); parse_common(p5)
|
p1b = sub.add_parser("dotnet-test")
|
||||||
_ = p5.add_argument("--requirements", required=True)
|
parse_common(p1b)
|
||||||
|
|
||||||
p5b = sub.add_parser("python-pip-sync"); parse_common(p5b)
|
p1c = sub.add_parser("dotnet-publish")
|
||||||
_ = p5b.add_argument("--requirements", required=True)
|
parse_common(p1c)
|
||||||
|
|
||||||
p5c = sub.add_parser("python-pytest"); parse_common(p5c)
|
p2 = sub.add_parser("npm-install")
|
||||||
p6 = sub.add_parser("cargo-build"); parse_common(p6)
|
parse_common(p2)
|
||||||
p6b = sub.add_parser("cargo-test"); parse_common(p6b)
|
|
||||||
|
|
||||||
p7 = sub.add_parser("tauri-build"); parse_common(p7)
|
p2b = sub.add_parser("npm-ci")
|
||||||
_ = p7.add_argument("--no-bundle", action="store_true")
|
parse_common(p2b)
|
||||||
|
|
||||||
p8 = sub.add_parser("git-status"); parse_common(p8)
|
p3 = sub.add_parser("npm-build")
|
||||||
p9 = sub.add_parser("git-fetch"); parse_common(p9)
|
parse_common(p3)
|
||||||
p10 = sub.add_parser("git-pull"); parse_common(p10)
|
|
||||||
p11 = sub.add_parser("git-clean"); parse_common(p11)
|
p3b = sub.add_parser("npm-test")
|
||||||
p12 = sub.add_parser("docker-build"); parse_common(p12)
|
parse_common(p3b)
|
||||||
p13 = sub.add_parser("docker-compose-up"); parse_common(p13)
|
|
||||||
p14 = sub.add_parser("docker-compose-down"); parse_common(p14)
|
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)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@ -559,8 +590,7 @@ def main() -> int:
|
|||||||
code, summary = handlers[args.action](args)
|
code, summary = handlers[args.action](args)
|
||||||
if args.json:
|
if args.json:
|
||||||
print(json.dumps(summary))
|
print(json.dumps(summary))
|
||||||
|
return code
|
||||||
return int(code)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,42 +1,90 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
from typing import cast
|
|
||||||
from script_common import ensure_npm_build, find_node_app_root, resolve_repo_root
|
|
||||||
|
from script_common import find_node_app_root, resolve_repo_root, run, sha256_files
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Cross-platform web/tauri publish helper")
|
parser = argparse.ArgumentParser(description="Cross-platform web/tauri publish helper")
|
||||||
_ = parser.add_argument("--target", choices=["web", "tauri"], default="web")
|
parser.add_argument("--target", choices=["web", "tauri"], default="web")
|
||||||
_ = parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||||
_ = parser.add_argument("--tauri-bundles", choices=["none", "nsis", "msi"], default="none")
|
parser.add_argument("--tauri-bundles", choices=["none", "nsis", "msi"], default="none")
|
||||||
_ = parser.add_argument("--install-deps", action="store_true")
|
parser.add_argument("--install-deps", action="store_true")
|
||||||
_ = parser.add_argument("--skip-install", action="store_true")
|
parser.add_argument("--skip-install", action="store_true")
|
||||||
_ = parser.add_argument("--dry-run", action="store_true")
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
_ = parser.add_argument("--repo-root", default=None)
|
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("--app-root", default=None, help="Relative or absolute app root with package.json")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
repo_root_val = cast(str | None, args.repo_root)
|
repo_root = resolve_repo_root(args.repo_root)
|
||||||
repo_root = resolve_repo_root(repo_root_val)
|
app_root = find_node_app_root(repo_root, args.app_root)
|
||||||
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:
|
if app_root is None:
|
||||||
print("Unable to locate app root (no unique package.json found).")
|
print("Unable to locate app root (no unique package.json found).")
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
# If dry-run is requested, we just print intent.
|
package_json = app_root / "package.json"
|
||||||
if args.dry_run:
|
lock_file = app_root / "package-lock.json"
|
||||||
print(f"Dry-run: Would build {args.target} ({args.configuration}) in {app_root}")
|
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)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
res = ensure_npm_build(
|
tauri_cmd = ["run", "tauri", "build"]
|
||||||
app_root=app_root,
|
tauri_tail: list[str] = []
|
||||||
target=str(args.target),
|
if args.tauri_bundles == "none":
|
||||||
configuration=str(args.configuration),
|
tauri_tail.extend(["--no-bundle"])
|
||||||
tauri_bundles=str(args.tauri_bundles)
|
else:
|
||||||
)
|
tauri_tail.extend(["--bundles", args.tauri_bundles])
|
||||||
|
if args.configuration == "Debug":
|
||||||
|
tauri_tail.append("--debug")
|
||||||
|
if tauri_tail:
|
||||||
|
tauri_cmd.extend(["--", *tauri_tail])
|
||||||
|
|
||||||
return int(res["exit_code"])
|
print("$ npm " + " ".join(tauri_cmd))
|
||||||
|
if not args.dry_run:
|
||||||
|
return run("npm", tauri_cmd, app_root)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,11 +1,22 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import json
|
import json
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from script_common import find_csproj_by_keyword, find_node_app_root, resolve_repo_root, run # type: ignore
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
def has_package_script(app_root: Path, script_name: str) -> bool:
|
def has_package_script(app_root: Path, script_name: str) -> bool:
|
||||||
package_json = app_root / "package.json"
|
package_json = app_root / "package.json"
|
||||||
@ -24,18 +35,18 @@ def has_package_script(app_root: Path, script_name: str) -> bool:
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Publish bundled outputs using Python script entrypoints")
|
parser = argparse.ArgumentParser(description="Publish bundled outputs using Python script entrypoints")
|
||||||
_ = parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||||
_ = parser.add_argument("--runtime", default="win-x64")
|
parser.add_argument("--runtime", default="win-x64")
|
||||||
_ = parser.add_argument("--skip-sidecar", action="store_true")
|
parser.add_argument("--skip-sidecar", action="store_true")
|
||||||
_ = parser.add_argument("--skip-web", action="store_true")
|
parser.add_argument("--skip-web", action="store_true")
|
||||||
_ = parser.add_argument("--skip-webgateway", action="store_true")
|
parser.add_argument("--skip-webgateway", action="store_true")
|
||||||
_ = parser.add_argument("--skip-tauri", action="store_true")
|
parser.add_argument("--skip-tauri", action="store_true")
|
||||||
_ = parser.add_argument("--dry-run", action="store_true")
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
_ = parser.add_argument("--repo-root", default=None)
|
parser.add_argument("--repo-root", default=None)
|
||||||
_ = parser.add_argument("--sidecar-project", default=None)
|
parser.add_argument("--sidecar-project", default=None)
|
||||||
_ = parser.add_argument("--gateway-project", default=None)
|
parser.add_argument("--gateway-project", default=None)
|
||||||
_ = parser.add_argument("--app-root", default=None)
|
parser.add_argument("--app-root", default=None)
|
||||||
_ = parser.add_argument("--output-dir", default="output")
|
parser.add_argument("--output-dir", default="output")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
repo_root = resolve_repo_root(args.repo_root)
|
repo_root = resolve_repo_root(args.repo_root)
|
||||||
@ -45,48 +56,45 @@ def main() -> int:
|
|||||||
sidecar_project = (repo_root / args.sidecar_project).resolve() if args.sidecar_project else find_csproj_by_keyword(repo_root, ["sidecar"])
|
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"])
|
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)
|
app_root = (repo_root / args.app_root).resolve() if args.app_root else find_node_app_root(repo_root, None)
|
||||||
|
|
||||||
tauri_conf = None
|
tauri_conf = None
|
||||||
if app_root is not None:
|
if app_root is not None:
|
||||||
tauri_conf = next((p for p in [app_root/"src-tauri"/"tauri.conf.json", app_root/"tauri.conf.json"] if p.exists()), 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
|
||||||
|
|
||||||
py = sys.executable
|
py = sys.executable
|
||||||
scripts_dir = Path(__file__).parent
|
|
||||||
|
|
||||||
if not args.skip_sidecar:
|
if not args.skip_sidecar:
|
||||||
if sidecar_project is None:
|
if sidecar_project is None:
|
||||||
print("Skipping sidecar: no sidecar csproj detected.")
|
print("Skipping sidecar: no sidecar csproj detected.")
|
||||||
else:
|
else:
|
||||||
cmd = ["-m", "scripts.publish-sidecar" if __package__ else "publish-sidecar",
|
cmd = [py, "scripts/publish-sidecar.py", "--configuration", args.configuration, "--runtime", args.runtime]
|
||||||
"--configuration", args.configuration, "--runtime", args.runtime, "--project", str(sidecar_project)]
|
cmd.extend(["--project", str(sidecar_project)])
|
||||||
print(f"\n> Publishing Sidecar\n$ {py} {' '.join(cmd)}")
|
code = run_step("Publish sidecar", cmd, repo_root, args.dry_run)
|
||||||
if not args.dry_run:
|
if code != 0:
|
||||||
code = run(py, cmd, scripts_dir)
|
return code
|
||||||
if code != 0: return code
|
|
||||||
|
|
||||||
if not args.skip_web:
|
if not args.skip_web:
|
||||||
if app_root is None:
|
if app_root is None:
|
||||||
print("Skipping web: no app root detected.")
|
print("Skipping web: no app root with package.json detected.")
|
||||||
elif not has_package_script(app_root, "build"):
|
elif not has_package_script(app_root, "build"):
|
||||||
print("Skipping web: package.json has no 'build' script.")
|
print("Skipping web: package.json has no 'build' script.")
|
||||||
else:
|
else:
|
||||||
cmd = ["-m", "scripts.publish-app" if __package__ else "publish-app",
|
cmd = [py, "scripts/publish-app.py", "--target", "web", "--configuration", args.configuration, "--app-root", str(app_root)]
|
||||||
"--target", "web", "--configuration", args.configuration, "--app-root", str(app_root)]
|
code = run_step("Build web", cmd, repo_root, args.dry_run)
|
||||||
print(f"\n> Building Web\n$ {py} {' '.join(cmd)}")
|
if code != 0:
|
||||||
if not args.dry_run:
|
return code
|
||||||
code = run(py, cmd, scripts_dir)
|
|
||||||
if code != 0: return code
|
|
||||||
|
|
||||||
if not args.skip_webgateway:
|
if not args.skip_webgateway:
|
||||||
if gateway_project is None:
|
if gateway_project is None:
|
||||||
print("Skipping web gateway: no gateway csproj detected.")
|
print("Skipping web gateway: no gateway csproj detected.")
|
||||||
else:
|
else:
|
||||||
cmd = ["-m", "scripts.publish-webgateway" if __package__ else "publish-webgateway",
|
cmd = [py, "scripts/publish-webgateway.py", "--configuration", args.configuration, "--runtime", args.runtime, "--project", str(gateway_project)]
|
||||||
"--configuration", args.configuration, "--runtime", args.runtime, "--project", str(gateway_project)]
|
code = run_step("Publish web gateway", cmd, repo_root, args.dry_run)
|
||||||
print(f"\n> Publishing Web Gateway\n$ {py} {' '.join(cmd)}")
|
if code != 0:
|
||||||
if not args.dry_run:
|
return code
|
||||||
code = run(py, cmd, scripts_dir)
|
|
||||||
if code != 0: return code
|
|
||||||
|
|
||||||
if not args.skip_tauri:
|
if not args.skip_tauri:
|
||||||
if app_root is None or tauri_conf is None:
|
if app_root is None or tauri_conf is None:
|
||||||
@ -94,20 +102,17 @@ def main() -> int:
|
|||||||
elif not has_package_script(app_root, "tauri"):
|
elif not has_package_script(app_root, "tauri"):
|
||||||
print("Skipping tauri: package.json has no 'tauri' script.")
|
print("Skipping tauri: package.json has no 'tauri' script.")
|
||||||
else:
|
else:
|
||||||
cmd = ["-m", "scripts.publish-app" if __package__ else "publish-app",
|
cmd = [py, "scripts/publish-app.py", "--target", "tauri", "--configuration", args.configuration, "--tauri-bundles", "none", "--app-root", str(app_root)]
|
||||||
"--target", "tauri", "--configuration", args.configuration, "--tauri-bundles", "none", "--app-root", str(app_root)]
|
code = run_step("Build tauri", cmd, repo_root, args.dry_run)
|
||||||
print(f"\n> Building Tauri\n$ {py} {' '.join(cmd)}")
|
if code != 0:
|
||||||
if not args.dry_run:
|
return code
|
||||||
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")
|
target_dir = app_root / "src-tauri" / "target" / ("debug" if args.configuration == "Debug" else "release")
|
||||||
pattern = "*.exe" if os.name == "nt" else "*"
|
exes = sorted(target_dir.glob("*.exe"), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||||
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:
|
if exes:
|
||||||
staged = output_root / exes[0].name
|
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:
|
else:
|
||||||
shutil.copy2(exes[0], staged)
|
shutil.copy2(exes[0], staged)
|
||||||
print(f"Staged desktop executable: {staged}")
|
print(f"Staged desktop executable: {staged}")
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
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,48 +1,51 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
from typing import cast
|
|
||||||
from script_common import ensure_dotnet_publish, find_csproj_by_keyword, resolve_repo_root, SdtResult
|
|
||||||
|
from script_common import dotnet_env, find_csproj_by_keyword, resolve_repo_root, run
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Cross-platform .NET sidecar publish helper")
|
parser = argparse.ArgumentParser(description="Cross-platform .NET sidecar publish helper")
|
||||||
_ = parser.add_argument("--configuration", default="Release")
|
parser.add_argument("--configuration", default="Release")
|
||||||
_ = parser.add_argument("--runtime", default="win-x64")
|
parser.add_argument("--runtime", default="win-x64")
|
||||||
_ = parser.add_argument("--repo-root", default=None)
|
parser.add_argument("--repo-root", default=None)
|
||||||
_ = parser.add_argument("--project", default=None, help="Relative/absolute path to sidecar csproj")
|
parser.add_argument("--project", default=None, help="Relative/absolute path to sidecar csproj")
|
||||||
_ = parser.add_argument("--output-dir", default="output")
|
parser.add_argument("--output-dir", default="output")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
repo_root_val = cast(str | None, args.repo_root)
|
repo_root = resolve_repo_root(args.repo_root)
|
||||||
repo_root = resolve_repo_root(repo_root_val)
|
output_dir = (repo_root / args.output_dir).resolve()
|
||||||
output_dir_val = cast(str, args.output_dir)
|
|
||||||
output_dir = (repo_root / output_dir_val).resolve()
|
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
project_val = cast(str | None, args.project)
|
if args.project:
|
||||||
if project_val:
|
csproj = (repo_root / args.project).resolve()
|
||||||
csproj = (repo_root / project_val).resolve()
|
|
||||||
else:
|
else:
|
||||||
csproj = find_csproj_by_keyword(repo_root, ["sidecar"])
|
csproj = find_csproj_by_keyword(repo_root, ["sidecar"])
|
||||||
|
|
||||||
if csproj is None or not csproj.exists():
|
if csproj is None or not csproj.exists():
|
||||||
print("Could not locate sidecar project. Pass --project <path/to/project.csproj>.")
|
print("Could not locate sidecar project. Pass --project <path/to/project.csproj>.")
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
res: SdtResult = ensure_dotnet_publish(
|
publish_args = [
|
||||||
csproj=csproj,
|
"publish",
|
||||||
output_dir=output_dir,
|
str(csproj),
|
||||||
configuration=str(args.configuration),
|
"-c",
|
||||||
runtime=str(args.runtime),
|
args.configuration,
|
||||||
single_file=True,
|
"-r",
|
||||||
self_contained=True
|
args.runtime,
|
||||||
)
|
"--self-contained",
|
||||||
|
"-p:PublishSingleFile=true",
|
||||||
if res["exit_code"] != 0:
|
"-p:IncludeNativeLibrariesForSelfExtract=true",
|
||||||
return int(res["exit_code"])
|
"-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
|
||||||
|
|
||||||
runtime_val = str(args.runtime)
|
binary_name = csproj.stem + (".exe" if args.runtime.startswith("win-") else "")
|
||||||
binary_name = csproj.stem + (".exe" if runtime_val.startswith("win-") else "")
|
|
||||||
binary_path = output_dir / binary_name
|
binary_path = output_dir / binary_name
|
||||||
if binary_path.exists():
|
if binary_path.exists():
|
||||||
print(f"Published executable: {binary_path}")
|
print(f"Published executable: {binary_path}")
|
||||||
|
|||||||
@ -1,67 +1,74 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
import shutil
|
import shutil
|
||||||
from typing import cast
|
|
||||||
from script_common import ensure_dotnet_publish, find_csproj_by_keyword, resolve_repo_root, SdtResult
|
from script_common import dotnet_env, find_csproj_by_keyword, resolve_repo_root, run
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Cross-platform ASP.NET gateway publish helper")
|
parser = argparse.ArgumentParser(description="Cross-platform ASP.NET gateway publish helper")
|
||||||
_ = parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release")
|
||||||
_ = parser.add_argument("--runtime", default="win-x64")
|
parser.add_argument("--runtime", default="win-x64")
|
||||||
_ = parser.add_argument("--self-contained", action="store_true")
|
parser.add_argument("--self-contained", action="store_true")
|
||||||
_ = parser.add_argument("--skip-web-assets", action="store_true")
|
parser.add_argument("--skip-web-assets", action="store_true")
|
||||||
_ = parser.add_argument("--repo-root", default=None)
|
parser.add_argument("--repo-root", default=None)
|
||||||
_ = parser.add_argument("--project", default=None, help="Relative/absolute path to gateway csproj")
|
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("--web-build-dir", default=None, help="Relative path to web build assets root")
|
||||||
_ = parser.add_argument("--output-dir", default="output/webgateway")
|
parser.add_argument("--output-dir", default="output/webgateway")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
repo_root_val = cast(str | None, args.repo_root)
|
repo_root = resolve_repo_root(args.repo_root)
|
||||||
repo_root = resolve_repo_root(repo_root_val)
|
output_dir = (repo_root / args.output_dir).resolve()
|
||||||
output_dir_val = cast(str, args.output_dir)
|
|
||||||
output_dir = (repo_root / output_dir_val).resolve()
|
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
project_val = cast(str | None, args.project)
|
if args.project:
|
||||||
if project_val:
|
csproj = (repo_root / args.project).resolve()
|
||||||
csproj = (repo_root / project_val).resolve()
|
|
||||||
else:
|
else:
|
||||||
csproj = find_csproj_by_keyword(repo_root, ["webgateway", "gateway"])
|
csproj = find_csproj_by_keyword(repo_root, ["webgateway", "gateway"])
|
||||||
|
|
||||||
if csproj is None or not csproj.exists():
|
if csproj is None or not csproj.exists():
|
||||||
print("Could not locate web gateway project. Pass --project <path/to/project.csproj>.")
|
print("Could not locate web gateway project. Pass --project <path/to/project.csproj>.")
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
res: SdtResult = ensure_dotnet_publish(
|
publish_args = [
|
||||||
csproj=csproj,
|
"publish",
|
||||||
output_dir=output_dir,
|
str(csproj),
|
||||||
configuration=str(args.configuration),
|
"-c",
|
||||||
runtime=str(args.runtime),
|
args.configuration,
|
||||||
self_contained=bool(args.self_contained),
|
"-r",
|
||||||
single_file=False
|
args.runtime,
|
||||||
)
|
"--self-contained",
|
||||||
|
"true" if args.self_contained else "false",
|
||||||
if res["exit_code"] != 0:
|
"-p:RestoreIgnoreFailedSources=true",
|
||||||
return int(res["exit_code"])
|
"-p:NuGetAudit=false",
|
||||||
|
"-o",
|
||||||
|
str(output_dir),
|
||||||
|
]
|
||||||
|
code = run("dotnet", publish_args, repo_root, env=dotnet_env(repo_root))
|
||||||
|
if code != 0:
|
||||||
|
return code
|
||||||
|
|
||||||
if not args.skip_web_assets:
|
if not args.skip_web_assets:
|
||||||
web_build_dir_val = cast(str | None, args.web_build_dir)
|
if args.web_build_dir:
|
||||||
if web_build_dir_val:
|
web_build_dir = (repo_root / args.web_build_dir).resolve()
|
||||||
web_build_dir = (repo_root / web_build_dir_val).resolve()
|
|
||||||
else:
|
else:
|
||||||
# Look for recent web build output
|
web_build_dir = next((p.parent for p in repo_root.rglob("package.json") if (p.parent / "build").exists()), None)
|
||||||
# (Note: rglob is costly but necessary for discovery here)
|
if web_build_dir is not None:
|
||||||
web_pj = next((p.parent for p in repo_root.rglob("package.json") if (p.parent / "build").exists()), None)
|
web_build_dir = web_build_dir / "build"
|
||||||
web_build_dir = web_pj / "build" if web_pj else None
|
|
||||||
|
|
||||||
if web_build_dir is None or not web_build_dir.exists():
|
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.")
|
print("Web assets not found. Skip with --skip-web-assets or pass --web-build-dir.")
|
||||||
else:
|
else:
|
||||||
web_out = output_dir / "wwwroot"
|
web_out = output_dir / "wwwroot"
|
||||||
print(f"Copying web assets: {web_build_dir} -> {web_out}")
|
web_out.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.copytree(web_build_dir, web_out, dirs_exist_ok=True)
|
for item in web_build_dir.iterdir():
|
||||||
print(f"Copied web assets to {web_out}")
|
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"Publish completed: {output_dir}")
|
print(f"Publish completed: {output_dir}")
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@ -1,133 +0,0 @@
|
|||||||
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,298 +6,357 @@ import pathlib
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
from typing import Dict, Iterable, List, Sequence
|
||||||
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 = [
|
PROXY_VARS = [
|
||||||
"HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy",
|
"HTTP_PROXY",
|
||||||
"GIT_HTTP_PROXY", "GIT_HTTPS_PROXY", "PIP_NO_INDEX"
|
"HTTPS_PROXY",
|
||||||
|
"ALL_PROXY",
|
||||||
|
"http_proxy",
|
||||||
|
"https_proxy",
|
||||||
|
"all_proxy",
|
||||||
|
"GIT_HTTP_PROXY",
|
||||||
|
"GIT_HTTPS_PROXY",
|
||||||
|
"PIP_NO_INDEX",
|
||||||
]
|
]
|
||||||
|
|
||||||
# --- Domain: FS Utilities ---
|
|
||||||
|
|
||||||
def sha256_files(paths: Iterable[pathlib.Path]) -> str:
|
def resolve_repo_root(start: str | None = None) -> pathlib.Path:
|
||||||
h = hashlib.sha256()
|
base = pathlib.Path(start or os.getcwd()).resolve()
|
||||||
for p in sorted(paths):
|
|
||||||
if p.exists(): h.update(p.read_bytes())
|
|
||||||
return h.hexdigest()
|
|
||||||
|
|
||||||
def first_existing(paths: Iterable[pathlib.Path]) -> Optional[pathlib.Path]:
|
# 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:
|
||||||
for p in paths:
|
for p in paths:
|
||||||
if p.exists(): return p
|
p.mkdir(parents=True, exist_ok=True)
|
||||||
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 ensure_dirs(paths: list[pathlib.Path]) -> None:
|
def clean_proxy_env(env: Dict[str, str]) -> None:
|
||||||
for p in paths: p.mkdir(parents=True, exist_ok=True)
|
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 _hint_matches(root: pathlib.Path, hint: str) -> bool:
|
def _hint_matches(root: pathlib.Path, hint: str) -> bool:
|
||||||
h = hint.strip()
|
h = hint.strip()
|
||||||
if not h:
|
if not h:
|
||||||
return False
|
return False
|
||||||
try:
|
|
||||||
has_glob = any(ch in h for ch in ("*", "?", "["))
|
|
||||||
if has_glob:
|
|
||||||
if any(root.glob(h)):
|
|
||||||
return True
|
|
||||||
return any(root.rglob(h))
|
|
||||||
|
|
||||||
marker = root / h
|
has_glob = any(ch in h for ch in ("*", "?", "["))
|
||||||
if marker.exists():
|
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 True
|
||||||
|
return any(root.rglob(h))
|
||||||
|
|
||||||
# If hint is a plain filename marker, allow bounded search in root tree.
|
marker = root / h
|
||||||
if not any(sep in h for sep in ("/", "\\")):
|
if marker.exists():
|
||||||
return any(p.name == h for p in root.rglob(h))
|
return True
|
||||||
|
|
||||||
return False
|
# If hint is just a filename marker, look bounded in tree.
|
||||||
except:
|
if not any(sep in h for sep in ("\\", "/")):
|
||||||
return False
|
return any(p.name == h for p in root.rglob(h))
|
||||||
|
|
||||||
# --- Domain: Project Discovery ---
|
return False
|
||||||
|
|
||||||
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]:
|
def _expand_windows_path_segment(segment: str) -> str:
|
||||||
if cfg is None:
|
expanded = segment
|
||||||
sdt_configs = list(repo_root.glob("sdtconfig-*.json"))
|
# Expand %VAR% tokens repeatedly for nested references.
|
||||||
cfg = sdt_configs[0] if sdt_configs else (repo_root / "devtool.json")
|
for _ in range(4):
|
||||||
if not cfg.exists(): return []
|
next_value = os.path.expandvars(expanded)
|
||||||
try:
|
if next_value == expanded:
|
||||||
data = json.loads(cfg.read_text(encoding="utf-8"))
|
break
|
||||||
hints = data.get("project", {}).get("rootHints", [])
|
expanded = next_value
|
||||||
return [str(x) for x in hints if isinstance(x, str) and x.strip()]
|
return expanded
|
||||||
except: return []
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def load_node_working_dir(repo_root: pathlib.Path) -> Optional[str]:
|
def _which_windows(command: str) -> str | None:
|
||||||
cfg = _resolve_project_config_path(repo_root)
|
found = shutil.which(command)
|
||||||
if cfg is None:
|
if found:
|
||||||
|
return found
|
||||||
|
|
||||||
|
if os.name != "nt":
|
||||||
return None
|
return None
|
||||||
try:
|
|
||||||
data = json.loads(cfg.read_text(encoding="utf-8"))
|
path_value = os.environ.get("PATH", "")
|
||||||
node = data.get("toolchains", {}).get("node", {})
|
pathext = os.environ.get("PATHEXT", ".COM;.EXE;.BAT;.CMD")
|
||||||
value = node.get("workingDir")
|
exts = [e.lower() for e in pathext.split(";") if e]
|
||||||
if isinstance(value, str) and value.strip():
|
|
||||||
return value.strip()
|
has_ext = pathlib.Path(command).suffix != ""
|
||||||
except:
|
names = [command] if has_ext else [command, *(command + e.lower() for e in exts)]
|
||||||
pass
|
|
||||||
|
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
|
return None
|
||||||
|
|
||||||
def find_csproj(repo_root: pathlib.Path, hints: Sequence[str] | None = None) -> Optional[pathlib.Path]:
|
|
||||||
|
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:
|
if hints:
|
||||||
for h in hints:
|
for hint in hints:
|
||||||
p = (repo_root / h).resolve()
|
candidate = (repo_root / hint).resolve()
|
||||||
if p.exists() and p.suffix == ".csproj": return p
|
if candidate.exists() and candidate.suffix.lower() == ".csproj":
|
||||||
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 candidate
|
||||||
return hits[0] if len(hits) == 1 else None
|
|
||||||
|
|
||||||
def find_csproj_by_keyword(repo_root: pathlib.Path, keywords: Sequence[str]) -> Optional[pathlib.Path]:
|
csprojs = sorted(repo_root.rglob("*.csproj"))
|
||||||
hits = [h for h in repo_root.rglob("*.csproj") if not any(x in h.parts for x in [".git", "node_modules", "bin", "obj"])]
|
if not csprojs:
|
||||||
for kw in keywords:
|
return None
|
||||||
matches = [h for h in hits if kw.lower() in h.name.lower()]
|
if len(csprojs) == 1:
|
||||||
if len(matches) == 1: return matches[0]
|
return csprojs[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
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:
|
def find_csproj_by_keyword(repo_root: pathlib.Path, keywords: Sequence[str]) -> pathlib.Path | None:
|
||||||
preferred = load_node_working_dir(repo_root)
|
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():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
data = json.loads(package_json.read_text(encoding="utf-8"))
|
||||||
|
return data if isinstance(data, dict) else None
|
||||||
|
except Exception:
|
||||||
|
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 _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 _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
|
||||||
|
|
||||||
if preferred:
|
if preferred:
|
||||||
p = (repo_root / preferred).resolve()
|
p = (repo_root / preferred).resolve()
|
||||||
p = p.parent if p.is_file() else p
|
package_json = p / "package.json"
|
||||||
if (p / "package.json").exists():
|
if package_json.exists():
|
||||||
return p
|
# 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")):
|
||||||
|
return p
|
||||||
|
|
||||||
tauri = [p.parent for p in _iter_pj() if _is_tauri(p.parent)]
|
package_files = _iter_package_jsons()
|
||||||
if len(tauri) == 1: return tauri[0]
|
if not package_files:
|
||||||
if len(tauri) > 1:
|
return None
|
||||||
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()]
|
# Strong preference: a tauri app root with tauri config and package.json.
|
||||||
return all_pj[0] if len(all_pj) == 1 else None
|
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]
|
||||||
|
|
||||||
# --- Domain: Environment Setup ---
|
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 clean_proxy_env(env: dict[str, str]) -> None:
|
# As a last fallback, return unique package root only.
|
||||||
for k in PROXY_VARS: env.pop(k, None)
|
if len(package_files) == 1:
|
||||||
|
return package_files[0].parent
|
||||||
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
|
return None
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def run_capture(command: str, args: Sequence[str], cwd: pathlib.Path, env: dict[str, str] | None = None) -> tuple[int, str, str]:
|
def newest_file(search_root: pathlib.Path, pattern: str) -> pathlib.Path | None:
|
||||||
try:
|
if not search_root.exists():
|
||||||
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 None
|
||||||
return proc.returncode, proc.stdout, proc.stderr
|
files = [p for p in search_root.rglob(pattern) if p.is_file() and "\\obj\\" not in str(p).replace("/", "\\")]
|
||||||
except: return 127, "", "Command not found"
|
if not files:
|
||||||
|
return None
|
||||||
def _safe_stream_write(stream: Any, text: str) -> None:
|
files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||||
if not text:
|
return files[0]
|
||||||
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
|
|
||||||
|
|
||||||
# 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
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from script_common import newest_file, resolve_repo_root # type: ignore
|
from script_common import newest_file, resolve_repo_root
|
||||||
|
|
||||||
|
|
||||||
def copy_tree_contents(src: Path, dst: Path) -> None:
|
def copy_tree_contents(src: Path, dst: Path) -> None:
|
||||||
@ -21,79 +21,55 @@ def copy_tree_contents(src: Path, dst: Path) -> None:
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Sync newest built assets into output folder")
|
parser = argparse.ArgumentParser(description="Sync newest built assets into output folder")
|
||||||
_ = parser.add_argument("--repo-root", default=None)
|
parser.add_argument("--repo-root", default=None)
|
||||||
_ = parser.add_argument("--output-dir", default="output")
|
parser.add_argument("--output-dir", default="output")
|
||||||
_ = parser.add_argument("--web-build-dir", default=None, help="Path to web build 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("--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("--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("--tauri-target-dir", default=None, help="Path to tauri target root")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
repo_root = resolve_repo_root(args.repo_root)
|
repo_root = resolve_repo_root(args.repo_root)
|
||||||
output_dir = (repo_root / args.output_dir).resolve()
|
output_dir = (repo_root / args.output_dir).resolve()
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
web_build_dir_val = args.web_build_dir
|
web_build = (repo_root / args.web_build_dir).resolve() if args.web_build_dir else None
|
||||||
web_build = (repo_root / web_build_dir_val).resolve() if web_build_dir_val else None
|
|
||||||
if web_build is None:
|
if web_build is None:
|
||||||
web_build = next((p for p in repo_root.rglob("build") if (p.parent / "package.json").exists()), 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():
|
if web_build is not None and web_build.exists():
|
||||||
web_out = output_dir / "webgateway" / "wwwroot"
|
web_out = output_dir / "webgateway" / "wwwroot"
|
||||||
copy_tree_contents(web_build, web_out)
|
copy_tree_contents(web_build, web_out)
|
||||||
print(f"Synced web assets -> {web_out}")
|
print(f"Synced web assets -> {web_out}")
|
||||||
|
|
||||||
sidecar_bin_dir_val = args.sidecar_bin_dir
|
sidecar_bin = (repo_root / args.sidecar_bin_dir).resolve() if args.sidecar_bin_dir else None
|
||||||
sidecar_bin = (repo_root / sidecar_bin_dir_val).resolve() if sidecar_bin_dir_val else None
|
|
||||||
if sidecar_bin is 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_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
|
sidecar_bin = sidecar_proj / "bin" if sidecar_proj else None
|
||||||
|
|
||||||
if sidecar_bin is not None:
|
if sidecar_bin is not None:
|
||||||
sidecar_exe = None
|
sidecar_pattern = "*.exe" if os.name == "nt" else "*"
|
||||||
if os.name == "nt":
|
sidecar_exe = newest_file(sidecar_bin, sidecar_pattern)
|
||||||
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:
|
if sidecar_exe is not None:
|
||||||
copy_tree_contents(sidecar_exe.parent, output_dir)
|
copy_tree_contents(sidecar_exe.parent, output_dir)
|
||||||
print(f"Synced sidecar -> {output_dir}")
|
print(f"Synced sidecar -> {output_dir}")
|
||||||
|
|
||||||
gateway_bin_dir_val = args.gateway_bin_dir
|
gateway_bin = (repo_root / args.gateway_bin_dir).resolve() if args.gateway_bin_dir else None
|
||||||
gateway_bin = (repo_root / gateway_bin_dir_val).resolve() if gateway_bin_dir_val else None
|
|
||||||
if gateway_bin is 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_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
|
gateway_bin = gateway_proj / "bin" if gateway_proj else None
|
||||||
|
|
||||||
if gateway_bin is not None:
|
if gateway_bin is not None:
|
||||||
gw_exe = None
|
gateway_pattern = "*.exe" if os.name == "nt" else "*"
|
||||||
if os.name == "nt":
|
gw_exe = newest_file(gateway_bin, gateway_pattern)
|
||||||
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:
|
if gw_exe is not None:
|
||||||
gw_out = output_dir / "webgateway"
|
gw_out = output_dir / "webgateway"
|
||||||
copy_tree_contents(gw_exe.parent, gw_out)
|
copy_tree_contents(gw_exe.parent, gw_out)
|
||||||
print(f"Synced gateway -> {gw_out}")
|
print(f"Synced gateway -> {gw_out}")
|
||||||
|
|
||||||
tauri_target_dir_val = args.tauri_target_dir
|
tauri_target = (repo_root / args.tauri_target_dir).resolve() if args.tauri_target_dir else None
|
||||||
tauri_target = (repo_root / tauri_target_dir_val).resolve() if tauri_target_dir_val else None
|
|
||||||
if tauri_target is None:
|
if tauri_target is None:
|
||||||
tauri_src = next((p for p in repo_root.rglob("src-tauri") if (p / "target").exists()), None)
|
tauri_target = 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
|
tauri_target = tauri_target / "target" if tauri_target else None
|
||||||
|
|
||||||
if tauri_target is not None:
|
if tauri_target is not None:
|
||||||
app_exe = None
|
app_exe = newest_file(tauri_target, "*.exe")
|
||||||
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:
|
if app_exe is not None:
|
||||||
shutil.copy2(app_exe, output_dir / app_exe.name)
|
shutil.copy2(app_exe, output_dir / app_exe.name)
|
||||||
print(f"Synced desktop app ({app_exe.name}) -> {output_dir}")
|
print(f"Synced desktop app ({app_exe.name}) -> {output_dir}")
|
||||||
|
|||||||
@ -5,23 +5,23 @@ import pathlib
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Optional, Sequence
|
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
||||||
|
|
||||||
from script_common import resolve_command, resolve_repo_root
|
from script_common import resolve_command, resolve_repo_root
|
||||||
|
|
||||||
|
|
||||||
def load_config(project_root: pathlib.Path) -> dict[str, Any]:
|
def load_config(project_root: pathlib.Path) -> dict:
|
||||||
sdt_configs = list(project_root.glob("sdtconfig-*.json"))
|
config_path = project_root / "devtool.json"
|
||||||
config_path = sdt_configs[0] if sdt_configs else (project_root / "devtool.json")
|
|
||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
raise FileNotFoundError(f"Project config not found at: {config_path}")
|
raise FileNotFoundError(f"devtool.json not found at: {config_path}")
|
||||||
return json.loads(config_path.read_text(encoding="utf-8"))
|
return json.loads(config_path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def iter_workflows(config: dict[str, Any], selected: Optional[set[str]]) -> list[dict[str, Any]]:
|
def iter_workflows(config: dict, selected: Optional[set[str]]) -> List[dict]:
|
||||||
workflows = config.get("workflows", [])
|
workflows = config.get("workflows", [])
|
||||||
if not isinstance(workflows, list):
|
if not isinstance(workflows, list):
|
||||||
return []
|
return []
|
||||||
normalized: list[dict[str, Any]] = [w for w in workflows if isinstance(w, dict) and isinstance(w.get("id"), str)]
|
normalized: List[dict] = [w for w in workflows if isinstance(w, dict) and isinstance(w.get("id"), str)]
|
||||||
if selected:
|
if selected:
|
||||||
normalized = [w for w in normalized if w["id"] in selected]
|
normalized = [w for w in normalized if w["id"] in selected]
|
||||||
return normalized
|
return normalized
|
||||||
@ -45,25 +45,21 @@ def resolve_script_arg(project_root: pathlib.Path, working_dir: pathlib.Path, ar
|
|||||||
return b
|
return b
|
||||||
|
|
||||||
|
|
||||||
def static_check_workflow(project_root: pathlib.Path, workflow: dict[str, Any]) -> dict[str, Any]:
|
def static_check_workflow(project_root: pathlib.Path, workflow: dict) -> dict:
|
||||||
result: dict[str, Any] = {
|
result = {
|
||||||
"workflowId": workflow.get("id"),
|
"workflowId": workflow.get("id"),
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"issues": [],
|
"issues": [],
|
||||||
"steps": [],
|
"steps": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
steps = workflow.get("steps", [])
|
for step in workflow.get("steps", []):
|
||||||
if not isinstance(steps, list):
|
|
||||||
return result
|
|
||||||
|
|
||||||
for step in steps:
|
|
||||||
if not isinstance(step, dict):
|
if not isinstance(step, dict):
|
||||||
continue
|
continue
|
||||||
step_id = step.get("id", "<unknown>")
|
step_id = step.get("id", "<unknown>")
|
||||||
step_result: dict[str, Any] = {"stepId": step_id, "ok": True, "issues": []}
|
step_result = {"stepId": step_id, "ok": True, "issues": []}
|
||||||
|
|
||||||
working_dir_rel = str(step.get("workingDir") or ".")
|
working_dir_rel = step.get("workingDir") or "."
|
||||||
working_dir = (project_root / working_dir_rel).resolve()
|
working_dir = (project_root / working_dir_rel).resolve()
|
||||||
if not working_dir.exists():
|
if not working_dir.exists():
|
||||||
step_result["ok"] = False
|
step_result["ok"] = False
|
||||||
@ -79,13 +75,14 @@ def static_check_workflow(project_root: pathlib.Path, workflow: dict[str, Any])
|
|||||||
step_result["issues"].append(f"command_not_found:{command}")
|
step_result["issues"].append(f"command_not_found:{command}")
|
||||||
|
|
||||||
if command.lower() in ("python", "py", "python3", "python.exe", "py.exe"):
|
if command.lower() in ("python", "py", "python3", "python.exe", "py.exe"):
|
||||||
if isinstance(args, list) and args and isinstance(args[0], str) and args[0].endswith(".py"):
|
if args and isinstance(args[0], str) and args[0].endswith(".py"):
|
||||||
script_path = resolve_script_arg(project_root, working_dir, args[0])
|
script_path = resolve_script_arg(project_root, working_dir, args[0])
|
||||||
if not script_path.exists():
|
if not script_path.exists():
|
||||||
step_result["ok"] = False
|
step_result["ok"] = False
|
||||||
step_result["issues"].append(f"python_script_not_found:{script_path}")
|
step_result["issues"].append(f"python_script_not_found:{script_path}")
|
||||||
|
|
||||||
if isinstance(action, str) and action.strip():
|
if isinstance(action, str) and action.strip():
|
||||||
|
# Action-based steps still require workingDir existence for reliable execution.
|
||||||
if not working_dir.exists():
|
if not working_dir.exists():
|
||||||
step_result["ok"] = False
|
step_result["ok"] = False
|
||||||
step_result["issues"].append("action_working_dir_not_found")
|
step_result["issues"].append("action_working_dir_not_found")
|
||||||
@ -99,8 +96,8 @@ def static_check_workflow(project_root: pathlib.Path, workflow: dict[str, Any])
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def sdt_attempts(repo_root: pathlib.Path) -> list[list[str]]:
|
def sdt_attempts(repo_root: pathlib.Path) -> List[List[str]]:
|
||||||
attempts: list[list[str]] = []
|
attempts: List[List[str]] = []
|
||||||
attempts.append(["sdt"])
|
attempts.append(["sdt"])
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
attempts.append(["sdt.exe"])
|
attempts.append(["sdt.exe"])
|
||||||
@ -113,8 +110,9 @@ def sdt_attempts(repo_root: pathlib.Path) -> list[list[str]]:
|
|||||||
if devtool_csproj.exists():
|
if devtool_csproj.exists():
|
||||||
attempts.append(["dotnet", "run", "--project", str(devtool_csproj), "--"])
|
attempts.append(["dotnet", "run", "--project", str(devtool_csproj), "--"])
|
||||||
|
|
||||||
seen: set[tuple[str, ...]] = set()
|
# Preserve order but dedupe exact attempts.
|
||||||
unique: list[list[str]] = []
|
seen = set()
|
||||||
|
unique: List[List[str]] = []
|
||||||
for a in attempts:
|
for a in attempts:
|
||||||
key = tuple(a)
|
key = tuple(a)
|
||||||
if key in seen:
|
if key in seen:
|
||||||
@ -128,8 +126,8 @@ def try_run_sdt(
|
|||||||
repo_root: pathlib.Path,
|
repo_root: pathlib.Path,
|
||||||
command_args: Sequence[str],
|
command_args: Sequence[str],
|
||||||
timeout_seconds: int,
|
timeout_seconds: int,
|
||||||
) -> tuple[Optional[subprocess.CompletedProcess[str]], Optional[str]]:
|
) -> Tuple[Optional[subprocess.CompletedProcess], Optional[str]]:
|
||||||
errors: list[str] = []
|
errors: List[str] = []
|
||||||
for base in sdt_attempts(repo_root):
|
for base in sdt_attempts(repo_root):
|
||||||
cmd = [*base, *command_args]
|
cmd = [*base, *command_args]
|
||||||
try:
|
try:
|
||||||
@ -149,7 +147,7 @@ def try_run_sdt(
|
|||||||
return None, "; ".join(errors) if errors else "no_sdt_attempts"
|
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()]
|
lines = [line.strip() for line in stdout.splitlines() if line.strip()]
|
||||||
for line in reversed(lines):
|
for line in reversed(lines):
|
||||||
if not line.startswith("{"):
|
if not line.startswith("{"):
|
||||||
@ -169,8 +167,8 @@ def execute_check_workflow(
|
|||||||
workflow_id: str,
|
workflow_id: str,
|
||||||
env_profile: Optional[str],
|
env_profile: Optional[str],
|
||||||
timeout_seconds: int,
|
timeout_seconds: int,
|
||||||
) -> dict[str, Any]:
|
) -> dict:
|
||||||
run_args = [
|
args = [
|
||||||
"run",
|
"run",
|
||||||
workflow_id,
|
workflow_id,
|
||||||
"--json",
|
"--json",
|
||||||
@ -179,9 +177,9 @@ def execute_check_workflow(
|
|||||||
"--non-interactive",
|
"--non-interactive",
|
||||||
]
|
]
|
||||||
if env_profile:
|
if env_profile:
|
||||||
run_args.extend(["--env-profile", env_profile])
|
args.extend(["--env-profile", env_profile])
|
||||||
|
|
||||||
proc, attempted = try_run_sdt(repo_root, run_args, timeout_seconds)
|
proc, attempted = try_run_sdt(repo_root, args, timeout_seconds)
|
||||||
if proc is None:
|
if proc is None:
|
||||||
return {
|
return {
|
||||||
"workflowId": workflow_id,
|
"workflowId": workflow_id,
|
||||||
@ -217,13 +215,13 @@ def main() -> int:
|
|||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Verify SDT workflow routes (static path checks + optional headless execution)."
|
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("--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("--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("--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("--execute", action="store_true", help="Also run each workflow via `sdt run ... --json`")
|
||||||
_ = parser.add_argument("--env-profile", default=None)
|
parser.add_argument("--env-profile", default=None)
|
||||||
_ = parser.add_argument("--timeout-seconds", type=int, default=600)
|
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("--output-json", default=None, help="Write full report JSON to file")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
repo_root = resolve_repo_root(args.repo_root)
|
repo_root = resolve_repo_root(args.repo_root)
|
||||||
@ -237,10 +235,10 @@ def main() -> int:
|
|||||||
return 2
|
return 2
|
||||||
|
|
||||||
static_results = [static_check_workflow(project_root, w) for w in workflows]
|
static_results = [static_check_workflow(project_root, w) for w in workflows]
|
||||||
execute_results: list[dict[str, Any]] = []
|
execute_results: List[dict] = []
|
||||||
if args.execute:
|
if args.execute:
|
||||||
for w in workflows:
|
for w in workflows:
|
||||||
wid = str(w["id"])
|
wid = w["id"]
|
||||||
execute_results.append(
|
execute_results.append(
|
||||||
execute_check_workflow(
|
execute_check_workflow(
|
||||||
repo_root=repo_root,
|
repo_root=repo_root,
|
||||||
@ -254,7 +252,7 @@ def main() -> int:
|
|||||||
static_failures = [r for r in static_results if not r["ok"]]
|
static_failures = [r for r in static_results if not r["ok"]]
|
||||||
exec_failures = [r for r in execute_results if not r["ok"]]
|
exec_failures = [r for r in execute_results if not r["ok"]]
|
||||||
|
|
||||||
report: dict[str, Any] = {
|
report = {
|
||||||
"repoRoot": str(repo_root),
|
"repoRoot": str(repo_root),
|
||||||
"projectRoot": str(project_root),
|
"projectRoot": str(project_root),
|
||||||
"totalWorkflows": len(workflows),
|
"totalWorkflows": len(workflows),
|
||||||
@ -285,13 +283,13 @@ def main() -> int:
|
|||||||
|
|
||||||
if static_failures:
|
if static_failures:
|
||||||
print("\nStatic failures:")
|
print("\nStatic failures:")
|
||||||
for sf in static_failures:
|
for f in static_failures:
|
||||||
print(f"- {sf['workflowId']}: {', '.join(sf['issues'])}")
|
print(f"- {f['workflowId']}: {', '.join(f['issues'])}")
|
||||||
|
|
||||||
if exec_failures:
|
if exec_failures:
|
||||||
print("\nExecution failures:")
|
print("\nExecution failures:")
|
||||||
for ef in exec_failures:
|
for f in exec_failures:
|
||||||
print(f"- {ef['workflowId']}: stopReason={ef.get('stopReason')} message={ef.get('message')}")
|
print(f"- {f['workflowId']}: stopReason={f.get('stopReason')} message={f.get('message')}")
|
||||||
|
|
||||||
return 1 if static_failures or exec_failures else 0
|
return 1 if static_failures or exec_failures else 0
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
15
README.md
15
README.md
@ -165,7 +165,7 @@ dotnet run --project Journal.SmokeTests
|
|||||||
NuGet package versions are managed centrally in `Directory.Packages.props`. Project-level `.csproj` files reference packages without version numbers.
|
NuGet package versions are managed centrally in `Directory.Packages.props`. Project-level `.csproj` files reference packages without version numbers.
|
||||||
|
|
||||||
- `Journal.Core` — `Microsoft.Data.Sqlite.Core`, `SQLitePCLRaw.bundle_e_sqlcipher`, `Microsoft.Extensions.DependencyInjection.Abstractions`
|
- `Journal.Core` — `Microsoft.Data.Sqlite.Core`, `SQLitePCLRaw.bundle_e_sqlcipher`, `Microsoft.Extensions.DependencyInjection.Abstractions`
|
||||||
- `Journal.AI` — `LLamaSharp`, `LLamaSharp.Backend.Cpu`, `LLamaSharp.Backend.Vulkan` + references `Journal.Core`
|
- `Journal.AI` — `LLamaSharp`, `LLamaSharp.Backend.Cpu` + references `Journal.Core`
|
||||||
- `Journal.Sidecar` — `Microsoft.Extensions.DependencyInjection`, `NAudio`, `Whisper.net` + references `Journal.Core`, `Journal.AI`
|
- `Journal.Sidecar` — `Microsoft.Extensions.DependencyInjection`, `NAudio`, `Whisper.net` + references `Journal.Core`, `Journal.AI`
|
||||||
- `Journal.WebGateway` — `Microsoft.NET.Sdk.Web` + references `Journal.Core`, `Journal.AI`
|
- `Journal.WebGateway` — `Microsoft.NET.Sdk.Web` + references `Journal.Core`, `Journal.AI`
|
||||||
- `Journal.SmokeTests` — references `Journal.Core`
|
- `Journal.SmokeTests` — references `Journal.Core`
|
||||||
@ -187,7 +187,6 @@ NuGet package versions are managed centrally in `Directory.Packages.props`. Proj
|
|||||||
| `JOURNAL_VAULT_DIR` | `<root>/journal/vault` | Override vault directory path |
|
| `JOURNAL_VAULT_DIR` | `<root>/journal/vault` | Override vault directory path |
|
||||||
| `JOURNAL_DATA_DIR` | _(empty)_ | Override decrypted data directory path |
|
| `JOURNAL_DATA_DIR` | _(empty)_ | Override decrypted data directory path |
|
||||||
| `JOURNAL_AI_PROVIDER` | `none` | AI provider mode (`none`, `llamasharp`) |
|
| `JOURNAL_AI_PROVIDER` | `none` | AI provider mode (`none`, `llamasharp`) |
|
||||||
| `JOURNAL_GPU_LAYERS` | `-1` (all) | Number of model layers to offload to GPU (`-1` = all, `0` = CPU only) |
|
|
||||||
| `JOURNAL_LOG_LEVEL` | `warning` | Log verbosity (`trace`, `debug`, `information`, `warning`, `error`, `critical`) |
|
| `JOURNAL_LOG_LEVEL` | `warning` | Log verbosity (`trace`, `debug`, `information`, `warning`, `error`, `critical`) |
|
||||||
| `JOURNAL_WEB_DIST` | auto | Override web UI dist path for WebGateway |
|
| `JOURNAL_WEB_DIST` | auto | Override web UI dist path for WebGateway |
|
||||||
|
|
||||||
@ -200,18 +199,6 @@ NuGet package versions are managed centrally in `Directory.Packages.props`. Proj
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## AI / LLM Notes
|
|
||||||
|
|
||||||
The `Journal.AI` project uses **LLamaSharp** for local LLM inference.
|
|
||||||
|
|
||||||
- **CPU backend** (`LLamaSharp.Backend.Cpu`) is always installed as a fallback.
|
|
||||||
- **Vulkan backend** (`LLamaSharp.Backend.Vulkan`) provides GPU acceleration for AMD, Intel, and NVIDIA GPUs. LLamaSharp picks the best available backend at runtime.
|
|
||||||
- All backend packages must share the **same version**. Currently pinned to **0.25.0** because `LLamaSharp.Backend.Vulkan` has not yet published a 0.26.0 release. Watch the [NuGet page](https://www.nuget.org/packages/LLamaSharp.Backend.Vulkan) and upgrade all three packages together when a new version ships.
|
|
||||||
- **Known issue**: on some machines the Vulkan backend falls back to CPU because the internal `vulkaninfo --summary` detection times out at 1 second. If you see CPU-only inference despite having a Vulkan-capable GPU, this is likely the cause. The LLamaSharp team has acknowledged the issue ([#930](https://github.com/SciSharp/LLamaSharp/issues/930)).
|
|
||||||
- Set `JOURNAL_GPU_LAYERS=-1` (the default) to offload all model layers to the GPU, or `0` to force CPU-only.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Journal.WebGateway
|
## Journal.WebGateway
|
||||||
|
|
||||||
An ASP.NET Core minimal API that wraps `Journal.Core` for browser use.
|
An ASP.NET Core minimal API that wraps `Journal.Core` for browser use.
|
||||||
|
|||||||
786
devtool.json
Normal file
786
devtool.json
Normal file
@ -0,0 +1,786 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
677
devtool.json.bak-20260301-155008
Normal file
677
devtool.json.bak-20260301-155008
Normal file
@ -0,0 +1,677 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
@ -1,558 +0,0 @@
|
|||||||
{
|
|
||||||
"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