From a05c4b02093d941b654cae46148261437d3f85fa Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Mon, 2 Mar 2026 19:33:11 -0600 Subject: [PATCH] Set up monorepo: centralize .NET packages, npm workspaces, remove Journal.DevTool - Add Directory.Build.props with shared TargetFramework/Nullable/ImplicitUsings - Add Directory.Packages.props for centralized NuGet version management - Strip duplicated properties and Version attributes from all .csproj files - Fix Directory.Build.props TFM from net10 to net10.0 - Configure npm workspaces in root package.json (Journal.App) - Hoist node_modules to repo root with single lockfile - Add node_modules/ to .gitignore - Remove Journal.DevTool contents, keep as empty folder with .gitkeep - Remove Journal.DevTool from solution and npm workspaces Co-Authored-By: Oz --- .gitignore | 6 +- Directory.Build.props | 7 + Directory.Packages.props | 16 + Journal.AI/Journal.AI.csproj | 10 +- Journal.App/package-lock.json | 1926 ---------------- Journal.Core/Journal.Core.csproj | 12 +- Journal.DevTool/.gitignore | 15 - Journal.DevTool/.gitkeep | 0 Journal.DevTool/Config/ConfigBootstrapper.cs | 626 ------ Journal.DevTool/Config/ConfigLoader.cs | 223 -- Journal.DevTool/Config/DevToolConfig.cs | 185 -- .../Config/WorkflowModelBuilder.cs | 100 - Journal.DevTool/Config/WorkspaceConfig.cs | 22 - Journal.DevTool/Config/WorkspaceLoader.cs | 170 -- Journal.DevTool/Core/ActionRunner.cs | 207 -- Journal.DevTool/Core/CommandResolver.cs | 173 -- .../Core/ConfigDoctorAutoFixService.cs | 67 - Journal.DevTool/Core/ConfigDoctorService.cs | 270 --- Journal.DevTool/Core/Contracts.cs | 87 - Journal.DevTool/Core/Debug/DebugContracts.cs | 52 - .../Core/Debug/DebugProfileRunner.cs | 185 -- .../Core/Debug/DiagnosticsBundleService.cs | 99 - .../Core/LegacyScriptRequirementResolver.cs | 59 - .../Core/PrereqInstallerService.cs | 303 --- Journal.DevTool/Core/PythonResolver.cs | 46 - Journal.DevTool/Core/RequirementResolver.cs | 67 - Journal.DevTool/Core/RunEventJsonlRecorder.cs | 65 - Journal.DevTool/Core/RunEventLogReader.cs | 99 - Journal.DevTool/Core/RunEvents.cs | 34 - Journal.DevTool/Core/ScriptLocator.cs | 19 - Journal.DevTool/Core/ToolProbeService.cs | 122 -- Journal.DevTool/Core/WorkflowExecutor.cs | 273 --- Journal.DevTool/Core/WorkflowPlanner.cs | 35 - Journal.DevTool/DevTool.csproj | 37 - Journal.DevTool/Journal.DevTool.csproj | 17 - Journal.DevTool/Program.cs | 143 -- Journal.DevTool/README.md | 200 -- Journal.DevTool/ROADMAP.md | 50 - Journal.DevTool/Runner/ProcessRunner.cs | 83 - Journal.DevTool/Runner/TargetRunner.cs | 41 - Journal.DevTool/Tui/App.cs | 800 ------- Journal.DevTool/Tui/EventsScreen.cs | 126 -- Journal.DevTool/Tui/Theme.cs | 54 - Journal.DevTool/Tui/ToolchainScreen.cs | 347 --- Journal.DevTool/Tui/WorkspaceScreen.cs | 174 -- Journal.DevTool/package-lock.json | 31 - Journal.DevTool/package.json | 5 - Journal.DevTool/scripts/README.md | 63 - Journal.DevTool/scripts/WORKFLOWS.md | 57 - Journal.DevTool/scripts/_pwsh-python-shim.ps1 | 39 - Journal.DevTool/scripts/build.py | 419 ---- Journal.DevTool/scripts/dev-shell.cmd | 17 - Journal.DevTool/scripts/dev-shell.ps1 | 21 - Journal.DevTool/scripts/dev-shell.sh | 16 - Journal.DevTool/scripts/dev_shell.py | 148 -- Journal.DevTool/scripts/diag.py | 128 -- Journal.DevTool/scripts/dotnet-min.py | 34 - .../scripts/legacy/dotnet-min.legacy.ps1 | 62 - .../scripts/legacy/migration-gate.legacy.ps1 | 57 - .../scripts/legacy/npm-clean.legacy.ps1 | 62 - .../legacy/nuget-export-cache.legacy.ps1 | 57 - .../legacy/nuget-import-cache.legacy.ps1 | 25 - .../scripts/legacy/pip-min.legacy.ps1 | 56 - .../scripts/legacy/publish-app.legacy.ps1 | 216 -- .../scripts/legacy/publish-output.legacy.ps1 | 103 - .../scripts/legacy/publish-sidecar.legacy.ps1 | 54 - .../legacy/publish-webgateway.legacy.ps1 | 55 - .../scripts/legacy/run-webgateway.legacy.ps1 | 69 - .../scripts/legacy/sync-output.legacy.ps1 | 64 - Journal.DevTool/scripts/migration-gate.py | 44 - Journal.DevTool/scripts/npm-clean.py | 35 - Journal.DevTool/scripts/nuget-export-cache.py | 42 - Journal.DevTool/scripts/nuget-import-cache.py | 28 - Journal.DevTool/scripts/pip-min.py | 35 - Journal.DevTool/scripts/pip_safe.py | 46 - Journal.DevTool/scripts/publish-app.py | 91 - Journal.DevTool/scripts/publish-output.py | 90 - Journal.DevTool/scripts/publish-sidecar.ps1 | 10 - Journal.DevTool/scripts/publish-sidecar.py | 58 - Journal.DevTool/scripts/publish-webgateway.py | 78 - Journal.DevTool/scripts/run-webgateway.py | 60 - Journal.DevTool/scripts/script-common.ps1 | 124 -- Journal.DevTool/scripts/script_common.py | 315 --- Journal.DevTool/scripts/sync-output.py | 82 - .../ActionRunnerLegacyPwshTests.cs | 31 - .../DevTool.Tests/CommandResolverTests.cs | 124 -- .../DevTool.Tests/ConfigBootstrapperTests.cs | 108 - .../ConfigDoctorAutoFixServiceTests.cs | 54 - .../DevTool.Tests/ConfigDoctorServiceTests.cs | 80 - .../tests/DevTool.Tests/DebugConfigTests.cs | 40 - .../tests/DevTool.Tests/DebugServicesTests.cs | 191 -- .../DevTool.Tests/DevShellScriptTests.cs | 120 - .../tests/DevTool.Tests/DevTool.Tests.csproj | 18 - .../tests/DevTool.Tests/LegacyModeTests.cs | 88 - .../PrereqInstallerServiceTests.cs | 99 - .../DevTool.Tests/RequirementResolverTests.cs | 44 - .../RunEventJsonlRecorderTests.cs | 31 - .../DevTool.Tests/RunEventLogReaderTests.cs | 48 - .../tests/DevTool.Tests/ScriptCommonTests.cs | 166 -- .../tests/DevTool.Tests/ScriptSmokeTests.cs | 179 -- .../DevTool.Tests/WorkflowExecutorTests.cs | 311 --- .../WorkflowModelBuilderTests.cs | 149 -- .../DevTool.Tests/WorkspaceDefaultsTests.cs | 141 -- .../DevTool.Tests/WorkspaceLoaderTests.cs | 56 - Journal.Sidecar/Journal.Sidecar.csproj | 11 +- Journal.SmokeTests/Journal.SmokeTests.csproj | 3 - Journal.WebGateway/Journal.WebGateway.csproj | 5 - Journal.slnx | 2 +- package-lock.json | 1933 ++++++++++++++++- package.json | 7 + 110 files changed, 1975 insertions(+), 12712 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props delete mode 100644 Journal.App/package-lock.json delete mode 100644 Journal.DevTool/.gitignore create mode 100644 Journal.DevTool/.gitkeep delete mode 100644 Journal.DevTool/Config/ConfigBootstrapper.cs delete mode 100644 Journal.DevTool/Config/ConfigLoader.cs delete mode 100644 Journal.DevTool/Config/DevToolConfig.cs delete mode 100644 Journal.DevTool/Config/WorkflowModelBuilder.cs delete mode 100644 Journal.DevTool/Config/WorkspaceConfig.cs delete mode 100644 Journal.DevTool/Config/WorkspaceLoader.cs delete mode 100644 Journal.DevTool/Core/ActionRunner.cs delete mode 100644 Journal.DevTool/Core/CommandResolver.cs delete mode 100644 Journal.DevTool/Core/ConfigDoctorAutoFixService.cs delete mode 100644 Journal.DevTool/Core/ConfigDoctorService.cs delete mode 100644 Journal.DevTool/Core/Contracts.cs delete mode 100644 Journal.DevTool/Core/Debug/DebugContracts.cs delete mode 100644 Journal.DevTool/Core/Debug/DebugProfileRunner.cs delete mode 100644 Journal.DevTool/Core/Debug/DiagnosticsBundleService.cs delete mode 100644 Journal.DevTool/Core/LegacyScriptRequirementResolver.cs delete mode 100644 Journal.DevTool/Core/PrereqInstallerService.cs delete mode 100644 Journal.DevTool/Core/PythonResolver.cs delete mode 100644 Journal.DevTool/Core/RequirementResolver.cs delete mode 100644 Journal.DevTool/Core/RunEventJsonlRecorder.cs delete mode 100644 Journal.DevTool/Core/RunEventLogReader.cs delete mode 100644 Journal.DevTool/Core/RunEvents.cs delete mode 100644 Journal.DevTool/Core/ScriptLocator.cs delete mode 100644 Journal.DevTool/Core/ToolProbeService.cs delete mode 100644 Journal.DevTool/Core/WorkflowExecutor.cs delete mode 100644 Journal.DevTool/Core/WorkflowPlanner.cs delete mode 100644 Journal.DevTool/DevTool.csproj delete mode 100644 Journal.DevTool/Journal.DevTool.csproj delete mode 100644 Journal.DevTool/Program.cs delete mode 100644 Journal.DevTool/README.md delete mode 100644 Journal.DevTool/ROADMAP.md delete mode 100644 Journal.DevTool/Runner/ProcessRunner.cs delete mode 100644 Journal.DevTool/Runner/TargetRunner.cs delete mode 100644 Journal.DevTool/Tui/App.cs delete mode 100644 Journal.DevTool/Tui/EventsScreen.cs delete mode 100644 Journal.DevTool/Tui/Theme.cs delete mode 100644 Journal.DevTool/Tui/ToolchainScreen.cs delete mode 100644 Journal.DevTool/Tui/WorkspaceScreen.cs delete mode 100644 Journal.DevTool/package-lock.json delete mode 100644 Journal.DevTool/package.json delete mode 100644 Journal.DevTool/scripts/README.md delete mode 100644 Journal.DevTool/scripts/WORKFLOWS.md delete mode 100644 Journal.DevTool/scripts/_pwsh-python-shim.ps1 delete mode 100644 Journal.DevTool/scripts/build.py delete mode 100644 Journal.DevTool/scripts/dev-shell.cmd delete mode 100644 Journal.DevTool/scripts/dev-shell.ps1 delete mode 100644 Journal.DevTool/scripts/dev-shell.sh delete mode 100644 Journal.DevTool/scripts/dev_shell.py delete mode 100644 Journal.DevTool/scripts/diag.py delete mode 100644 Journal.DevTool/scripts/dotnet-min.py delete mode 100644 Journal.DevTool/scripts/legacy/dotnet-min.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/migration-gate.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/npm-clean.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/nuget-export-cache.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/nuget-import-cache.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/pip-min.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/publish-app.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/publish-output.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/publish-sidecar.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/publish-webgateway.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/run-webgateway.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/legacy/sync-output.legacy.ps1 delete mode 100644 Journal.DevTool/scripts/migration-gate.py delete mode 100644 Journal.DevTool/scripts/npm-clean.py delete mode 100644 Journal.DevTool/scripts/nuget-export-cache.py delete mode 100644 Journal.DevTool/scripts/nuget-import-cache.py delete mode 100644 Journal.DevTool/scripts/pip-min.py delete mode 100644 Journal.DevTool/scripts/pip_safe.py delete mode 100644 Journal.DevTool/scripts/publish-app.py delete mode 100644 Journal.DevTool/scripts/publish-output.py delete mode 100644 Journal.DevTool/scripts/publish-sidecar.ps1 delete mode 100644 Journal.DevTool/scripts/publish-sidecar.py delete mode 100644 Journal.DevTool/scripts/publish-webgateway.py delete mode 100644 Journal.DevTool/scripts/run-webgateway.py delete mode 100644 Journal.DevTool/scripts/script-common.ps1 delete mode 100644 Journal.DevTool/scripts/script_common.py delete mode 100644 Journal.DevTool/scripts/sync-output.py delete mode 100644 Journal.DevTool/tests/DevTool.Tests/ActionRunnerLegacyPwshTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/CommandResolverTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/ConfigBootstrapperTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/ConfigDoctorAutoFixServiceTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/ConfigDoctorServiceTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/DebugConfigTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/DebugServicesTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/DevShellScriptTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/DevTool.Tests.csproj delete mode 100644 Journal.DevTool/tests/DevTool.Tests/LegacyModeTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/PrereqInstallerServiceTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/RequirementResolverTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/RunEventJsonlRecorderTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/RunEventLogReaderTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/ScriptCommonTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/ScriptSmokeTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/WorkflowExecutorTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/WorkflowModelBuilderTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/WorkspaceDefaultsTests.cs delete mode 100644 Journal.DevTool/tests/DevTool.Tests/WorkspaceLoaderTests.cs create mode 100644 package.json diff --git a/.gitignore b/.gitignore index 7ae3e83..c51e801 100644 --- a/.gitignore +++ b/.gitignore @@ -45,13 +45,15 @@ logs/ # macOS .DS_Store +# Node +node_modules/ + # OTHER .just/ journalapp.exe Journal.App/node_modules.old/@rollup/.rollup-win32-x64-msvc-IjiZshxL/rollup.win32-x64-msvc.node journalapp(1).exe .cache/ -Journal.DevTool/node_modules/ scripts/__pycache__/ .sdt/ devtool.backup.json @@ -61,5 +63,3 @@ sdt.exe sdt.pdb sdt.runtimeconfig.json Spectre.Console.dll -Journal.DevTool/devtool.generated.workflows.json -Journal.DevTool/sdt-workspace.json diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..43790c8 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,7 @@ + + + net10.0 + enable + enable + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..9845347 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,16 @@ + + + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/Journal.AI/Journal.AI.csproj b/Journal.AI/Journal.AI.csproj index 97186d9..32ff915 100644 --- a/Journal.AI/Journal.AI.csproj +++ b/Journal.AI/Journal.AI.csproj @@ -1,14 +1,8 @@  - - net10.0 - enable - enable - - - - + + diff --git a/Journal.App/package-lock.json b/Journal.App/package-lock.json deleted file mode 100644 index 0407a46..0000000 --- a/Journal.App/package-lock.json +++ /dev/null @@ -1,1926 +0,0 @@ -{ - "name": "journalapp", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "journalapp", - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "^2.6.0", - "@tauri-apps/plugin-opener": "^2", - "tauri-plugin-mic-recorder-api": "^2.0.0" - }, - "devDependencies": { - "@sveltejs/adapter-static": "^3.0.6", - "@sveltejs/kit": "^2.9.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "@tauri-apps/cli": "^2", - "prettier": "^3.8.1", - "prettier-plugin-svelte": "^3.5.0", - "svelte": "^5.0.0", - "svelte-check": "^4.0.0", - "typescript": "~5.6.2", - "vite": "^6.0.3" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", - "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@sveltejs/adapter-static": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", - "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" - } - }, - "node_modules/@sveltejs/kit": { - "version": "2.53.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.53.1.tgz", - "integrity": "sha512-NXsZLvalgI3HrHG6ogoEVzjyV7bSFQNqQeekfU7nNufQFrRyV3EBDfQKEwxx50peu7spZR42JuC1PFhwxuvBrg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.1", - "cookie": "^0.6.0", - "devalue": "^5.6.3", - "esm-env": "^1.2.2", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "mrmime": "^2.0.0", - "set-cookie-parser": "^3.0.0", - "sirv": "^3.0.0" - }, - "bin": { - "svelte-kit": "svelte-kit.js" - }, - "engines": { - "node": ">=18.13" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.3.3", - "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", - "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", - "debug": "^4.4.1", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.17", - "vitefu": "^1.0.6" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", - "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.7" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "svelte": "^5.0.0", - "vite": "^6.0.0" - } - }, - "node_modules/@tauri-apps/api": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", - "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/cli": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.0.tgz", - "integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==", - "dev": true, - "license": "Apache-2.0 OR MIT", - "bin": { - "tauri": "tauri.js" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - }, - "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.10.0", - "@tauri-apps/cli-darwin-x64": "2.10.0", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", - "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", - "@tauri-apps/cli-linux-arm64-musl": "2.10.0", - "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-musl": "2.10.0", - "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", - "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", - "@tauri-apps/cli-win32-x64-msvc": "2.10.0" - } - }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz", - "integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz", - "integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz", - "integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz", - "integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz", - "integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz", - "integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz", - "integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz", - "integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz", - "integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz", - "integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz", - "integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/plugin-dialog": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz", - "integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.8.0" - } - }, - "node_modules/@tauri-apps/plugin-opener": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz", - "integrity": "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.8.0" - } - }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aria-query": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/devalue": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", - "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esrap": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz", - "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-svelte": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.0.tgz", - "integrity": "sha512-2lLO/7EupnjO/95t+XZesXs8Bf3nYLIDfCo270h5QWbj/vjLqmrQ1LiRk9LPggxSDsnVYfehamZNf+rgQYApZg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": "^3.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/set-cookie-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", - "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svelte": { - "version": "5.53.3", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.3.tgz", - "integrity": "sha512-pRUBr6j6uQDgBi208gHnGRMykw0Rf2Yr1HmLyRucsvcaYgIUxswJkT93WZJflsmezu5s8Lq+q78EoyLv2yaFCg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "@types/trusted-types": "^2.0.7", - "acorn": "^8.12.1", - "aria-query": "5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "devalue": "^5.6.3", - "esm-env": "^1.2.1", - "esrap": "^2.2.2", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/svelte-check": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.3.tgz", - "integrity": "sha512-4HtdEv2hOoLCEsSXI+RDELk9okP/4sImWa7X02OjMFFOWeSdFF3NFy3vqpw0z+eH9C88J9vxZfUXz/Uv2A1ANw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": ">=5.0.0" - } - }, - "node_modules/tauri-plugin-mic-recorder-api": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tauri-plugin-mic-recorder-api/-/tauri-plugin-mic-recorder-api-2.0.0.tgz", - "integrity": "sha512-04wqYCX4WIlYd6KUY7aS3+W4B5RtnSoVczaQCBSXKpQkEx9XdaaBN05XCee2unxGva0btSXBItFqQSdosnS4jQ==", - "license": "MIT", - "dependencies": { - "@tauri-apps/api": ">=2.0.0-beta.6" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", - "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", - "dev": true, - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/Journal.Core/Journal.Core.csproj b/Journal.Core/Journal.Core.csproj index 7e67750..60a9a1f 100644 --- a/Journal.Core/Journal.Core.csproj +++ b/Journal.Core/Journal.Core.csproj @@ -1,15 +1,9 @@  - - net10.0 - enable - enable - - - - - + + + diff --git a/Journal.DevTool/.gitignore b/Journal.DevTool/.gitignore deleted file mode 100644 index d148d21..0000000 --- a/Journal.DevTool/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -bin/ -obj/ -__pycache__/ -.cache/ -.vscode/ -.idea/ -.vs/ -.git/ -.pip -.tmp -.venv -.dotnet_home -.nuget -publish-test/ - diff --git a/Journal.DevTool/.gitkeep b/Journal.DevTool/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Journal.DevTool/Config/ConfigBootstrapper.cs b/Journal.DevTool/Config/ConfigBootstrapper.cs deleted file mode 100644 index 2b65e39..0000000 --- a/Journal.DevTool/Config/ConfigBootstrapper.cs +++ /dev/null @@ -1,626 +0,0 @@ -using System.Text.Json; - -namespace Sdt.Config; - -public sealed record BootstrapScanResult( - string ProjectRoot, - string ProjectName, - IReadOnlyList ToolFamilies, - string? NodeWorkingDir, - string? PythonRequirementsFile, - bool HasDockerCompose, - IReadOnlyList RootHints); - -public static class ConfigBootstrapper -{ - private const int MaxScanDepth = 4; - - private static readonly HashSet ExcludedDirectories = new(StringComparer.OrdinalIgnoreCase) - { - ".git", - "node_modules", - ".venv", - "venv", - "bin", - "obj", - ".idea", - ".vscode", - "dist", - "build", - ".sdt", - }; - - private static readonly string[] RequirementCandidates = - [ - "requirements.txt", - "requirements-dev.txt", - "requirements_cpu_only.txt", - "requirements_gpu.txt", - ]; - - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true - }; - - public static BootstrapScanResult Scan(string startDir) - { - var root = FindProjectRoot(startDir); - var toolFamilies = new HashSet(StringComparer.OrdinalIgnoreCase); - var rootHints = new HashSet(StringComparer.OrdinalIgnoreCase); - - if (Directory.Exists(Path.Combine(root, ".git"))) - { - toolFamilies.Add("git"); - rootHints.Add(".git"); - } - - var hasTopLevelSln = Directory.EnumerateFiles(root, "*.sln", SearchOption.TopDirectoryOnly).Any(); - var hasCsproj = hasTopLevelSln || EnumerateFilesBounded(root, "*.csproj", MaxScanDepth).Any(); - if (hasCsproj) - { - toolFamilies.Add("dotnet"); - rootHints.Add("*.sln"); - } - - var topLevelPackageJson = Path.Combine(root, "package.json"); - var packageJson = File.Exists(topLevelPackageJson) - ? topLevelPackageJson - : EnumerateFilesBounded(root, "package.json", MaxScanDepth) - .OrderBy(p => p.Length) - .FirstOrDefault(); - string? nodeWorkingDir = null; - if (packageJson is not null) - { - toolFamilies.Add("node"); - toolFamilies.Add("npm"); - nodeWorkingDir = Path.GetRelativePath(root, Path.GetDirectoryName(packageJson)!); - rootHints.Add("package.json"); - } - - string? requirements = RequirementCandidates - .Select(name => Path.Combine(root, name)) - .FirstOrDefault(File.Exists); - if (requirements is null) - { - var pyproject = Path.Combine(root, "pyproject.toml"); - if (File.Exists(pyproject)) - { - toolFamilies.Add("python"); - rootHints.Add("pyproject.toml"); - } - } - else - { - toolFamilies.Add("python"); - rootHints.Add(Path.GetFileName(requirements)!); - } - - var hasCargo = File.Exists(Path.Combine(root, "Cargo.toml")) || - EnumerateFilesBounded(root, "Cargo.toml", MaxScanDepth).Any(); - if (hasCargo) - { - toolFamilies.Add("cargo"); - rootHints.Add("Cargo.toml"); - } - - var hasTauri = File.Exists(Path.Combine(root, "tauri.conf.json")) || - EnumerateFilesBounded(root, "tauri.conf.json", MaxScanDepth).Any(); - if (hasTauri) - { - toolFamilies.Add("tauri"); - toolFamilies.Add("cargo"); - toolFamilies.Add("node"); - toolFamilies.Add("npm"); - rootHints.Add("tauri.conf.json"); - } - - var hasDockerCompose = File.Exists(Path.Combine(root, "docker-compose.yml")) || - File.Exists(Path.Combine(root, "docker-compose.yaml")); - if (hasDockerCompose || File.Exists(Path.Combine(root, "Dockerfile"))) - { - toolFamilies.Add("docker"); - rootHints.Add(hasDockerCompose ? "docker-compose.yml" : "Dockerfile"); - } - - var scriptsDir = Path.Combine(root, "scripts"); - if (Directory.Exists(scriptsDir) && - Directory.EnumerateFiles(scriptsDir, "*.py", SearchOption.TopDirectoryOnly).Any()) - { - toolFamilies.Add("python"); - rootHints.Add("scripts"); - } - - if (rootHints.Count == 0) - rootHints.Add("devtool.json"); - - return new BootstrapScanResult( - ProjectRoot: root, - ProjectName: new DirectoryInfo(root).Name, - ToolFamilies: toolFamilies.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList(), - NodeWorkingDir: nodeWorkingDir, - PythonRequirementsFile: requirements is null ? null : Path.GetRelativePath(root, requirements), - HasDockerCompose: hasDockerCompose, - RootHints: rootHints.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList()); - } - - public static DevToolConfig BuildDefaultConfig(BootstrapScanResult scan) - { - var workflows = BuildWorkflows(scan).ToList(); - - var toolingTools = scan.ToolFamilies - .Select(t => new ToolInstallDefinition { Tool = t, PreferredInstallCommands = [] }) - .ToList(); - - var toolchains = new ToolchainConfig - { - Python = scan.ToolFamilies.Contains("python", StringComparer.OrdinalIgnoreCase) - ? new PythonToolchain - { - Executable = "python", - WindowsExecutable = "py", - VenvDir = ".venv", - Profiles = [] - } - : null, - Node = scan.ToolFamilies.Contains("node", StringComparer.OrdinalIgnoreCase) - ? new NodeToolchain - { - PackageManager = "npm", - WorkingDir = string.IsNullOrWhiteSpace(scan.NodeWorkingDir) ? "." : scan.NodeWorkingDir - } - : null - }; - - var debugProfiles = BuildDebugProfiles(scan).ToList(); - - return new DevToolConfig - { - Name = scan.ProjectName, - Version = "0.1.0", - Project = new ProjectMetadata - { - Type = "generic", - RootHints = scan.RootHints.ToList(), - Artifacts = ["bin", "obj", ".sdt/debug"] - }, - Toolchains = toolchains, - Tooling = new ToolingConfig { Tools = toolingTools }, - Workflows = workflows, - Debug = new DebugConfig - { - Profiles = debugProfiles, - Diagnostics = new DebugDiagnosticsOptions - { - Enabled = true, - OutputDir = ".sdt/debug", - IncludeAllEnv = false, - CaptureEnvKeys = - [ - "SDT_LOG_LEVEL", - "DOTNET_CLI_HOME", - "NUGET_PACKAGES", - "PIP_CACHE_DIR", - "NVM_HOME", - "NVM_SYMLINK", - ], - BundleOnFailure = true - } - }, - Env = - [ - new EnvVarDef - { - Key = "SDT_LOG_LEVEL", - Description = "CLI log verbosity", - DefaultValue = "information", - Options = ["trace", "debug", "information", "warning", "error", "critical"] - } - ] - }; - } - - public static string ToJson(DevToolConfig config) => JsonSerializer.Serialize(config, JsonOptions) + Environment.NewLine; - - public static string WriteDefaultConfig(string projectRoot, DevToolConfig config, bool overwrite = false) - { - var path = Path.Combine(projectRoot, "devtool.json"); - if (File.Exists(path) && !overwrite) - throw new InvalidOperationException($"devtool.json already exists at {path}"); - - File.WriteAllText(path, ToJson(config)); - return path; - } - - private static IEnumerable BuildWorkflows(BootstrapScanResult scan) - { - var has = new Func(tool => scan.ToolFamilies.Contains(tool, StringComparer.OrdinalIgnoreCase)); - var scripts = DetectScriptHelpers(scan.ProjectRoot); - - if (scripts.Contains("publish-sidecar.py") || - scripts.Contains("publish-app.py") || - scripts.Contains("publish-webgateway.py") || - scripts.Contains("publish-output.py") || - scripts.Contains("sync-output.py") || - scripts.Contains("run-webgateway.py")) - { - foreach (var workflow in BuildScriptDrivenWorkflows(scripts)) - yield return workflow; - } - - var buildSteps = new List(); - if (has("dotnet")) buildSteps.Add(StepAction("dotnet-build", "dotnet build", "dotnet-build")); - if (has("npm")) buildSteps.Add(StepAction("npm-build", "npm run build", "npm-build", scan.NodeWorkingDir)); - if (has("cargo")) buildSteps.Add(StepAction("cargo-build", "cargo build", "cargo-build")); - if (has("tauri")) buildSteps.Add(StepAction("tauri-build", "tauri build", "tauri-build", scan.NodeWorkingDir ?? ".")); - if (buildSteps.Count > 0) - { - yield return new WorkflowDefinition - { - Id = "build", - Label = "Build", - Description = "Build detected project stacks", - Group = "Build", - Steps = buildSteps - }; - } - - var depsSteps = new List(); - if (has("dotnet")) depsSteps.Add(StepAction("dotnet-restore", "dotnet restore", "dotnet-restore")); - if (has("npm")) depsSteps.Add(StepAction("npm-ci", "npm ci", "npm-ci", scan.NodeWorkingDir)); - if (has("python") && !string.IsNullOrWhiteSpace(scan.PythonRequirementsFile)) - { - depsSteps.Add(StepAction("python-pip-sync", "python pip sync", "python-pip-sync", ".", ["--requirements", scan.PythonRequirementsFile!])); - } - if (depsSteps.Count > 0) - { - yield return new WorkflowDefinition - { - Id = "deps-refresh", - Label = "Refresh Dependencies", - Description = "Restore/install dependency stacks", - Group = "Deps", - Steps = depsSteps - }; - } - - var testSteps = new List(); - if (has("dotnet")) testSteps.Add(StepAction("dotnet-test", "dotnet test", "dotnet-test")); - if (has("npm")) testSteps.Add(StepAction("npm-test", "npm test", "npm-test", scan.NodeWorkingDir)); - if (has("python")) testSteps.Add(StepAction("python-pytest", "python -m pytest", "python-pytest")); - if (has("cargo")) testSteps.Add(StepAction("cargo-test", "cargo test", "cargo-test")); - if (testSteps.Count > 0) - { - yield return new WorkflowDefinition - { - Id = "test", - Label = "Run Tests", - Description = "Run detected test stacks", - Group = "Test", - Steps = testSteps - }; - } - - if (has("git")) - { - yield return new WorkflowDefinition - { - Id = "repo-health", - Label = "Repo Health", - Description = "Check repo status and fetch remotes", - Group = "Repo", - Steps = - [ - StepAction("git-status", "git status", "git-status"), - StepAction("git-fetch", "git fetch", "git-fetch") - ] - }; - } - - if (has("docker")) - { - yield return new WorkflowDefinition - { - Id = "containers", - Label = "Containers", - Description = scan.HasDockerCompose ? "Manage docker compose stack" : "Build docker image", - Group = "Containers", - Steps = scan.HasDockerCompose - ? [StepAction("docker-compose-up", "docker compose up -d", "docker-compose-up")] - : [StepAction("docker-build", "docker build .", "docker-build")] - }; - } - } - - private static IEnumerable BuildDebugProfiles(BootstrapScanResult scan) - { - var has = new Func(tool => scan.ToolFamilies.Contains(tool, StringComparer.OrdinalIgnoreCase)); - if (has("dotnet")) - { - yield return new DebugProfileDefinition - { - Id = "dotnet-run", - Label = "Run .NET app", - Type = "dotnet", - Command = "dotnet", - Args = ["run"], - WorkingDir = ".", - Requires = [new ToolRequirement { Tool = "dotnet", InstallPolicy = InstallPolicy.Prompt }], - Attach = new DebugAttachConfig - { - Kind = "manual", - Note = "Attach your IDE debugger to the running dotnet process." - } - }; - } - - if (has("npm")) - { - yield return new DebugProfileDefinition - { - Id = "npm-dev", - Label = "Run npm dev server", - Type = "node", - Command = "npm", - Args = ["run", "dev"], - WorkingDir = string.IsNullOrWhiteSpace(scan.NodeWorkingDir) ? "." : scan.NodeWorkingDir!, - Requires = - [ - new ToolRequirement { Tool = "node", InstallPolicy = InstallPolicy.Prompt }, - new ToolRequirement { Tool = "npm", InstallPolicy = InstallPolicy.Prompt } - ] - }; - } - } - - private static WorkflowStep StepAction( - string id, - string label, - string action, - string? workingDir = null, - IReadOnlyList? actionArgs = null) - { - return new WorkflowStep - { - Id = id, - Label = label, - Action = action, - ActionArgs = actionArgs?.ToList() ?? [], - WorkingDir = string.IsNullOrWhiteSpace(workingDir) ? "." : workingDir - }; - } - - private static HashSet DetectScriptHelpers(string projectRoot) - { - var scriptsDir = Path.Combine(projectRoot, "scripts"); - if (!Directory.Exists(scriptsDir)) - return new HashSet(StringComparer.OrdinalIgnoreCase); - - return Directory.EnumerateFiles(scriptsDir, "*.py", SearchOption.TopDirectoryOnly) - .Select(Path.GetFileName) - .Where(f => !string.IsNullOrWhiteSpace(f)) - .Cast() - .ToHashSet(StringComparer.OrdinalIgnoreCase); - } - - private static IEnumerable BuildScriptDrivenWorkflows(HashSet scripts) - { - static WorkflowStep ScriptStep(string id, string label, params string[] scriptArgs) => new() - { - Id = id, - Label = label, - Command = "python", - Args = scriptArgs.ToList(), - WorkingDir = ".", - Requires = [new ToolRequirement { Tool = "python", InstallPolicy = InstallPolicy.Prompt }] - }; - - if (scripts.Contains("publish-sidecar.py")) - { - yield return new WorkflowDefinition - { - Id = "sidecar", - Label = "Publish Sidecar", - Description = "Publish sidecar service", - Group = "Build", - Steps = [ScriptStep("sidecar:run", "python scripts/publish-sidecar.py", "scripts/publish-sidecar.py")] - }; - } - - if (scripts.Contains("publish-app.py")) - { - yield return new WorkflowDefinition - { - Id = "web", - Label = "Build Web UI", - Description = "Build frontend assets", - Group = "Build", - Steps = - [ - ScriptStep("web:run", "python scripts/publish-app.py --target web", "scripts/publish-app.py", "--target", "web") - ] - }; - - yield return new WorkflowDefinition - { - Id = "tauri", - Label = "Build Tauri Desktop App", - Description = "Build desktop binary", - Group = "Build", - DependsOn = scripts.Contains("publish-sidecar.py") ? ["sidecar"] : [], - Steps = - [ - ScriptStep("tauri:run", "python scripts/publish-app.py --target tauri --tauri-bundles none", - "scripts/publish-app.py", "--target", "tauri", "--tauri-bundles", "none") - ] - }; - } - - if (scripts.Contains("publish-webgateway.py")) - { - yield return new WorkflowDefinition - { - Id = "webgateway", - Label = "Publish WebGateway", - Description = "Publish ASP.NET gateway", - Group = "Build", - DependsOn = scripts.Contains("publish-app.py") ? ["web"] : [], - Steps = [ScriptStep("webgateway:run", "python scripts/publish-webgateway.py", "scripts/publish-webgateway.py")] - }; - } - - if (scripts.Contains("sync-output.py")) - { - yield return new WorkflowDefinition - { - Id = "sync-output", - Label = "Sync Output", - Description = "Sync newest artifacts to output", - Group = "Build", - Steps = [ScriptStep("sync-output:run", "python scripts/sync-output.py", "scripts/sync-output.py")] - }; - } - - if (scripts.Contains("publish-output.py")) - { - yield return new WorkflowDefinition - { - Id = "stage-output", - Label = "Stage Output Bundle", - Description = "Publish and stage distributable output", - Group = "Build", - Steps = [ScriptStep("stage-output:run", "python scripts/publish-output.py", "scripts/publish-output.py")] - }; - } - - if (scripts.Contains("run-webgateway.py")) - { - yield return new WorkflowDefinition - { - Id = "run-gateway-dev", - Label = "Run WebGateway Server (Dev)", - Description = "Run gateway in development mode", - Group = "Dev", - Steps = - [ - ScriptStep("run-gateway-dev:run", "python scripts/run-webgateway.py --mode Dev", - "scripts/run-webgateway.py", "--mode", "Dev") - ] - }; - } - } - - private static string FindProjectRoot(string startDir) - { - var start = Path.GetFullPath(startDir); - var gitRoot = TryGetGitRoot(start); - if (!string.IsNullOrWhiteSpace(gitRoot)) - return gitRoot!; - - var best = start; - var bestScore = ScoreRoot(start); - var cursor = new DirectoryInfo(start); - while (cursor.Parent is not null) - { - cursor = cursor.Parent; - var score = ScoreRoot(cursor.FullName); - if (score > bestScore) - { - best = cursor.FullName; - bestScore = score; - } - } - - return best; - } - - private static int ScoreRoot(string path) - { - var score = 0; - if (File.Exists(Path.Combine(path, "package.json"))) score += 2; - if (File.Exists(Path.Combine(path, "pyproject.toml"))) score += 2; - if (File.Exists(Path.Combine(path, "Cargo.toml"))) score += 2; - if (File.Exists(Path.Combine(path, "docker-compose.yml")) || - File.Exists(Path.Combine(path, "docker-compose.yaml")) || - File.Exists(Path.Combine(path, "Dockerfile"))) score += 1; - if (Directory.EnumerateFiles(path, "*.sln", SearchOption.TopDirectoryOnly).Any()) score += 3; - if (Directory.Exists(Path.Combine(path, ".git"))) score += 1; - return score; - } - - private static string? TryGetGitRoot(string start) - { - try - { - var psi = new System.Diagnostics.ProcessStartInfo - { - FileName = "git", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true, - WorkingDirectory = start - }; - psi.ArgumentList.Add("rev-parse"); - psi.ArgumentList.Add("--show-toplevel"); - using var process = System.Diagnostics.Process.Start(psi); - if (process is null) - return null; - var stdout = process.StandardOutput.ReadToEnd(); - process.WaitForExit(2000); - if (process.ExitCode == 0 && !string.IsNullOrWhiteSpace(stdout)) - return stdout.Trim(); - return null; - } - catch - { - return null; - } - } - - private static IEnumerable EnumerateFilesBounded(string root, string pattern, int maxDepth) - { - var queue = new Queue<(string Dir, int Depth)>(); - queue.Enqueue((root, 0)); - - while (queue.Count > 0) - { - var (dir, depth) = queue.Dequeue(); - IEnumerable files = []; - try - { - files = Directory.EnumerateFiles(dir, pattern, SearchOption.TopDirectoryOnly); - } - catch - { - // Ignore unreadable directories. - } - - foreach (var file in files) - yield return file; - - if (depth >= maxDepth) - continue; - - IEnumerable subdirs = []; - try - { - subdirs = Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly); - } - catch - { - // Ignore unreadable directories. - } - - foreach (var subdir in subdirs) - { - var name = Path.GetFileName(subdir); - if (ExcludedDirectories.Contains(name)) - continue; - - queue.Enqueue((subdir, depth + 1)); - } - } - } -} diff --git a/Journal.DevTool/Config/ConfigLoader.cs b/Journal.DevTool/Config/ConfigLoader.cs deleted file mode 100644 index 507c9d6..0000000 --- a/Journal.DevTool/Config/ConfigLoader.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Nodes; -using Sdt.Core; - -namespace Sdt.Config; - -public sealed record LoadedProjectConfig( - DevToolConfig Config, - string ProjectRoot, - IReadOnlyList Warnings); - -public sealed record LegacyMigrationApplyResult( - bool Success, - string Message, - string? BackupPath = null, - string? ConfigPath = null); - -public static class ConfigLoader -{ - public const string WorkspaceDefaultsFileName = "sdt-defaults.json"; - - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - AllowTrailingCommas = true, - ReadCommentHandling = JsonCommentHandling.Skip, - }; - - /// - /// Walks up from (or CWD) until it finds devtool.json. - /// Returns null if not found. - /// - public static string? FindConfigPath(string? startDir = null) - { - var dir = new DirectoryInfo(startDir ?? Directory.GetCurrentDirectory()); - while (dir is not null) - { - var candidate = Path.Combine(dir.FullName, "devtool.json"); - if (File.Exists(candidate)) - return candidate; - dir = dir.Parent!; - } - return null; - } - - public static LoadedProjectConfig? FindAndLoad(string? startDir = null) - { - var configPath = FindConfigPath(startDir); - if (configPath is null) - return null; - - var projectRoot = Path.GetDirectoryName(configPath) - ?? throw new InvalidOperationException($"Could not resolve project root from {configPath}"); - - try - { - var effectiveConfig = LoadEffectiveConfig(projectRoot, configPath, out var defaultsPath); - var warnings = new List(); - if (!string.IsNullOrWhiteSpace(defaultsPath)) - warnings.Add($"Applied workspace defaults from {defaultsPath}."); - - var legacyMode = ResolveLegacyMode(); - if (legacyMode == LegacyMode.Strict && effectiveConfig.Workflows.Count == 0 && effectiveConfig.Targets.Count > 0) - { - var previewPath = Path.Combine(projectRoot, "devtool.generated.workflows.json"); - try - { - var previewConfig = WorkflowModelBuilder.BuildMigrationPreviewConfig(effectiveConfig, new RequirementResolver()); - File.WriteAllText(previewPath, ConfigBootstrapper.ToJson(previewConfig)); - } - catch - { - // Keep strict failure even if preview generation fails. - } - - throw new InvalidOperationException( - $"Legacy targets-only config detected at {configPath}. Strict mode requires workflows. " + - "Use migration preview file 'devtool.generated.workflows.json' and migrate devtool.json. " + - "Temporary rollback: set SDT_LEGACY_MODE=compat."); - } - - var normalized = WorkflowModelBuilder.Normalize(effectiveConfig, legacyMode, new RequirementResolver()); - warnings.AddRange(normalized.Warnings); - return new LoadedProjectConfig(effectiveConfig, projectRoot, warnings); - } - catch (Exception ex) - { - throw new InvalidOperationException( - $"Failed to parse devtool.json at {configPath}: {ex.Message}", ex); - } - } - - public static LegacyMigrationApplyResult ApplyLegacyTargetMigration( - string configPath, - bool createBackup = true) - { - try - { - if (!File.Exists(configPath)) - return new LegacyMigrationApplyResult(false, $"Config file not found: {configPath}"); - - var json = File.ReadAllText(configPath); - var config = JsonSerializer.Deserialize(json, JsonOptions) - ?? throw new InvalidOperationException("devtool.json deserialized to null."); - - if (config.Targets.Count == 0) - return new LegacyMigrationApplyResult(false, "No legacy targets found to migrate.", ConfigPath: configPath); - - var migrated = WorkflowModelBuilder.BuildMigrationPreviewConfig(config, new RequirementResolver()); - var backupPath = (string?)null; - if (createBackup) - { - backupPath = configPath + $".bak-{DateTimeOffset.Now:yyyyMMdd-HHmmss}"; - File.Copy(configPath, backupPath, overwrite: false); - } - - File.WriteAllText(configPath, ConfigBootstrapper.ToJson(migrated)); - return new LegacyMigrationApplyResult( - true, - "Legacy targets migrated to workflows.", - BackupPath: backupPath, - ConfigPath: configPath); - } - catch (Exception ex) - { - return new LegacyMigrationApplyResult(false, ex.Message, ConfigPath: configPath); - } - } - - private static LegacyMode ResolveLegacyMode() - { - var raw = Environment.GetEnvironmentVariable("SDT_LEGACY_MODE"); - return string.Equals(raw, "compat", StringComparison.OrdinalIgnoreCase) - ? LegacyMode.Compat - : LegacyMode.Strict; - } - - private static DevToolConfig LoadEffectiveConfig( - string projectRoot, - string projectConfigPath, - out string? defaultsPath) - { - defaultsPath = FindWorkspaceDefaultsPath(projectRoot); - var projectObj = LoadJsonObject(projectConfigPath, "project config"); - if (string.IsNullOrWhiteSpace(defaultsPath)) - return DeserializeConfig(projectObj, projectConfigPath); - - var defaultsObj = LoadJsonObject(defaultsPath!, "workspace defaults"); - var merged = MergeObjects(defaultsObj, projectObj); - return DeserializeConfig(merged, projectConfigPath); - } - - private static string? FindWorkspaceDefaultsPath(string startDir) - { - var workspaceBoundary = FindWorkspaceBoundary(startDir); - var dir = new DirectoryInfo(startDir); - while (dir is not null) - { - var candidate = Path.Combine(dir.FullName, WorkspaceDefaultsFileName); - if (File.Exists(candidate)) - return candidate; - if (workspaceBoundary is not null && - string.Equals(dir.FullName, workspaceBoundary, StringComparison.OrdinalIgnoreCase)) - { - break; - } - if (workspaceBoundary is null) - break; - dir = dir.Parent; - } - - return null; - } - - private static string? FindWorkspaceBoundary(string startDir) - { - var dir = new DirectoryInfo(startDir); - while (dir is not null) - { - var workspacePath = Path.Combine(dir.FullName, WorkspaceLoader.FileName); - if (File.Exists(workspacePath)) - return dir.FullName; - dir = dir.Parent; - } - - return null; - } - - private static JsonObject LoadJsonObject(string path, string label) - { - var json = File.ReadAllText(path); - var node = JsonNode.Parse(json) - ?? throw new InvalidOperationException($"{label} at {path} deserialized to null."); - if (node is not JsonObject obj) - throw new InvalidOperationException($"{label} at {path} must be a JSON object."); - return obj; - } - - private static DevToolConfig DeserializeConfig(JsonObject obj, string sourcePath) - { - return obj.Deserialize(JsonOptions) - ?? throw new InvalidOperationException($"devtool.json at {sourcePath} deserialized to null."); - } - - private static JsonObject MergeObjects(JsonObject baseObj, JsonObject overlayObj) - { - var result = (JsonObject)baseObj.DeepClone(); - foreach (var kv in overlayObj) - { - if (kv.Value is JsonObject overlayChild && - result[kv.Key] is JsonObject baseChild) - { - result[kv.Key] = MergeObjects(baseChild, overlayChild); - continue; - } - - result[kv.Key] = kv.Value?.DeepClone(); - } - - return result; - } -} diff --git a/Journal.DevTool/Config/DevToolConfig.cs b/Journal.DevTool/Config/DevToolConfig.cs deleted file mode 100644 index 4a4c108..0000000 --- a/Journal.DevTool/Config/DevToolConfig.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Sdt.Config; - -public sealed class DevToolConfig -{ - public string Name { get; init; } = "SDT Project"; - public string Version { get; init; } = "0.1.0"; - public List Targets { get; init; } = []; - public List Workflows { get; init; } = []; - public List Env { get; init; } = []; - public ToolchainConfig? Toolchains { get; init; } - public ToolingConfig? Tooling { get; init; } - public ProjectMetadata? Project { get; init; } - public DebugConfig? Debug { get; init; } -} - -public sealed class BuildTarget -{ - public string Id { get; init; } = ""; - public string Label { get; init; } = ""; - public string Description { get; init; } = ""; - public string Group { get; init; } = "General"; - - /// Executable name. Null = virtual aggregator (runs DependsOn only). - public string? Command { get; init; } - - public List Args { get; init; } = []; - - /// Working directory relative to project root. - public string WorkingDir { get; init; } = "."; - - public List DependsOn { get; init; } = []; -} - -public sealed class EnvVarDef -{ - public string Key { get; init; } = ""; - public string Description { get; init; } = ""; - - [System.Text.Json.Serialization.JsonPropertyName("default")] - public string DefaultValue { get; init; } = ""; - - /// If non-empty, shown as a dropdown. Otherwise free-text input. - public List Options { get; init; } = []; -} - -public sealed class WorkflowDefinition -{ - public string Id { get; init; } = ""; - public string Label { get; init; } = ""; - public string Description { get; init; } = ""; - public string Group { get; init; } = "General"; - public List DependsOn { get; init; } = []; - public List Steps { get; init; } = []; -} - -public sealed class WorkflowStep -{ - public string Id { get; init; } = ""; - public string Label { get; init; } = ""; - public string? Command { get; init; } - public List Args { get; init; } = []; - public string WorkingDir { get; init; } = "."; - public string? Action { get; init; } - public List ActionArgs { get; init; } = []; - public List Requires { get; init; } = []; -} - -public sealed class ToolRequirement -{ - public string Tool { get; init; } = ""; - - [JsonConverter(typeof(JsonStringEnumConverter))] - public InstallPolicy InstallPolicy { get; init; } = InstallPolicy.Prompt; -} - -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum InstallPolicy -{ - Prompt, - Auto, - Never, -} - -public sealed class ToolingConfig -{ - public List Tools { get; init; } = []; -} - -public sealed class ToolInstallDefinition -{ - public string Tool { get; init; } = ""; - public List PreferredInstallCommands { get; init; } = []; - public List Executables { get; init; } = []; -} - -public sealed class ProjectMetadata -{ - public string Type { get; init; } = ""; - public List RootHints { get; init; } = []; - public List Artifacts { get; init; } = []; -} - -public sealed class DebugConfig -{ - public List Profiles { get; init; } = []; - public DebugDiagnosticsOptions Diagnostics { get; init; } = new(); -} - -public sealed class DebugProfileDefinition -{ - public string Id { get; init; } = ""; - public string Label { get; init; } = ""; - public string Type { get; init; } = "generic"; - public string Command { get; init; } = ""; - public List Args { get; init; } = []; - public string WorkingDir { get; init; } = "."; - public Dictionary Env { get; init; } = new(StringComparer.OrdinalIgnoreCase); - public List Requires { get; init; } = []; - public DebugAttachConfig? Attach { get; init; } -} - -public sealed class DebugAttachConfig -{ - public string Kind { get; init; } = ""; - public int? Port { get; init; } - public string? ProcessName { get; init; } - public string? Note { get; init; } -} - -public sealed class DebugDiagnosticsOptions -{ - public bool Enabled { get; init; } = true; - public string OutputDir { get; init; } = ".sdt/debug"; - public bool IncludeAllEnv { get; init; } = false; - public List CaptureEnvKeys { get; init; } = []; - public bool BundleOnFailure { get; init; } = true; -} - -// ── Toolchain config ────────────────────────────────────────────────────────── - -public sealed class ToolchainConfig -{ - public PythonToolchain? Python { get; init; } - public NodeToolchain? Node { get; init; } -} - -public sealed class PythonToolchain -{ - /// Python executable (e.g. "python3.14", "python"). - public string Executable { get; init; } = "python"; - - /// Windows-specific override (e.g. "py" when using the launcher). - public string? WindowsExecutable { get; init; } - - /// Optional version flag to pass (e.g. "-3.14" for py launcher). - public string? LauncherVersion { get; init; } - - /// Venv directory relative to project root. - public string VenvDir { get; init; } = ".venv"; - - public List Profiles { get; init; } = []; - - /// Optional path to a pip wrapper script (relative to project root). - public string? PipScript { get; init; } -} - -public sealed class PythonProfile -{ - public string Id { get; init; } = ""; - public string Label { get; init; } = ""; - public string RequirementsFile { get; init; } = ""; - public string? ExtraIndexUrl { get; init; } - public List PostInstallCommands { get; init; } = []; -} - -public sealed class NodeToolchain -{ - /// Package manager: "npm", "pnpm", or "yarn". - public string PackageManager { get; init; } = "npm"; - - /// Working directory for the frontend (relative to project root). - public string WorkingDir { get; init; } = "."; -} diff --git a/Journal.DevTool/Config/WorkflowModelBuilder.cs b/Journal.DevTool/Config/WorkflowModelBuilder.cs deleted file mode 100644 index 6501835..0000000 --- a/Journal.DevTool/Config/WorkflowModelBuilder.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Sdt.Core; - -namespace Sdt.Config; - -public enum LegacyMode -{ - Strict, - Compat, -} - -public sealed record WorkflowNormalizationResult( - IReadOnlyList Workflows, - IReadOnlyList Warnings); - -public static class WorkflowModelBuilder -{ - public static WorkflowNormalizationResult Normalize( - DevToolConfig config, - LegacyMode legacyMode = LegacyMode.Strict, - IRequirementResolver? requirementResolver = null) - { - requirementResolver ??= new RequirementResolver(); - var warnings = new List(); - - if (config.Workflows.Count > 0) - { - if (config.Targets.Count > 0) - { - warnings.Add("Both 'workflows' and legacy 'targets' are present. SDT will use 'workflows'."); - } - - return new WorkflowNormalizationResult(config.Workflows, warnings); - } - - if (config.Targets.Count == 0) - { - warnings.Add("No 'workflows' or legacy 'targets' were found."); - return new WorkflowNormalizationResult([], warnings); - } - - if (legacyMode == LegacyMode.Strict) - { - throw new InvalidOperationException( - "Legacy 'targets' are not allowed in strict mode. Migrate to 'workflows' or set SDT_LEGACY_MODE=compat temporarily."); - } - - warnings.Add("Using legacy 'targets' schema. Migrate to 'workflows' for v1+ features."); - return new WorkflowNormalizationResult(ConvertLegacyTargets(config.Targets, requirementResolver), warnings); - } - - public static DevToolConfig BuildMigrationPreviewConfig(DevToolConfig config, IRequirementResolver? requirementResolver = null) - { - requirementResolver ??= new RequirementResolver(); - return new DevToolConfig - { - Name = config.Name, - Version = config.Version, - Targets = [], - Workflows = ConvertLegacyTargets(config.Targets, requirementResolver), - Env = config.Env, - Toolchains = config.Toolchains, - Tooling = config.Tooling, - Project = config.Project, - Debug = config.Debug, - }; - } - - private static List ConvertLegacyTargets( - IReadOnlyList targets, - IRequirementResolver requirementResolver) - { - var workflows = new List(targets.Count); - foreach (var target in targets) - { - var step = target.Command is null - ? null - : new WorkflowStep - { - Id = $"{target.Id}:run", - Label = string.IsNullOrWhiteSpace(target.Label) ? target.Id : target.Label, - Command = target.Command, - Args = target.Args, - WorkingDir = target.WorkingDir, - Requires = requirementResolver.Resolve(target), - }; - - workflows.Add(new WorkflowDefinition - { - Id = target.Id, - Label = target.Label, - Description = target.Description, - Group = target.Group, - DependsOn = target.DependsOn, - Steps = step is null ? [] : [step], - }); - } - - return workflows; - } -} diff --git a/Journal.DevTool/Config/WorkspaceConfig.cs b/Journal.DevTool/Config/WorkspaceConfig.cs deleted file mode 100644 index 92c9b57..0000000 --- a/Journal.DevTool/Config/WorkspaceConfig.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Sdt.Config; - -public sealed class WorkspaceConfig -{ - public string Name { get; init; } = "SDT Workspace"; - public List Projects { get; init; } = []; -} - -public sealed class WorkspaceProject -{ - public string Name { get; init; } = ""; - public string Description { get; init; } = ""; - - /// - /// Relative or absolute path to the project root - /// (the directory containing devtool.json). - /// - public string Path { get; init; } = ""; - public List Tags { get; init; } = []; - public List ToolFamilies { get; init; } = []; - public bool Disabled { get; init; } = false; -} diff --git a/Journal.DevTool/Config/WorkspaceLoader.cs b/Journal.DevTool/Config/WorkspaceLoader.cs deleted file mode 100644 index 90cc7a5..0000000 --- a/Journal.DevTool/Config/WorkspaceLoader.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System.Text.Json; - -namespace Sdt.Config; - -public static class WorkspaceLoader -{ - public const string FileName = "sdt-workspace.json"; - - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - AllowTrailingCommas = true, - ReadCommentHandling = JsonCommentHandling.Skip, - }; - - /// - /// Walks up from (or CWD) to find sdt-workspace.json. - /// Returns null if not found. - /// - public static (WorkspaceConfig Config, string WorkspaceRoot)? FindAndLoad(string? startDir = null) - { - var dir = new DirectoryInfo(startDir ?? Directory.GetCurrentDirectory()); - while (dir is not null) - { - var candidate = Path.Combine(dir.FullName, FileName); - if (File.Exists(candidate)) - { - try - { - var json = File.ReadAllText(candidate); - var config = JsonSerializer.Deserialize(json, JsonOptions) - ?? throw new InvalidOperationException($"{FileName} deserialized to null."); - return (config, dir.FullName); - } - catch (Exception ex) - { - throw new InvalidOperationException( - $"Failed to parse {FileName} at {candidate}: {ex.Message}", ex); - } - } - dir = dir.Parent!; - } - - // No workspace file found; synthesize one by scanning nearby project roots. - return TryAutoDiscover(startDir ?? Directory.GetCurrentDirectory()); - } - - /// - /// Resolves the absolute project root for a workspace project entry. - /// - public static string ResolveProjectRoot(string workspaceRoot, WorkspaceProject project) - => Path.GetFullPath(Path.IsPathRooted(project.Path) - ? project.Path - : Path.Combine(workspaceRoot, project.Path)); - - public static string GetWorkspaceFilePath(string workspaceRoot) - => Path.Combine(workspaceRoot, FileName); - - public static void Save(string workspaceRoot, WorkspaceConfig workspace) - { - var path = GetWorkspaceFilePath(workspaceRoot); - var saveOptions = new JsonSerializerOptions(JsonOptions) - { - WriteIndented = true - }; - var json = JsonSerializer.Serialize(workspace, saveOptions); - File.WriteAllText(path, json + Environment.NewLine); - } - - private static (WorkspaceConfig Config, string WorkspaceRoot)? TryAutoDiscover(string startDir) - { - LoadedProjectConfig? loaded; - try - { - loaded = ConfigLoader.FindAndLoad(startDir); - } - catch - { - return null; - } - - if (loaded is null) - return null; - - var currentRoot = loaded.ProjectRoot; - var parent = Directory.GetParent(currentRoot); - var workspaceRoot = parent?.FullName ?? currentRoot; - - var roots = DiscoverProjectRoots(workspaceRoot, currentRoot); - if (roots.Count == 0) - return null; - - var projects = new List(); - foreach (var root in roots) - { - LoadedProjectConfig? cfg; - try - { - cfg = ConfigLoader.FindAndLoad(root); - } - catch - { - continue; - } - - var name = cfg?.Config.Name; - projects.Add(new WorkspaceProject - { - Name = string.IsNullOrWhiteSpace(name) ? new DirectoryInfo(root).Name : name!, - Description = $"Auto-discovered at {root}", - Path = Path.GetRelativePath(workspaceRoot, root), - }); - } - - return ( - new WorkspaceConfig - { - Name = "SDT Auto Workspace", - Projects = projects - }, - workspaceRoot); - } - - private static List DiscoverProjectRoots(string workspaceRoot, string currentRoot) - { - var set = new HashSet(StringComparer.OrdinalIgnoreCase); - var excluded = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "bin", - "obj", - ".git", - ".venv", - "node_modules", - }; - - void AddIfProject(string path) - { - var full = Path.GetFullPath(path); - if (File.Exists(Path.Combine(full, "devtool.json"))) - set.Add(full); - } - - AddIfProject(currentRoot); - AddIfProject(workspaceRoot); - - try - { - foreach (var dir in Directory.EnumerateDirectories(workspaceRoot)) - { - if (excluded.Contains(Path.GetFileName(dir))) - continue; - - AddIfProject(dir); - foreach (var sub in Directory.EnumerateDirectories(dir)) - { - if (excluded.Contains(Path.GetFileName(sub))) - continue; - AddIfProject(sub); - } - } - } - catch - { - // Ignore inaccessible directories during auto-discovery. - } - - return set.OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToList(); - } -} diff --git a/Journal.DevTool/Core/ActionRunner.cs b/Journal.DevTool/Core/ActionRunner.cs deleted file mode 100644 index 735415b..0000000 --- a/Journal.DevTool/Core/ActionRunner.cs +++ /dev/null @@ -1,207 +0,0 @@ -using Sdt.Config; -using Sdt.Runner; - -namespace Sdt.Core; - -public sealed class ActionRunner : IActionRunner -{ - public async Task RunStepAsync( - WorkflowStep step, - string projectRoot, - Action onOutput, - CancellationToken cancellationToken = default) - { - if (!string.IsNullOrWhiteSpace(step.Action)) - { - var scriptPath = ScriptLocator.FindHelperScript(projectRoot, "build.py"); - if (scriptPath is null) - throw new InvalidOperationException("build.py not found in bundled scripts or project scripts directory."); - - var actionArgs = new List - { - scriptPath, - step.Action, - "--project-root", - projectRoot, - }; - actionArgs.AddRange(step.ActionArgs); - - return await ProcessRunner.RunAsync( - PythonResolver.ResolveExecutable(), - actionArgs, - projectRoot, - onOutput, - cancellationToken: cancellationToken).ConfigureAwait(false); - } - - if (string.IsNullOrWhiteSpace(step.Command)) - return new RunResult(0, TimeSpan.Zero); - - var workingDir = Path.GetFullPath(Path.Combine(projectRoot, step.WorkingDir)); - - var pwshReroute = await TryRunLegacyPwshScriptViaPythonAsync( - step, - projectRoot, - workingDir, - onOutput, - cancellationToken).ConfigureAwait(false); - if (pwshReroute is not null) - return pwshReroute; - - return await ProcessRunner.RunAsync( - step.Command, - step.Args, - workingDir, - onOutput, - cancellationToken: cancellationToken).ConfigureAwait(false); - } - - private static async Task TryRunLegacyPwshScriptViaPythonAsync( - WorkflowStep step, - string projectRoot, - string workingDir, - Action onOutput, - CancellationToken cancellationToken) - { - if (!IsPowerShellCommand(step.Command)) - return null; - - var args = step.Args; - var fileIndex = FindArgIndex(args, "-File"); - if (fileIndex < 0 || fileIndex + 1 >= args.Count) - return null; - - var psScriptArg = args[fileIndex + 1]; - if (!psScriptArg.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) - return null; - - var pyScriptPath = ResolvePythonScriptPath(projectRoot, workingDir, psScriptArg); - if (pyScriptPath is null) - return null; - - var translated = TranslatePowerShellArgsToPython(args.Skip(fileIndex + 2)); - var pythonArgs = new List { pyScriptPath }; - pythonArgs.AddRange(translated); - - onOutput($"Legacy PowerShell target detected. Trying Python script first: {Path.GetFileName(pyScriptPath)}", false); - - var pyRun = await ProcessRunner.RunAsync( - PythonResolver.ResolveExecutable(), - pythonArgs, - workingDir, - onOutput, - cancellationToken: cancellationToken).ConfigureAwait(false); - - if (pyRun.Success) - return pyRun; - - var psScriptPath = ResolveScriptPath(workingDir, psScriptArg); - if (psScriptPath is null || !File.Exists(psScriptPath)) - return pyRun; - - onOutput( - $"Python script failed (exit {pyRun.ExitCode}). Falling back to legacy PowerShell script: {psScriptArg}", - true); - return null; - } - - private static string? ResolvePythonScriptPath(string projectRoot, string workingDir, string psScriptArg) - { - var pyArg = Path.ChangeExtension(psScriptArg, ".py"); - var candidate = ResolveScriptPath(workingDir, pyArg); - if (candidate is not null && File.Exists(candidate)) - return candidate; - - var fileName = Path.GetFileName(pyArg); - return ScriptLocator.FindHelperScript(projectRoot, fileName); - } - - private static string? ResolveScriptPath(string workingDir, string scriptArg) - { - if (Path.IsPathRooted(scriptArg)) - return scriptArg; - return Path.GetFullPath(Path.Combine(workingDir, scriptArg)); - } - - private static bool IsPowerShellCommand(string? command) - { - if (string.IsNullOrWhiteSpace(command)) - return false; - - var normalized = Path.GetFileNameWithoutExtension(command).ToLowerInvariant(); - return normalized is "pwsh" or "powershell"; - } - - private static int FindArgIndex(IReadOnlyList args, string name) - { - for (var i = 0; i < args.Count; i++) - { - if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) - return i; - } - return -1; - } - - private static List TranslatePowerShellArgsToPython(IEnumerable inputArgs) - { - var result = new List(); - var list = inputArgs.ToList(); - - for (var i = 0; i < list.Count; i++) - { - var token = list[i]; - if (!token.StartsWith("-", StringComparison.Ordinal) || token == "-") - { - result.Add(token); - continue; - } - - var key = token.TrimStart('-'); - if (key.Length == 0) - continue; - - var mapped = MapPowerShellParameter(key); - var nextIsValue = (i + 1) < list.Count && !list[i + 1].StartsWith("-", StringComparison.Ordinal); - result.Add(mapped); - if (nextIsValue) - { - result.Add(list[i + 1]); - i++; - } - } - - return result; - } - - private static string MapPowerShellParameter(string key) - { - return key.ToLowerInvariant() switch - { - "tauribundles" => "--tauri-bundles", - "projectroot" => "--project-root", - "reporoot" => "--repo-root", - "outputzip" => "--output-zip", - "inputzip" => "--input-zip", - "workingdir" => "--working-dir", - "outputdir" => "--output-dir", - _ => "--" + ToKebabCase(key) - }; - } - - private static string ToKebabCase(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return value.ToLowerInvariant(); - - var chars = new List(value.Length + 4); - for (var i = 0; i < value.Length; i++) - { - var c = value[i]; - if (char.IsUpper(c) && i > 0 && value[i - 1] != '-') - chars.Add('-'); - chars.Add(char.ToLowerInvariant(c)); - } - - return new string(chars.ToArray()); - } -} diff --git a/Journal.DevTool/Core/CommandResolver.cs b/Journal.DevTool/Core/CommandResolver.cs deleted file mode 100644 index 262b6fa..0000000 --- a/Journal.DevTool/Core/CommandResolver.cs +++ /dev/null @@ -1,173 +0,0 @@ -namespace Sdt.Core; - -public enum CommandResolutionSource -{ - Exact, - Path, - Shim, - NodeAdjacentShim, - ConfiguredOverride, - Fallback, -} - -public sealed record CommandResolutionResult( - string Requested, - string Resolved, - CommandResolutionSource Source); - -public static class CommandResolver -{ - public static CommandResolutionResult ResolveWithTrace(string command, Config.DevToolConfig? config = null, string? tool = null) - { - if (string.IsNullOrWhiteSpace(command)) - return new CommandResolutionResult(command, command, CommandResolutionSource.Exact); - - if (!OperatingSystem.IsWindows()) - return new CommandResolutionResult(command, command, CommandResolutionSource.Exact); - - if (command.Contains(Path.DirectorySeparatorChar) || command.Contains(Path.AltDirectorySeparatorChar)) - return new CommandResolutionResult(command, command, CommandResolutionSource.Exact); - - var normalized = command.ToLowerInvariant(); - if (Path.HasExtension(command)) - { - var extensionResolved = ResolveFromPath(command); - return extensionResolved is null - ? new CommandResolutionResult(command, command, CommandResolutionSource.Fallback) - : new CommandResolutionResult(command, extensionResolved, CommandResolutionSource.Path); - } - - var overrideTool = string.IsNullOrWhiteSpace(tool) ? normalized : tool.ToLowerInvariant(); - var configuredCandidates = config?.Tooling?.Tools - .FirstOrDefault(t => string.Equals(t.Tool, overrideTool, StringComparison.OrdinalIgnoreCase)) - ?.Executables - ?.Where(x => !string.IsNullOrWhiteSpace(x)) - .ToList(); - - if (configuredCandidates is not null) - { - foreach (var configured in configuredCandidates) - { - var resolvedConfigured = ResolveFromPath(configured!) ?? configured!; - if (IsUsableExecutable(resolvedConfigured)) - return new CommandResolutionResult(command, resolvedConfigured, CommandResolutionSource.ConfiguredOverride); - } - } - - foreach (var candidate in BuildWindowsCandidates(command, normalized)) - { - var resolved = ResolveFromPath(candidate); - if (!string.IsNullOrWhiteSpace(resolved)) - { - var source = candidate.EndsWith(".cmd", StringComparison.OrdinalIgnoreCase) || - candidate.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) || - candidate.EndsWith(".bat", StringComparison.OrdinalIgnoreCase) - ? CommandResolutionSource.Shim - : CommandResolutionSource.Path; - return new CommandResolutionResult(command, resolved!, source); - } - } - - if (normalized is "npm" or "npx" or "pnpm" or "yarn") - { - var nodePath = ResolveFromPath("node.exe") ?? ResolveFromPath("node"); - if (!string.IsNullOrWhiteSpace(nodePath)) - { - var nodeDir = Path.GetDirectoryName(nodePath); - if (!string.IsNullOrWhiteSpace(nodeDir)) - { - var shim = Path.Combine(nodeDir, normalized + ".cmd"); - if (File.Exists(shim)) - return new CommandResolutionResult(command, shim, CommandResolutionSource.NodeAdjacentShim); - } - } - } - - var fallback = BuildWindowsCandidates(command, normalized).LastOrDefault() ?? command; - return new CommandResolutionResult(command, fallback, CommandResolutionSource.Fallback); - } - - public static string Resolve(string command) - { - return ResolveWithTrace(command).Resolved; - } - - private static string? ResolveFromPath(string executable) - { - var pathValue = Environment.GetEnvironmentVariable("PATH"); - if (string.IsNullOrWhiteSpace(pathValue)) - return null; - - foreach (var segment in pathValue.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) - { - try - { - var expandedSegment = ExpandWindowsPathTokens(segment.Trim()); - var candidate = Path.Combine(expandedSegment, executable); - if (File.Exists(candidate)) - { - // If PATH lookup hit extensionless npm but npm.cmd exists beside it, prefer npm.cmd. - var fileName = Path.GetFileName(candidate).ToLowerInvariant(); - if (fileName is "npm" or "npx" or "pnpm" or "yarn" or "tauri") - { - var shim = candidate + ".cmd"; - if (File.Exists(shim)) - return shim; - } - return candidate; - } - } - catch - { - // Ignore malformed PATH segments. - } - } - - return null; - } - - private static string ExpandWindowsPathTokens(string segment) - { - if (string.IsNullOrWhiteSpace(segment) || !OperatingSystem.IsWindows()) - return segment; - - var expanded = segment; - for (var i = 0; i < 4; i++) - { - var next = Environment.ExpandEnvironmentVariables(expanded); - if (string.Equals(next, expanded, StringComparison.Ordinal)) - break; - expanded = next; - } - return expanded; - } - - private static List BuildWindowsCandidates(string command, string normalized) - { - var candidates = new List(); - if (normalized is "npm" or "npx" or "pnpm" or "yarn" or "tauri") - { - candidates.Add(command + ".cmd"); - candidates.Add(command + ".exe"); - candidates.Add(command + ".bat"); - candidates.Add(command); - } - else - { - candidates.Add(command); - } - - return candidates; - } - - private static bool IsUsableExecutable(string resolved) - { - if (string.IsNullOrWhiteSpace(resolved)) - return false; - - if (Path.IsPathRooted(resolved)) - return File.Exists(resolved); - - return ResolveFromPath(resolved) is not null; - } -} diff --git a/Journal.DevTool/Core/ConfigDoctorAutoFixService.cs b/Journal.DevTool/Core/ConfigDoctorAutoFixService.cs deleted file mode 100644 index d7bca94..0000000 --- a/Journal.DevTool/Core/ConfigDoctorAutoFixService.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Sdt.Config; - -namespace Sdt.Core; - -public sealed record DoctorAutoFixResult( - bool Success, - string Message, - int CreatedDirectories = 0, - string? BackupPath = null); - -public sealed class ConfigDoctorAutoFixService -{ - public IReadOnlyList FindMissingWorkingDirectories(DevToolConfig config, string projectRoot) - { - var missing = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var workflow in config.Workflows) - { - foreach (var step in workflow.Steps) - { - if (string.IsNullOrWhiteSpace(step.WorkingDir)) - continue; - var path = Path.GetFullPath(Path.Combine(projectRoot, step.WorkingDir)); - if (!Directory.Exists(path)) - missing.Add(path); - } - } - - return missing.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList(); - } - - public DoctorAutoFixResult CreateMissingWorkingDirectories(IReadOnlyList directories) - { - var created = 0; - try - { - foreach (var dir in directories) - { - if (Directory.Exists(dir)) - continue; - Directory.CreateDirectory(dir); - created++; - } - - return new DoctorAutoFixResult( - Success: true, - Message: created == 0 ? "No directories needed creation." : $"Created {created} missing working director{(created == 1 ? "y" : "ies")}.", - CreatedDirectories: created); - } - catch (Exception ex) - { - return new DoctorAutoFixResult(false, ex.Message, CreatedDirectories: created); - } - } - - public DoctorAutoFixResult ApplyLegacyMigration(string projectRoot) - { - var configPath = ConfigLoader.FindConfigPath(projectRoot); - if (string.IsNullOrWhiteSpace(configPath)) - return new DoctorAutoFixResult(false, "Could not find devtool.json for migration."); - - var migration = ConfigLoader.ApplyLegacyTargetMigration(configPath, createBackup: true); - return new DoctorAutoFixResult( - Success: migration.Success, - Message: migration.Message, - BackupPath: migration.BackupPath); - } -} diff --git a/Journal.DevTool/Core/ConfigDoctorService.cs b/Journal.DevTool/Core/ConfigDoctorService.cs deleted file mode 100644 index 2b6fe91..0000000 --- a/Journal.DevTool/Core/ConfigDoctorService.cs +++ /dev/null @@ -1,270 +0,0 @@ -using Sdt.Config; - -namespace Sdt.Core; - -public enum DoctorStatus -{ - Pass, - Warn, - Fail, -} - -public sealed record DoctorCheck( - string Name, - DoctorStatus Status, - string Detail, - string? Fix = null); - -public sealed record DoctorReport( - IReadOnlyList Checks) -{ - public bool HasFailures => Checks.Any(c => c.Status == DoctorStatus.Fail); - public bool HasWarnings => Checks.Any(c => c.Status == DoctorStatus.Warn); -} - -public sealed class ConfigDoctorService( - IToolProbe? toolProbe = null, - IRequirementResolver? requirementResolver = null) -{ - private readonly IToolProbe _toolProbe = toolProbe ?? new ToolProbeService(); - private readonly IRequirementResolver _requirementResolver = requirementResolver ?? new RequirementResolver(); - - public async Task RunAsync( - DevToolConfig config, - string projectRoot, - CancellationToken cancellationToken = default) - { - var checks = new List(); - var workflowMap = config.Workflows.ToDictionary(w => w.Id, StringComparer.OrdinalIgnoreCase); - - AddSchemaChecks(config, checks); - AddWorkflowChecks(config.Workflows, workflowMap, projectRoot, checks); - AddPathChecks(config, projectRoot, checks); - await AddToolProbeChecksAsync(config, projectRoot, checks, cancellationToken).ConfigureAwait(false); - - return new DoctorReport(checks); - } - - private static void AddSchemaChecks(DevToolConfig config, List checks) - { - if (config.Workflows.Count == 0 && config.Targets.Count == 0) - { - checks.Add(new DoctorCheck( - "Config schema", - DoctorStatus.Fail, - "No workflows or legacy targets found.", - "Add workflows or run SDT init/bootstrap.")); - return; - } - - if (config.Workflows.Count == 0 && config.Targets.Count > 0) - { - checks.Add(new DoctorCheck( - "Legacy schema", - DoctorStatus.Fail, - "Targets-only config detected (strict mode will block execution).", - "Use SYSTEM -> Migrate legacy targets -> workflows.")); - return; - } - - if (config.Targets.Count > 0) - { - checks.Add(new DoctorCheck( - "Legacy schema", - DoctorStatus.Warn, - "Both workflows and legacy targets are present.", - "Prefer workflows-only config and remove legacy targets once migrated.")); - } - else - { - checks.Add(new DoctorCheck("Config schema", DoctorStatus.Pass, "Workflow-first config detected.")); - } - } - - private static void AddWorkflowChecks( - IReadOnlyList workflows, - IReadOnlyDictionary workflowMap, - string projectRoot, - List checks) - { - var duplicateIds = workflows - .GroupBy(w => w.Id, StringComparer.OrdinalIgnoreCase) - .Where(g => g.Count() > 1) - .Select(g => g.Key) - .ToList(); - - if (duplicateIds.Count > 0) - { - checks.Add(new DoctorCheck( - "Workflow IDs", - DoctorStatus.Fail, - $"Duplicate workflow IDs: {string.Join(", ", duplicateIds)}", - "Ensure each workflow has a unique id.")); - } - else - { - checks.Add(new DoctorCheck("Workflow IDs", DoctorStatus.Pass, "No duplicate workflow IDs.")); - } - - var brokenDeps = new List(); - foreach (var workflow in workflows) - { - foreach (var dep in workflow.DependsOn) - { - if (!workflowMap.ContainsKey(dep)) - brokenDeps.Add($"{workflow.Id} -> {dep}"); - } - } - - if (brokenDeps.Count > 0) - { - checks.Add(new DoctorCheck( - "Workflow dependencies", - DoctorStatus.Fail, - $"Missing dependencies: {string.Join("; ", brokenDeps)}", - "Fix dependsOn IDs to reference existing workflows.")); - } - else - { - checks.Add(new DoctorCheck("Workflow dependencies", DoctorStatus.Pass, "All workflow dependencies are valid.")); - } - - var invalidSteps = new List(); - var missingWorkingDirs = new List(); - foreach (var workflow in workflows) - { - foreach (var step in workflow.Steps) - { - if (string.IsNullOrWhiteSpace(step.Command) && string.IsNullOrWhiteSpace(step.Action)) - invalidSteps.Add($"{workflow.Id}/{step.Id}"); - - var stepDir = Path.GetFullPath(Path.Combine(projectRoot, step.WorkingDir)); - if (!Directory.Exists(stepDir)) - missingWorkingDirs.Add($"{workflow.Id}/{step.Id} -> {step.WorkingDir}"); - } - } - - if (invalidSteps.Count > 0) - { - checks.Add(new DoctorCheck( - "Step definitions", - DoctorStatus.Fail, - $"Steps missing command/action: {string.Join(", ", invalidSteps)}", - "Each step must define either action or command.")); - } - else - { - checks.Add(new DoctorCheck("Step definitions", DoctorStatus.Pass, "All steps define an action or command.")); - } - - if (missingWorkingDirs.Count > 0) - { - checks.Add(new DoctorCheck( - "Working directories", - DoctorStatus.Warn, - $"Missing directories: {string.Join("; ", missingWorkingDirs)}", - "Create missing directories or fix step workingDir values.")); - } - else - { - checks.Add(new DoctorCheck("Working directories", DoctorStatus.Pass, "All referenced working directories exist.")); - } - } - - private static void AddPathChecks(DevToolConfig config, string projectRoot, List checks) - { - var configPath = Path.Combine(projectRoot, "devtool.json"); - checks.Add(File.Exists(configPath) - ? new DoctorCheck("Project root", DoctorStatus.Pass, $"Config found at {configPath}") - : new DoctorCheck("Project root", DoctorStatus.Fail, $"devtool.json not found at {configPath}", "Run SDT init/bootstrap.")); - - if (OperatingSystem.IsWindows()) - { - var pathValue = Environment.GetEnvironmentVariable("PATH") ?? string.Empty; - var unresolvedSegments = pathValue - .Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries) - .Where(s => s.Contains('%') && Environment.ExpandEnvironmentVariables(s) == s) - .Take(4) - .ToList(); - - if (unresolvedSegments.Count > 0) - { - checks.Add(new DoctorCheck( - "PATH expansion", - DoctorStatus.Warn, - $"Unresolved PATH tokens: {string.Join(" | ", unresolvedSegments)}", - "Set referenced env vars or remove invalid PATH segments.")); - } - else - { - checks.Add(new DoctorCheck("PATH expansion", DoctorStatus.Pass, "No unresolved PATH token segments detected.")); - } - } - - if (config.Project?.RootHints.Count > 0) - { - checks.Add(new DoctorCheck("Root hints", DoctorStatus.Pass, $"Configured root hints: {string.Join(", ", config.Project.RootHints)}")); - } - else - { - checks.Add(new DoctorCheck( - "Root hints", - DoctorStatus.Warn, - "No project.rootHints configured.", - "Add rootHints markers (for example .git, *.sln, package.json).")); - } - } - - private async Task AddToolProbeChecksAsync( - DevToolConfig config, - string projectRoot, - List checks, - CancellationToken cancellationToken) - { - var requiredTools = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var workflow in config.Workflows) - { - foreach (var step in workflow.Steps) - { - foreach (var req in _requirementResolver.Resolve(step)) - requiredTools.Add(req.Tool); - } - } - - foreach (var profile in config.Debug?.Profiles ?? []) - { - foreach (var req in profile.Requires) - requiredTools.Add(req.Tool); - } - - foreach (var toolDef in config.Tooling?.Tools ?? []) - requiredTools.Add(toolDef.Tool); - - if (requiredTools.Count == 0) - { - checks.Add(new DoctorCheck("Tool probes", DoctorStatus.Warn, "No tools discovered from workflows/debug/tooling.")); - return; - } - - foreach (var tool in requiredTools.OrderBy(t => t, StringComparer.OrdinalIgnoreCase)) - { - var probe = await _toolProbe.ProbeAsync(tool, projectRoot, config, cancellationToken).ConfigureAwait(false); - if (probe.IsAvailable) - { - checks.Add(new DoctorCheck( - $"Tool: {tool}", - DoctorStatus.Pass, - string.IsNullOrWhiteSpace(probe.Version) ? "available" : probe.Version!, - probe.Details)); - } - else - { - checks.Add(new DoctorCheck( - $"Tool: {tool}", - DoctorStatus.Fail, - string.IsNullOrWhiteSpace(probe.Details) ? "not available" : probe.Details!, - $"Install/configure {tool} or set tooling.tools[].executables for non-standard paths.")); - } - } - } -} diff --git a/Journal.DevTool/Core/Contracts.cs b/Journal.DevTool/Core/Contracts.cs deleted file mode 100644 index eb19cd0..0000000 --- a/Journal.DevTool/Core/Contracts.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Sdt.Config; -using Sdt.Runner; - -namespace Sdt.Core; - -public sealed record ProbeResult( - string Tool, - bool IsAvailable, - string? Version = null, - string? Details = null); - -public sealed record InstallCommand( - string Command, - IReadOnlyList Args); - -public sealed record InstallPlan( - string Tool, - bool Supported, - string Summary, - IReadOnlyList Commands); - -public sealed record WorkflowStepResult( - string WorkflowId, - string StepId, - string StepLabel, - RunResult Result); - -public enum ExecutionStopReason -{ - MissingPrereq, - InstallFailed, - CommandFailed, - ValidationFailed, - UserDeclined, -} - -public sealed record WorkflowExecutionResult( - bool Success, - ExecutionStopReason? StopReason, - string Message, - IReadOnlyList Steps); - -public interface IToolProbe -{ - Task ProbeAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default); -} - -public interface IPrereqInstaller -{ - Task GetInstallPlanAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default); - - Task RunInstallAsync( - InstallCommand command, - string projectRoot, - Action onOutput, - CancellationToken cancellationToken = default); -} - -public interface IActionRunner -{ - Task RunStepAsync( - WorkflowStep step, - string projectRoot, - Action onOutput, - CancellationToken cancellationToken = default); -} - -public interface IWorkflowPlanner -{ - List ResolvePlan( - WorkflowDefinition workflow, - IReadOnlyDictionary allWorkflows); -} - -public interface IRequirementResolver -{ - List Resolve(WorkflowStep step); - List Resolve(BuildTarget target); -} diff --git a/Journal.DevTool/Core/Debug/DebugContracts.cs b/Journal.DevTool/Core/Debug/DebugContracts.cs deleted file mode 100644 index c4c57fa..0000000 --- a/Journal.DevTool/Core/Debug/DebugContracts.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Sdt.Config; -using Sdt.Runner; - -namespace Sdt.Core.Debug; - -public sealed record DebugRunResult( - bool Success, - ExecutionStopReason? StopReason, - string Message, - DebugProfileDefinition Profile, - RunResult? RunResult, - IReadOnlyList OutputLines, - IReadOnlyList Probes); - -public sealed record DiagnosticsBundleResult( - bool Success, - string BundleDirectory, - string? ZipPath, - string Message); - -public sealed record DiagnosticsBundleRequest( - string Category, - string ProjectRoot, - string SummaryMessage, - IReadOnlyList OutputLines, - IReadOnlyList WorkflowSteps, - IReadOnlyList Probes, - DebugDiagnosticsOptions DiagnosticsOptions, - DevToolConfig Config, - ExecutionStopReason? StopReason = null, - RunResult? DebugRun = null, - DebugProfileDefinition? DebugProfile = null); - -public interface IDebugProfileRunner -{ - Task RunAsync( - DebugProfileDefinition profile, - DevToolConfig config, - string projectRoot, - bool verbose, - Func> confirmInstallAsync, - Action onOutput, - Action? onEvent = null, - CancellationToken cancellationToken = default); -} - -public interface IDiagnosticsBundleService -{ - Task WriteBundleAsync( - DiagnosticsBundleRequest request, - CancellationToken cancellationToken = default); -} diff --git a/Journal.DevTool/Core/Debug/DebugProfileRunner.cs b/Journal.DevTool/Core/Debug/DebugProfileRunner.cs deleted file mode 100644 index 06e3b0c..0000000 --- a/Journal.DevTool/Core/Debug/DebugProfileRunner.cs +++ /dev/null @@ -1,185 +0,0 @@ -using Sdt.Config; -using Sdt.Runner; - -namespace Sdt.Core.Debug; - -public sealed class DebugProfileRunner( - IToolProbe toolProbe, - IPrereqInstaller installer) : IDebugProfileRunner -{ - private readonly IToolProbe _toolProbe = toolProbe; - private readonly IPrereqInstaller _installer = installer; - - public async Task RunAsync( - DebugProfileDefinition profile, - DevToolConfig config, - string projectRoot, - bool verbose, - Func> confirmInstallAsync, - Action onOutput, - Action? onEvent = null, - CancellationToken cancellationToken = default) - { - var probes = new List(); - var output = new List(); - onEvent?.Invoke(new RunEvent( - Category: "debug", - Type: RunEventType.DebugStarted, - Message: $"Debug profile '{profile.Id}' started.")); - var requires = profile.Requires.Count > 0 - ? profile.Requires - : InferRequirements(profile); - - foreach (var req in requires) - { - var probe = await _toolProbe.ProbeAsync(req.Tool, projectRoot, config, cancellationToken).ConfigureAwait(false); - probes.Add(probe); - if (probe.IsAvailable) - continue; - - if (!string.IsNullOrWhiteSpace(probe.Details)) - { - var line = $"Probe detail [{req.Tool}]: {probe.Details}"; - output.Add("OUT: " + line); - if (verbose) - onOutput(line, false); - } - - if (req.InstallPolicy == InstallPolicy.Never) - { - onEvent?.Invoke(new RunEvent( - Category: "debug", - Type: RunEventType.DebugCompleted, - Message: $"Missing prerequisite '{req.Tool}'.", - Tool: req.Tool, - Success: false)); - return new DebugRunResult( - Success: false, - StopReason: ExecutionStopReason.MissingPrereq, - Message: $"Missing prerequisite '{req.Tool}' for debug profile '{profile.Label}'.", - Profile: profile, - RunResult: null, - OutputLines: output, - Probes: probes); - } - - var installPlan = await _installer.GetInstallPlanAsync(req.Tool, projectRoot, config, cancellationToken).ConfigureAwait(false); - if (!installPlan.Supported || installPlan.Commands.Count == 0) - { - onEvent?.Invoke(new RunEvent( - Category: "debug", - Type: RunEventType.DebugCompleted, - Message: $"No installer plan available for '{req.Tool}'.", - Tool: req.Tool, - Success: false)); - return new DebugRunResult( - Success: false, - StopReason: ExecutionStopReason.MissingPrereq, - Message: $"Missing prerequisite '{req.Tool}' and no installer plan is available.", - Profile: profile, - RunResult: null, - OutputLines: output, - Probes: probes); - } - - var approved = req.InstallPolicy == InstallPolicy.Auto - ? true - : await confirmInstallAsync(req.Tool, installPlan).ConfigureAwait(false); - if (!approved) - { - onEvent?.Invoke(new RunEvent( - Category: "debug", - Type: RunEventType.InstallDeclined, - Message: $"Install declined for '{req.Tool}'.", - Tool: req.Tool, - Success: false)); - return new DebugRunResult( - Success: false, - StopReason: ExecutionStopReason.UserDeclined, - Message: $"Install declined for missing prerequisite '{req.Tool}'.", - Profile: profile, - RunResult: null, - OutputLines: output, - Probes: probes); - } - - foreach (var cmd in installPlan.Commands) - { - var installResult = await _installer.RunInstallAsync(cmd, projectRoot, onOutput, cancellationToken).ConfigureAwait(false); - if (!installResult.Success) - { - onEvent?.Invoke(new RunEvent( - Category: "debug", - Type: RunEventType.DebugCompleted, - Message: $"Install failed for '{req.Tool}'.", - Tool: req.Tool, - Success: false, - ExitCode: installResult.ExitCode)); - return new DebugRunResult( - Success: false, - StopReason: ExecutionStopReason.InstallFailed, - Message: $"Failed to install prerequisite '{req.Tool}'.", - Profile: profile, - RunResult: installResult, - OutputLines: output, - Probes: probes); - } - } - } - - var cwd = Path.GetFullPath(Path.Combine(projectRoot, profile.WorkingDir)); - var mergedEnv = profile.Env.Count > 0 ? profile.Env : null; - onEvent?.Invoke(new RunEvent( - Category: "debug", - Type: RunEventType.DebugCommandStarted, - Message: $"{profile.Command} {string.Join(" ", profile.Args)}")); - var run = await ProcessRunner.RunAsync( - profile.Command, - profile.Args, - cwd, - (line, isErr) => - { - output.Add((isErr ? "ERR: " : "OUT: ") + line); - if (verbose) - onOutput(line, isErr); - }, - mergedEnv, - cancellationToken).ConfigureAwait(false); - onEvent?.Invoke(new RunEvent( - Category: "debug", - Type: RunEventType.DebugCommandCompleted, - Message: $"Debug command exited {run.ExitCode}.", - Success: run.Success, - ExitCode: run.ExitCode)); - onEvent?.Invoke(new RunEvent( - Category: "debug", - Type: RunEventType.DebugCompleted, - Message: run.Success ? "Debug run completed." : "Debug run failed.", - Success: run.Success, - ExitCode: run.ExitCode)); - - return new DebugRunResult( - Success: run.Success, - StopReason: run.Success ? null : ExecutionStopReason.CommandFailed, - Message: run.Success - ? $"Debug profile '{profile.Label}' completed." - : $"Debug profile '{profile.Label}' exited with code {run.ExitCode}.", - Profile: profile, - RunResult: run, - OutputLines: output, - Probes: probes); - } - - private static List InferRequirements(DebugProfileDefinition profile) - { - return profile.Type.ToLowerInvariant() switch - { - "dotnet" => [new ToolRequirement { Tool = "dotnet" }], - "node" => [new ToolRequirement { Tool = "node" }, new ToolRequirement { Tool = "npm" }], - "python" => [new ToolRequirement { Tool = "python" }], - _ => string.IsNullOrWhiteSpace(profile.Command) - ? [] - : [new ToolRequirement { Tool = profile.Command }], - }; - } -} diff --git a/Journal.DevTool/Core/Debug/DiagnosticsBundleService.cs b/Journal.DevTool/Core/Debug/DiagnosticsBundleService.cs deleted file mode 100644 index 0056912..0000000 --- a/Journal.DevTool/Core/Debug/DiagnosticsBundleService.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using Sdt.Config; - -namespace Sdt.Core.Debug; - -public sealed class DiagnosticsBundleService : IDiagnosticsBundleService -{ - private static readonly JsonSerializerOptions JsonOptions = new() - { - WriteIndented = true - }; - - public async Task WriteBundleAsync( - DiagnosticsBundleRequest request, - CancellationToken cancellationToken = default) - { - try - { - var timestamp = DateTimeOffset.Now.ToString("yyyyMMdd-HHmmss"); - var root = Path.GetFullPath(Path.Combine(request.ProjectRoot, request.DiagnosticsOptions.OutputDir)); - var bundleDir = Path.Combine(root, $"{request.Category}-{timestamp}"); - Directory.CreateDirectory(bundleDir); - - var stepsPath = Path.Combine(bundleDir, "steps.json"); - var toolsPath = Path.Combine(bundleDir, "tools.json"); - var envPath = Path.Combine(bundleDir, "env.json"); - var outputPath = Path.Combine(bundleDir, "output.log"); - var summaryPath = Path.Combine(bundleDir, "summary.json"); - - await File.WriteAllTextAsync(stepsPath, JsonSerializer.Serialize(request.WorkflowSteps, JsonOptions), cancellationToken); - await File.WriteAllTextAsync(toolsPath, JsonSerializer.Serialize(request.Probes, JsonOptions), cancellationToken); - await File.WriteAllTextAsync(envPath, JsonSerializer.Serialize(CaptureEnvironment(request.DiagnosticsOptions), JsonOptions), cancellationToken); - await File.WriteAllLinesAsync(outputPath, request.OutputLines, cancellationToken); - - var summary = new - { - category = request.Category, - stopReason = request.StopReason?.ToString(), - message = request.SummaryMessage, - createdAt = DateTimeOffset.Now, - configHash = HashConfig(request.Config), - debugProfile = request.DebugProfile?.Id, - debugExitCode = request.DebugRun?.ExitCode, - envCapture = BuildEnvCaptureSummary(request.DiagnosticsOptions), - }; - await File.WriteAllTextAsync(summaryPath, JsonSerializer.Serialize(summary, JsonOptions), cancellationToken); - - return new DiagnosticsBundleResult( - Success: true, - BundleDirectory: bundleDir, - ZipPath: null, - Message: "Diagnostics bundle generated."); - } - catch (Exception ex) - { - return new DiagnosticsBundleResult( - Success: false, - BundleDirectory: string.Empty, - ZipPath: null, - Message: ex.Message); - } - } - - private static Dictionary CaptureEnvironment(DebugDiagnosticsOptions options) - { - if (options.IncludeAllEnv) - { - return Environment.GetEnvironmentVariables() - .Cast() - .ToDictionary(e => e.Key?.ToString() ?? "", e => e.Value?.ToString() ?? "", StringComparer.OrdinalIgnoreCase); - } - - if (options.CaptureEnvKeys.Count == 0) - return new Dictionary(StringComparer.OrdinalIgnoreCase); - - var captured = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var key in options.CaptureEnvKeys) - captured[key] = Environment.GetEnvironmentVariable(key) ?? ""; - return captured; - } - - private static string BuildEnvCaptureSummary(DebugDiagnosticsOptions options) - { - if (options.IncludeAllEnv) - return "full"; - if (options.CaptureEnvKeys.Count == 0) - return "allowlist-empty (intentional minimal capture)"; - return $"allowlist ({options.CaptureEnvKeys.Count} keys)"; - } - - private static string HashConfig(object config) - { - var json = JsonSerializer.Serialize(config); - var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(json)); - return Convert.ToHexString(bytes); - } -} diff --git a/Journal.DevTool/Core/LegacyScriptRequirementResolver.cs b/Journal.DevTool/Core/LegacyScriptRequirementResolver.cs deleted file mode 100644 index 39057de..0000000 --- a/Journal.DevTool/Core/LegacyScriptRequirementResolver.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Sdt.Config; - -namespace Sdt.Core; - -internal static class LegacyScriptRequirementResolver -{ - public static List InferForPowerShellArgs(IReadOnlyList args) - { - var script = FindScriptArg(args); - if (string.IsNullOrWhiteSpace(script)) - return []; - - static ToolRequirement Req(string tool) => new() { Tool = tool, InstallPolicy = InstallPolicy.Prompt }; - - var file = Path.GetFileName(script).ToLowerInvariant(); - var lowerArgs = args.Select(a => a.ToLowerInvariant()).ToList(); - - return file switch - { - "publish-app.ps1" => IsTauriTarget(lowerArgs) - ? [Req("python"), Req("node"), Req("npm"), Req("cargo")] - : [Req("python"), Req("node"), Req("npm")], - "publish-sidecar.ps1" => [Req("python"), Req("dotnet")], - "publish-webgateway.ps1" => [Req("python"), Req("dotnet"), Req("node"), Req("npm")], - "run-webgateway.ps1" => [Req("python"), Req("dotnet")], - "migration-gate.ps1" => [Req("python"), Req("dotnet")], - "nuget-export-cache.ps1" => [Req("python"), Req("dotnet")], - "nuget-import-cache.ps1" => [Req("python"), Req("dotnet")], - "npm-clean.ps1" => [Req("python"), Req("node"), Req("npm")], - "publish-output.ps1" => [Req("python"), Req("dotnet"), Req("node"), Req("npm"), Req("cargo")], - "sync-output.ps1" => [Req("python")], - "dotnet-min.ps1" => [Req("python"), Req("dotnet")], - "pip-min.ps1" => [Req("python")], - _ => [Req("python")] - }; - } - - private static string? FindScriptArg(IReadOnlyList args) - { - for (var i = 0; i < args.Count - 1; i++) - { - if (string.Equals(args[i], "-File", StringComparison.OrdinalIgnoreCase)) - return args[i + 1]; - } - - return args.FirstOrDefault(a => a.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)); - } - - private static bool IsTauriTarget(IReadOnlyList lowerArgs) - { - for (var i = 0; i < lowerArgs.Count - 1; i++) - { - if (lowerArgs[i] is "-target" or "--target" && lowerArgs[i + 1] == "tauri") - return true; - } - - return false; - } -} diff --git a/Journal.DevTool/Core/PrereqInstallerService.cs b/Journal.DevTool/Core/PrereqInstallerService.cs deleted file mode 100644 index 8f87f0f..0000000 --- a/Journal.DevTool/Core/PrereqInstallerService.cs +++ /dev/null @@ -1,303 +0,0 @@ -using System.Diagnostics; -using System.Text.Json; -using System.Text.RegularExpressions; -using Sdt.Config; -using Sdt.Runner; - -namespace Sdt.Core; - -public sealed class PrereqInstallerService : IPrereqInstaller -{ - public async Task GetInstallPlanAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - { - var fromConfig = TryGetPlanFromConfig(tool, config); - if (fromConfig is not null) - return fromConfig; - - var scriptPath = ScriptLocator.FindHelperScript(projectRoot, "diag.py"); - if (scriptPath is null) - return FallbackPlan(tool); - - try - { - var psi = new ProcessStartInfo - { - FileName = PythonResolver.ResolveExecutable(), - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = projectRoot, - }; - psi.ArgumentList.Add(scriptPath); - psi.ArgumentList.Add("install-plan"); - psi.ArgumentList.Add("--tool"); - psi.ArgumentList.Add(tool); - psi.ArgumentList.Add("--json"); - - using var process = new Process { StartInfo = psi }; - process.Start(); - var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - var stderr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); - - if (process.ExitCode != 0) - { - var fallback = FallbackPlan(tool); - return fallback with - { - Summary = $"diag.py install-plan failed for {tool}; using fallback templates. " + - $"{(string.IsNullOrWhiteSpace(stderr) ? "No stderr output." : stderr.Trim())}" - }; - } - - var parsed = JsonSerializer.Deserialize(stdout); - if (parsed is null) - { - var fallback = FallbackPlan(tool); - return fallback with { Summary = $"diag.py returned invalid JSON for {tool}; using fallback templates." }; - } - - var commands = parsed.Commands - .Select(c => new InstallCommand(c.Command ?? "", c.Args ?? [])) - .Where(c => !string.IsNullOrWhiteSpace(c.Command)) - .ToList(); - if (!parsed.Supported || commands.Count == 0) - { - var fallback = FallbackPlan(tool); - return fallback with { Summary = $"diag.py returned no usable commands for {tool}; using fallback templates." }; - } - - return new InstallPlan( - parsed.Tool ?? tool, - parsed.Supported, - parsed.Summary ?? $"Install {tool}", - commands); - } - catch (Exception ex) - { - var fallback = FallbackPlan(tool); - return fallback with { Summary = $"diag.py install-plan exception for {tool}; using fallback templates. {ex.Message}" }; - } - } - - public Task RunInstallAsync( - InstallCommand command, - string projectRoot, - Action onOutput, - CancellationToken cancellationToken = default) - => ProcessRunner.RunAsync(command.Command, command.Args, projectRoot, onOutput, cancellationToken: cancellationToken); - - private static InstallPlan FallbackPlan(string tool) - { - var isWindows = OperatingSystem.IsWindows(); - var normalized = tool.ToLowerInvariant(); - if (normalized == "tauri") - return BuildTauriFallbackPlan(); - - var installCommand = normalized switch - { - "dotnet" => isWindows - ? new InstallCommand("winget", ["install", "Microsoft.DotNet.SDK.10"]) - : new InstallCommand("sh", ["-c", "echo Install dotnet SDK from your distro package manager"]), - "node" => isWindows - ? new InstallCommand("winget", ["install", "OpenJS.NodeJS.LTS"]) - : new InstallCommand("sh", ["-c", "echo Install nodejs via package manager"]), - "npm" => isWindows - ? new InstallCommand("winget", ["install", "OpenJS.NodeJS.LTS"]) - : new InstallCommand("sh", ["-c", "echo Install npm via package manager"]), - "cargo" => isWindows - ? new InstallCommand("winget", ["install", "Rustlang.Rustup"]) - : new InstallCommand("sh", ["-c", "curl https://sh.rustup.rs -sSf | sh"]), - "git" => isWindows - ? new InstallCommand("winget", ["install", "Git.Git"]) - : new InstallCommand("sh", ["-c", "echo Install git via package manager"]), - "docker" => isWindows - ? new InstallCommand("winget", ["install", "Docker.DockerDesktop"]) - : new InstallCommand("sh", ["-c", "echo Install docker engine via package manager"]), - "python" => isWindows - ? new InstallCommand("winget", ["install", "Python.Python.3.12"]) - : new InstallCommand("sh", ["-c", "echo Install python3 via package manager"]), - _ => new InstallCommand("sh", ["-c", $"echo No installer template for '{tool}'"]), - }; - - return new InstallPlan( - tool, - Supported: true, - Summary: $"Fallback install plan for {tool}", - Commands: [installCommand]); - } - - private static InstallPlan BuildTauriFallbackPlan() - { - if (OperatingSystem.IsWindows()) - { - return new InstallPlan( - "tauri", - Supported: true, - Summary: "Fallback tauri plan (Windows): install Node.js, Rust toolchain, and Tauri CLI. Visual Studio C++ build tools/WebView2 may also be required.", - Commands: - [ - new InstallCommand("winget", ["install", "OpenJS.NodeJS.LTS"]), - new InstallCommand("winget", ["install", "Rustlang.Rustup"]), - new InstallCommand("npm", ["install", "-g", "@tauri-apps/cli"]), - ]); - } - - if (OperatingSystem.IsMacOS()) - { - return new InstallPlan( - "tauri", - Supported: true, - Summary: "Fallback tauri plan (macOS): install Xcode command line tools, Rust toolchain, and Tauri CLI.", - Commands: - [ - new InstallCommand("sh", ["-c", "xcode-select --install || true"]), - new InstallCommand("sh", ["-c", "curl https://sh.rustup.rs -sSf | sh -s -- -y"]), - new InstallCommand("npm", ["install", "-g", "@tauri-apps/cli"]), - ]); - } - - var linuxPlan = BuildLinuxTauriPrereqCommand(); - return new InstallPlan( - "tauri", - Supported: true, - Summary: $"Fallback tauri plan (Linux): detected package manager `{linuxPlan.PackageManager}` for system deps, then install Rust toolchain and Tauri CLI.", - Commands: - [ - new InstallCommand("sh", ["-c", linuxPlan.Command]), - new InstallCommand("sh", ["-c", "curl https://sh.rustup.rs -sSf | sh -s -- -y"]), - new InstallCommand("npm", ["install", "-g", "@tauri-apps/cli"]), - ]); - } - - private static (string PackageManager, string Command) BuildLinuxTauriPrereqCommand() - { - if (CommandExists("apt-get")) - { - return ("apt-get", - "sudo apt-get update && sudo apt-get install -y build-essential libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev"); - } - - if (CommandExists("dnf")) - { - return ("dnf", - "sudo dnf install -y gcc gcc-c++ make webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3 librsvg2-devel"); - } - - if (CommandExists("pacman")) - { - return ("pacman", - "sudo pacman -S --needed base-devel webkit2gtk gtk3 libappindicator-gtk3 librsvg"); - } - - if (CommandExists("zypper")) - { - return ("zypper", - "sudo zypper install -y gcc gcc-c++ make webkit2gtk3-devel gtk3-devel libappindicator3-devel librsvg-devel"); - } - - if (CommandExists("apk")) - { - return ("apk", - "sudo apk add build-base webkit2gtk-dev gtk+3.0-dev libayatana-appindicator-dev librsvg-dev"); - } - - return ("unknown", - "echo Install tauri system dependencies using your distro package manager, then rerun SDT."); - } - - private static bool CommandExists(string command) - { - try - { - var psi = new ProcessStartInfo - { - FileName = OperatingSystem.IsWindows() ? "where" : "which", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - psi.ArgumentList.Add(command); - using var process = Process.Start(psi); - if (process is null) - return false; - process.WaitForExit(1500); - return process.ExitCode == 0; - } - catch - { - return false; - } - } - - private static InstallPlan? TryGetPlanFromConfig(string tool, DevToolConfig? config) - { - var preferred = config?.Tooling?.Tools - .FirstOrDefault(t => string.Equals(t.Tool, tool, StringComparison.OrdinalIgnoreCase)) - ?.PreferredInstallCommands; - - if (preferred is null || preferred.Count == 0) - return null; - - var commands = new List(); - foreach (var line in preferred) - { - var parts = SplitShellLike(line); - if (parts.Count == 0) - continue; - - commands.Add(new InstallCommand(parts[0], parts.Skip(1).ToList())); - } - - if (commands.Count == 0) - return null; - - return new InstallPlan( - tool, - Supported: true, - Summary: $"Configured install commands for {tool}", - Commands: commands); - } - - private static List SplitShellLike(string input) - { - if (string.IsNullOrWhiteSpace(input)) - return []; - - var tokens = new List(); - var matches = Regex.Matches(input, "\"([^\"]*)\"|'([^']*)'|\\S+"); - foreach (Match match in matches) - { - var value = match.Value.Trim(); - if (value.Length >= 2 && ( - (value.StartsWith("\"", StringComparison.Ordinal) && value.EndsWith("\"", StringComparison.Ordinal)) || - (value.StartsWith("'", StringComparison.Ordinal) && value.EndsWith("'", StringComparison.Ordinal)))) - { - value = value[1..^1]; - } - if (!string.IsNullOrWhiteSpace(value)) - tokens.Add(value); - } - return tokens; - } - - private sealed class InstallPlanJson - { - public string? Tool { get; init; } - public bool Supported { get; init; } - public string? Summary { get; init; } - public List Commands { get; init; } = []; - } - - private sealed class InstallCommandJson - { - public string? Command { get; init; } - public List? Args { get; init; } - } -} diff --git a/Journal.DevTool/Core/PythonResolver.cs b/Journal.DevTool/Core/PythonResolver.cs deleted file mode 100644 index 928c79a..0000000 --- a/Journal.DevTool/Core/PythonResolver.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Diagnostics; - -namespace Sdt.Core; - -internal static class PythonResolver -{ - public static string ResolveExecutable() - { - var candidates = OperatingSystem.IsWindows() - ? new[] { "python", "py" } - : new[] { "python3", "python" }; - - foreach (var candidate in candidates) - { - if (CanRun(candidate)) - return candidate; - } - - return "python"; - } - - private static bool CanRun(string exe) - { - try - { - var psi = new ProcessStartInfo - { - FileName = exe, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - psi.ArgumentList.Add("--version"); - - using var p = new Process { StartInfo = psi }; - p.Start(); - p.WaitForExit(2000); - return p.ExitCode == 0; - } - catch - { - return false; - } - } -} diff --git a/Journal.DevTool/Core/RequirementResolver.cs b/Journal.DevTool/Core/RequirementResolver.cs deleted file mode 100644 index 169d070..0000000 --- a/Journal.DevTool/Core/RequirementResolver.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Sdt.Config; - -namespace Sdt.Core; - -public sealed class RequirementResolver : IRequirementResolver -{ - public List Resolve(WorkflowStep step) - { - if (step.Requires.Count > 0) - return step.Requires.ToList(); - - if (!string.IsNullOrWhiteSpace(step.Action)) - return InferActionRequirements(step.Action); - - if (string.IsNullOrWhiteSpace(step.Command)) - return []; - - return InferCommandRequirements(step.Command, step.Args); - } - - public List Resolve(BuildTarget target) - { - if (string.IsNullOrWhiteSpace(target.Command)) - return []; - - return InferCommandRequirements(target.Command, target.Args); - } - - private static List InferActionRequirements(string action) - { - return action.ToLowerInvariant() switch - { - "dotnet-restore" or "dotnet-build" or "dotnet-test" or "dotnet-publish" => [Req("dotnet")], - "npm-install" or "npm-ci" or "npm-build" or "npm-test" or "npm-audit" => [Req("node"), Req("npm")], - "python-venv-create" or "python-pip-install" or "python-pip-sync" or "python-pytest" => [Req("python")], - "cargo-build" or "cargo-test" => [Req("cargo")], - "tauri-build" => [Req("cargo"), Req("node"), Req("npm")], - "git-status" or "git-fetch" or "git-pull" or "git-clean" => [Req("git")], - "docker-build" or "docker-compose-up" or "docker-compose-down" => [Req("docker")], - _ => [], - }; - } - - private static List InferCommandRequirements(string command, IReadOnlyList args) - { - return command.ToLowerInvariant() switch - { - "dotnet" => [Req("dotnet")], - "npm" => [Req("node"), Req("npm")], - "pnpm" => [Req("node"), Req("pnpm")], - "yarn" => [Req("node"), Req("yarn")], - "python" or "py" => [Req("python")], - "cargo" => [Req("cargo")], - "tauri" => [Req("cargo"), Req("node"), Req("npm")], - "git" => [Req("git")], - "docker" => [Req("docker")], - "pwsh" or "powershell" => LegacyScriptRequirementResolver.InferForPowerShellArgs(args), - _ => [], - }; - } - - private static ToolRequirement Req(string tool) => new() - { - Tool = tool, - InstallPolicy = InstallPolicy.Prompt, - }; -} diff --git a/Journal.DevTool/Core/RunEventJsonlRecorder.cs b/Journal.DevTool/Core/RunEventJsonlRecorder.cs deleted file mode 100644 index 526255e..0000000 --- a/Journal.DevTool/Core/RunEventJsonlRecorder.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Sdt.Core; - -public sealed class RunEventJsonlRecorder : IDisposable -{ - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = false - }; - - static RunEventJsonlRecorder() - { - JsonOptions.Converters.Add(new JsonStringEnumConverter()); - } - - private readonly StreamWriter _writer; - private readonly object _gate = new(); - private bool _disposed; - - public string FilePath { get; } - - private RunEventJsonlRecorder(string filePath, StreamWriter writer) - { - FilePath = filePath; - _writer = writer; - } - - public static RunEventJsonlRecorder Create(string projectRoot, string category) - { - var root = Path.Combine(projectRoot, ".sdt", "events"); - Directory.CreateDirectory(root); - var fileName = $"{category}-{DateTimeOffset.Now:yyyyMMdd-HHmmss}.jsonl"; - var path = Path.Combine(root, fileName); - var writer = new StreamWriter(new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - AutoFlush = true - }; - return new RunEventJsonlRecorder(path, writer); - } - - public void Write(RunEvent evt) - { - lock (_gate) - { - if (_disposed) - return; - var line = JsonSerializer.Serialize(evt, JsonOptions); - _writer.WriteLine(line); - } - } - - public void Dispose() - { - lock (_gate) - { - if (_disposed) - return; - _disposed = true; - _writer.Dispose(); - } - } -} diff --git a/Journal.DevTool/Core/RunEventLogReader.cs b/Journal.DevTool/Core/RunEventLogReader.cs deleted file mode 100644 index f2c223a..0000000 --- a/Journal.DevTool/Core/RunEventLogReader.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Text.Json; - -namespace Sdt.Core; - -public sealed record RunEventLogFile( - string Path, - string Name, - DateTimeOffset LastWriteTime, - long SizeBytes); - -public sealed class RunEventLogReader -{ - public IReadOnlyList ListEventFiles(string projectRoot) - { - var eventsRoot = Path.Combine(projectRoot, ".sdt", "events"); - if (!Directory.Exists(eventsRoot)) - return []; - - return Directory.EnumerateFiles(eventsRoot, "*.jsonl", SearchOption.TopDirectoryOnly) - .Select(path => - { - var info = new FileInfo(path); - return new RunEventLogFile( - Path: path, - Name: info.Name, - LastWriteTime: info.LastWriteTime, - SizeBytes: info.Length); - }) - .OrderByDescending(f => f.LastWriteTime) - .ToList(); - } - - public IReadOnlyList ReadEvents(string filePath) - { - var results = new List(); - if (!File.Exists(filePath)) - return results; - - foreach (var line in File.ReadLines(filePath)) - { - if (string.IsNullOrWhiteSpace(line)) - continue; - - if (TryParseLine(line, out var evt)) - results.Add(evt!); - } - - return results; - } - - internal static bool TryParseLine(string jsonLine, out RunEvent? evt) - { - evt = null; - try - { - using var doc = JsonDocument.Parse(jsonLine); - var root = doc.RootElement; - - var category = root.TryGetProperty("category", out var c) ? c.GetString() : null; - var typeRaw = root.TryGetProperty("type", out var t) ? t.GetString() : null; - var message = root.TryGetProperty("message", out var m) ? m.GetString() : null; - var workflowId = root.TryGetProperty("workflowId", out var wf) ? wf.GetString() : null; - var stepId = root.TryGetProperty("stepId", out var st) ? st.GetString() : null; - var tool = root.TryGetProperty("tool", out var tl) ? tl.GetString() : null; - var success = root.TryGetProperty("success", out var s) && s.ValueKind != JsonValueKind.Null ? s.GetBoolean() : (bool?)null; - var exitCode = root.TryGetProperty("exitCode", out var ec) && ec.ValueKind != JsonValueKind.Null ? ec.GetInt32() : (int?)null; - DateTimeOffset? occurred = null; - if (root.TryGetProperty("occurredAt", out var ts) && ts.ValueKind == JsonValueKind.String && - DateTimeOffset.TryParse(ts.GetString(), out var parsed)) - { - occurred = parsed; - } - - if (string.IsNullOrWhiteSpace(category) || - string.IsNullOrWhiteSpace(typeRaw) || - string.IsNullOrWhiteSpace(message) || - !Enum.TryParse(typeRaw, ignoreCase: true, out var type)) - { - return false; - } - - evt = new RunEvent( - Category: category!, - Type: type, - Message: message!, - WorkflowId: workflowId, - StepId: stepId, - Tool: tool, - Success: success, - ExitCode: exitCode, - Timestamp: occurred); - return true; - } - catch - { - return false; - } - } -} diff --git a/Journal.DevTool/Core/RunEvents.cs b/Journal.DevTool/Core/RunEvents.cs deleted file mode 100644 index 582b52b..0000000 --- a/Journal.DevTool/Core/RunEvents.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Sdt.Core; - -public enum RunEventType -{ - WorkflowStarted, - WorkflowPlanned, - WorkflowStepStarted, - WorkflowStepCompleted, - ProbeChecked, - ProbeFailed, - InstallPlanPrepared, - InstallDeclined, - InstallCommandStarted, - InstallCommandCompleted, - WorkflowCompleted, - DebugStarted, - DebugCommandStarted, - DebugCommandCompleted, - DebugCompleted, -} - -public sealed record RunEvent( - string Category, - RunEventType Type, - string Message, - string? WorkflowId = null, - string? StepId = null, - string? Tool = null, - bool? Success = null, - int? ExitCode = null, - DateTimeOffset? Timestamp = null) -{ - public DateTimeOffset OccurredAt { get; init; } = Timestamp ?? DateTimeOffset.Now; -} diff --git a/Journal.DevTool/Core/ScriptLocator.cs b/Journal.DevTool/Core/ScriptLocator.cs deleted file mode 100644 index 953da1e..0000000 --- a/Journal.DevTool/Core/ScriptLocator.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Sdt.Core; - -internal static class ScriptLocator -{ - public static string? FindHelperScript(string projectRoot, string scriptFileName) - { - // Packaged location: alongside executable in ./scripts - var bundled = Path.Combine(AppContext.BaseDirectory, "scripts", scriptFileName); - if (File.Exists(bundled)) - return bundled; - - // Source/project location fallback - var project = Path.Combine(projectRoot, "scripts", scriptFileName); - if (File.Exists(project)) - return project; - - return null; - } -} diff --git a/Journal.DevTool/Core/ToolProbeService.cs b/Journal.DevTool/Core/ToolProbeService.cs deleted file mode 100644 index d2e6b98..0000000 --- a/Journal.DevTool/Core/ToolProbeService.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Diagnostics; -using System.Text.Json; -using Sdt.Config; - -namespace Sdt.Core; - -public sealed class ToolProbeService : IToolProbe -{ - public async Task ProbeAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - { - var direct = await ProbeDirectAsync(tool, config, cancellationToken).ConfigureAwait(false); - if (direct.IsAvailable) - return direct; - - var scriptPath = ScriptLocator.FindHelperScript(projectRoot, "diag.py"); - if (scriptPath is null) - return direct; - - if (!(await ProbeDirectAsync("python", config, cancellationToken).ConfigureAwait(false)).IsAvailable) - return direct; - - try - { - var psi = new ProcessStartInfo - { - FileName = PythonResolver.ResolveExecutable(), - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = projectRoot, - }; - psi.ArgumentList.Add(scriptPath); - psi.ArgumentList.Add("probe"); - psi.ArgumentList.Add("--tool"); - psi.ArgumentList.Add(tool); - psi.ArgumentList.Add("--json"); - - using var process = new Process { StartInfo = psi }; - process.Start(); - var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - var stderr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); - if (process.ExitCode != 0) - return new ProbeResult(tool, false, Details: stderr.Trim()); - - var parsed = JsonSerializer.Deserialize(stdout); - if (parsed is null) - return new ProbeResult(tool, false, Details: "diag.py returned invalid JSON"); - - return new ProbeResult(parsed.Tool ?? tool, parsed.Available, parsed.Version, parsed.Details); - } - catch (Exception ex) - { - return new ProbeResult(tool, false, Details: ex.Message); - } - } - - private static async Task ProbeDirectAsync(string tool, DevToolConfig? config, CancellationToken cancellationToken) - { - var command = tool.ToLowerInvariant() switch - { - "python" => PythonResolver.ResolveExecutable(), - "dotnet" => "dotnet", - "node" => "node", - "npm" => "npm", - "cargo" => "cargo", - "tauri" => "tauri", - "git" => "git", - "docker" => "docker", - _ => tool, - }; - var resolution = CommandResolver.ResolveWithTrace(command, config, tool); - command = resolution.Resolved; - - var versionArg = command is "python" ? "--version" : "--version"; - try - { - var psi = new ProcessStartInfo - { - FileName = command, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - psi.ArgumentList.Add(versionArg); - - using var process = new Process { StartInfo = psi }; - process.Start(); - var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - var stderr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); - - if (process.ExitCode != 0) - { - var failDetails = string.IsNullOrWhiteSpace(stderr) ? stdout.Trim() : stderr.Trim(); - var trace = $"{resolution.Source}: {resolution.Resolved}"; - return new ProbeResult(tool, false, Details: string.IsNullOrWhiteSpace(failDetails) ? trace : $"{trace} | {failDetails}"); - } - - var version = string.IsNullOrWhiteSpace(stdout) ? stderr.Trim() : stdout.Trim(); - return new ProbeResult(tool, true, Version: version, Details: $"{resolution.Source}: {resolution.Resolved}"); - } - catch (Exception ex) - { - return new ProbeResult(tool, false, Details: $"{resolution.Source}: {resolution.Resolved} | {ex.Message}"); - } - } - - private sealed class DiagProbeJson - { - public string? Tool { get; init; } - public bool Available { get; init; } - public string? Version { get; init; } - public string? Details { get; init; } - } -} diff --git a/Journal.DevTool/Core/WorkflowExecutor.cs b/Journal.DevTool/Core/WorkflowExecutor.cs deleted file mode 100644 index 55c08e6..0000000 --- a/Journal.DevTool/Core/WorkflowExecutor.cs +++ /dev/null @@ -1,273 +0,0 @@ -using Sdt.Config; - -namespace Sdt.Core; - -public sealed class WorkflowExecutor( - IWorkflowPlanner planner, - IToolProbe toolProbe, - IPrereqInstaller installer, - IActionRunner actionRunner, - IRequirementResolver requirementResolver) -{ - private readonly IWorkflowPlanner _planner = planner; - private readonly IToolProbe _toolProbe = toolProbe; - private readonly IPrereqInstaller _installer = installer; - private readonly IActionRunner _actionRunner = actionRunner; - private readonly IRequirementResolver _requirementResolver = requirementResolver; - - public async Task ExecuteAsync( - WorkflowDefinition rootWorkflow, - IReadOnlyDictionary allWorkflows, - DevToolConfig config, - string projectRoot, - Func> confirmInstallAsync, - Action onOutput, - Action? onEvent = null, - CancellationToken cancellationToken = default) - { - var results = new List(); - var plan = _planner.ResolvePlan(rootWorkflow, allWorkflows); - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowStarted, - Message: $"Workflow '{rootWorkflow.Id}' started.", - WorkflowId: rootWorkflow.Id)); - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowPlanned, - Message: $"Execution plan contains {plan.Count} workflow(s).", - WorkflowId: rootWorkflow.Id)); - - if (plan.Count == 0) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowCompleted, - Message: "No executable workflow steps were found.", - WorkflowId: rootWorkflow.Id, - Success: false)); - return new WorkflowExecutionResult( - Success: false, - StopReason: ExecutionStopReason.ValidationFailed, - Message: "This workflow has no executable steps.", - Steps: results); - } - - foreach (var workflow in plan) - { - foreach (var step in workflow.Steps) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowStepStarted, - Message: $"Step '{step.Label}' started.", - WorkflowId: workflow.Id, - StepId: step.Id)); - - var requires = _requirementResolver.Resolve(step); - foreach (var req in requires) - { - var probe = await _toolProbe.ProbeAsync(req.Tool, projectRoot, config, cancellationToken).ConfigureAwait(false); - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.ProbeChecked, - Message: probe.IsAvailable - ? $"Tool '{req.Tool}' is available." - : $"Tool '{req.Tool}' is missing.", - WorkflowId: workflow.Id, - StepId: step.Id, - Tool: req.Tool, - Success: probe.IsAvailable)); - if (probe.IsAvailable) - continue; - - if (!string.IsNullOrWhiteSpace(probe.Details)) - { - onOutput($"Probe detail [{req.Tool}]: {probe.Details}", false); - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.ProbeFailed, - Message: probe.Details, - WorkflowId: workflow.Id, - StepId: step.Id, - Tool: req.Tool, - Success: false)); - } - - if (req.InstallPolicy == InstallPolicy.Never) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowCompleted, - Message: $"Missing prerequisite '{req.Tool}'.", - WorkflowId: rootWorkflow.Id, - Tool: req.Tool, - Success: false)); - return new WorkflowExecutionResult( - Success: false, - StopReason: ExecutionStopReason.MissingPrereq, - Message: $"Missing prerequisite '{req.Tool}' for step '{step.Label}'.", - Steps: results); - } - - var installPlan = await _installer.GetInstallPlanAsync( - req.Tool, - projectRoot, - config, - cancellationToken).ConfigureAwait(false); - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.InstallPlanPrepared, - Message: installPlan.Summary, - WorkflowId: workflow.Id, - StepId: step.Id, - Tool: req.Tool, - Success: installPlan.Supported)); - if (!installPlan.Supported || installPlan.Commands.Count == 0) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowCompleted, - Message: $"No installer plan available for '{req.Tool}'.", - WorkflowId: rootWorkflow.Id, - Tool: req.Tool, - Success: false)); - return new WorkflowExecutionResult( - Success: false, - StopReason: ExecutionStopReason.MissingPrereq, - Message: $"Missing prerequisite '{req.Tool}' and no installer plan is available.", - Steps: results); - } - - var approved = req.InstallPolicy == InstallPolicy.Auto - ? true - : await confirmInstallAsync(req.Tool, installPlan).ConfigureAwait(false); - - if (!approved) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.InstallDeclined, - Message: $"Install declined for '{req.Tool}'.", - WorkflowId: workflow.Id, - StepId: step.Id, - Tool: req.Tool, - Success: false)); - return new WorkflowExecutionResult( - Success: false, - StopReason: ExecutionStopReason.UserDeclined, - Message: $"Install declined for missing prerequisite '{req.Tool}'.", - Steps: results); - } - - foreach (var installCommand in installPlan.Commands) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.InstallCommandStarted, - Message: $"{installCommand.Command} {string.Join(" ", installCommand.Args)}", - WorkflowId: workflow.Id, - StepId: step.Id, - Tool: req.Tool)); - - var installResult = await _installer.RunInstallAsync( - installCommand, - projectRoot, - onOutput, - cancellationToken).ConfigureAwait(false); - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.InstallCommandCompleted, - Message: $"Install command exited {installResult.ExitCode}.", - WorkflowId: workflow.Id, - StepId: step.Id, - Tool: req.Tool, - Success: installResult.Success, - ExitCode: installResult.ExitCode)); - - if (!installResult.Success) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowCompleted, - Message: $"Install failed for '{req.Tool}'.", - WorkflowId: rootWorkflow.Id, - Tool: req.Tool, - Success: false)); - return new WorkflowExecutionResult( - Success: false, - StopReason: ExecutionStopReason.InstallFailed, - Message: $"Failed to install prerequisite '{req.Tool}'.", - Steps: results); - } - } - - var verifyProbe = await _toolProbe.ProbeAsync(req.Tool, projectRoot, config, cancellationToken).ConfigureAwait(false); - if (!verifyProbe.IsAvailable) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowCompleted, - Message: $"Tool '{req.Tool}' still missing after install.", - WorkflowId: rootWorkflow.Id, - Tool: req.Tool, - Success: false)); - return new WorkflowExecutionResult( - Success: false, - StopReason: ExecutionStopReason.InstallFailed, - Message: $"Prerequisite '{req.Tool}' is still missing after install attempt.", - Steps: results); - } - } - - var runResult = await _actionRunner.RunStepAsync( - step, - projectRoot, - onOutput, - cancellationToken).ConfigureAwait(false); - - results.Add(new WorkflowStepResult( - workflow.Id, - step.Id, - string.IsNullOrWhiteSpace(step.Label) ? step.Id : step.Label, - runResult)); - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowStepCompleted, - Message: $"Step '{step.Label}' exited {runResult.ExitCode}.", - WorkflowId: workflow.Id, - StepId: step.Id, - Success: runResult.Success, - ExitCode: runResult.ExitCode)); - - if (!runResult.Success) - { - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowCompleted, - Message: $"Step '{step.Label}' failed.", - WorkflowId: rootWorkflow.Id, - Success: false, - ExitCode: runResult.ExitCode)); - return new WorkflowExecutionResult( - Success: false, - StopReason: ExecutionStopReason.CommandFailed, - Message: $"Step '{step.Label}' failed with exit code {runResult.ExitCode}.", - Steps: results); - } - } - } - - onEvent?.Invoke(new RunEvent( - Category: "workflow", - Type: RunEventType.WorkflowCompleted, - Message: "Workflow completed successfully.", - WorkflowId: rootWorkflow.Id, - Success: true)); - return new WorkflowExecutionResult( - Success: true, - StopReason: null, - Message: "Workflow completed successfully.", - Steps: results); - } -} diff --git a/Journal.DevTool/Core/WorkflowPlanner.cs b/Journal.DevTool/Core/WorkflowPlanner.cs deleted file mode 100644 index d677df6..0000000 --- a/Journal.DevTool/Core/WorkflowPlanner.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Sdt.Config; - -namespace Sdt.Core; - -public sealed class WorkflowPlanner : IWorkflowPlanner -{ - public List ResolvePlan( - WorkflowDefinition workflow, - IReadOnlyDictionary allWorkflows) - { - var visited = new HashSet(StringComparer.OrdinalIgnoreCase); - var plan = new List(); - Visit(workflow, allWorkflows, visited, plan); - return plan; - } - - private static void Visit( - WorkflowDefinition workflow, - IReadOnlyDictionary allWorkflows, - HashSet visited, - List plan) - { - if (!visited.Add(workflow.Id)) - return; - - foreach (var depId in workflow.DependsOn) - { - if (allWorkflows.TryGetValue(depId, out var dep)) - Visit(dep, allWorkflows, visited, plan); - } - - if (workflow.Steps.Count > 0) - plan.Add(workflow); - } -} diff --git a/Journal.DevTool/DevTool.csproj b/Journal.DevTool/DevTool.csproj deleted file mode 100644 index 9bd6978..0000000 --- a/Journal.DevTool/DevTool.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - Exe - net10.0 - enable - enable - sdt - Sdt - false - - - - - - - - - - - - - - scripts\%(Filename)%(Extension) - scripts\%(Filename)%(Extension) - PreserveNewest - PreserveNewest - - - scripts\%(Filename)%(Extension) - scripts\%(Filename)%(Extension) - PreserveNewest - PreserveNewest - - - - diff --git a/Journal.DevTool/Journal.DevTool.csproj b/Journal.DevTool/Journal.DevTool.csproj deleted file mode 100644 index 5d8f248..0000000 --- a/Journal.DevTool/Journal.DevTool.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - Exe - net10.0 - enable - enable - sdt - Sdt - false - - - - - - - diff --git a/Journal.DevTool/Program.cs b/Journal.DevTool/Program.cs deleted file mode 100644 index a6b938a..0000000 --- a/Journal.DevTool/Program.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Sdt.Config; -using Sdt.Tui; -using Spectre.Console; - -try -{ - // ── Workspace + project discovery ──────────────────────────────────────── - - var workspaceResult = WorkspaceLoader.FindAndLoad(); - var projectResult = ConfigLoader.FindAndLoad(); - var cliArgs = Environment.GetCommandLineArgs().Skip(1).ToArray(); - var forceInit = cliArgs.Any(a => string.Equals(a, "init", StringComparison.OrdinalIgnoreCase) || - string.Equals(a, "--init", StringComparison.OrdinalIgnoreCase)); - - if (forceInit) - { - var scan = ConfigBootstrapper.Scan(Directory.GetCurrentDirectory()); - var generated = ConfigBootstrapper.BuildDefaultConfig(scan); - var path = ConfigBootstrapper.WriteDefaultConfig(scan.ProjectRoot, generated, overwrite: false); - AnsiConsole.MarkupLine(Theme.Ok($"Initialized config at {path}")); - projectResult = ConfigLoader.FindAndLoad(scan.ProjectRoot); - } - - if (projectResult is null) - { - AnsiConsole.MarkupLine($"[bold {Theme.Red}]SDT:[/] [{Theme.Amber}]No devtool.json found[/] in current directory or any parent."); - var bootstrap = AnsiConsole.Confirm( - $"[{Theme.Amber}]Generate a default devtool.json for this project now?[/]", - defaultValue: true); - if (!bootstrap) - { - AnsiConsole.MarkupLine(Theme.Faint("Create a devtool.json in your project root to get started.")); - return 1; - } - - var scan = ConfigBootstrapper.Scan(Directory.GetCurrentDirectory()); - AnsiConsole.MarkupLine(Theme.Faint($"Detected project root: {scan.ProjectRoot}")); - if (scan.ToolFamilies.Count > 0) - AnsiConsole.MarkupLine(Theme.Faint($"Detected tool families: {string.Join(", ", scan.ToolFamilies)}")); - - var generated = ConfigBootstrapper.BuildDefaultConfig(scan); - var preview = ConfigBootstrapper.ToJson(generated); - AnsiConsole.Write(new Panel(Markup.Escape(preview)).Header("Generated devtool.json preview").BorderStyle(Theme.DimStyle)); - - var confirmWrite = AnsiConsole.Confirm( - $"[{Theme.Amber}]Write generated devtool.json to {scan.ProjectRoot}?[/]", - defaultValue: true); - if (!confirmWrite) - return 1; - - var path = ConfigBootstrapper.WriteDefaultConfig(scan.ProjectRoot, generated, overwrite: false); - AnsiConsole.MarkupLine(Theme.Ok($"Created {path}")); - projectResult = ConfigLoader.FindAndLoad(scan.ProjectRoot); - if (projectResult is null) - { - AnsiConsole.MarkupLine(Theme.Fail("Generated config could not be reloaded.")); - return 1; - } - } - - // ── Main run loop (handles workspace project switching) ──────────────── - - var currentLoaded = projectResult; - var (workspace, workspaceRoot) = workspaceResult.HasValue - ? (workspaceResult.Value.Config, workspaceResult.Value.WorkspaceRoot) - : ((WorkspaceConfig?)null, (string?)null); - - while (true) - { - var app = new App(currentLoaded.Config, currentLoaded.ProjectRoot, currentLoaded.Warnings, workspace, workspaceRoot); - var result = await app.RunAsync(); - - if (result.Reason == AppExitReason.Quit) - break; - - // User switched projects — reload config from new root - if (result.Reason == AppExitReason.SwitchProject && result.NewProjectRoot is not null) - { - LoadedProjectConfig? loaded; - try - { - loaded = ConfigLoader.FindAndLoad(result.NewProjectRoot); - } - catch (Exception ex) - { - AnsiConsole.MarkupLine(Theme.Fail(ex.Message)); - AnsiConsole.MarkupLine(Theme.Faint("Press any key to stay on current project...")); - Console.ReadKey(intercept: true); - continue; - } - if (loaded is null) - { - AnsiConsole.MarkupLine(Theme.Fail($"No devtool.json found at: {result.NewProjectRoot}")); - AnsiConsole.MarkupLine(Theme.Faint("Press any key to stay on current project...")); - Console.ReadKey(intercept: true); - continue; // go back to current app - } - - currentLoaded = loaded; - } - } - - return 0; -} -catch (Exception ex) -{ - var message = ex.Message; - var isExpectedMigrationError = - ex is InvalidOperationException && - message.Contains("Legacy targets-only config detected", StringComparison.OrdinalIgnoreCase); - - AnsiConsole.MarkupLine(Theme.Fail($"Fatal: {message}")); - if (isExpectedMigrationError) - { - var configPath = ConfigLoader.FindConfigPath(); - if (!string.IsNullOrWhiteSpace(configPath)) - { - var migrate = AnsiConsole.Confirm( - $"[{Theme.Amber}]Apply automatic migration now (creates backup + converts targets -> workflows)?[/]", - defaultValue: true); - if (migrate) - { - var result = ConfigLoader.ApplyLegacyTargetMigration(configPath, createBackup: true); - if (result.Success) - { - AnsiConsole.MarkupLine(Theme.Ok("Migration applied successfully.")); - if (!string.IsNullOrWhiteSpace(result.BackupPath)) - AnsiConsole.MarkupLine(Theme.Faint($"Backup: {result.BackupPath}")); - AnsiConsole.MarkupLine(Theme.Faint("Run sdt.exe again in strict mode.")); - } - else - { - AnsiConsole.MarkupLine(Theme.Fail($"Migration failed: {result.Message}")); - } - } - } - } - - if (!isExpectedMigrationError) - AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything); - - return 1; -} diff --git a/Journal.DevTool/README.md b/Journal.DevTool/README.md deleted file mode 100644 index f36acd4..0000000 --- a/Journal.DevTool/README.md +++ /dev/null @@ -1,200 +0,0 @@ -# SDT (Stan's Dev Tools) - -Cross-platform terminal orchestrator for project workflows, toolchain checks, and prerequisite gating. - -## Current State - -- Standalone `.NET` TUI app (`net10.0`) -- Workflow-first config model in `devtool.json` -- Strict-by-default legacy migration (`targets`-only configs fail unless compat mode is enabled) -- Python-first diagnostics/build script layer under `scripts/` -- Fail-fast execution with install prompt gating for missing prerequisites -- Debug profiles with attach metadata and diagnostics bundle generation -- Workspace-first project switching with support for external project paths -- Workspace-level defaults layering via `sdt-defaults.json` (ancestor defaults merged, project config wins) -- Project status tracking is maintained in `ROADMAP.md` -- Core run-event stream (`RunEvent`) shared by workflow + debug execution (TUI consumes it; GUI-ready) -- Run events are persisted to JSONL at `.sdt/events/` for external tooling/GUI consumers -- TUI includes `SYSTEM -> View run events` to inspect persisted JSONL event logs -- `SYSTEM -> Run config doctor` can apply common autofixes (missing working dirs, legacy migration) - -## Run - -```powershell -dotnet run --project DevTool.csproj -``` - -Run from any subdirectory inside a project; SDT walks up to find `devtool.json`. - -If `devtool.json` is missing, SDT now offers to scan the repo and generate a default config. - -Explicit bootstrap command: - -```powershell -dotnet run --project DevTool.csproj -- init -``` - -Bootstrap detects common stacks (`dotnet`, `npm/node`, `python`, `cargo/tauri`, `git`, `docker`) and generates: - -- default workflows -- toolchain/tooling defaults -- debug profiles + diagnostics defaults - -## Config Model - -SDT supports both: - -- `workflows` (preferred) -- `targets` (legacy; compat mode only) - -### Legacy Migration Mode (v1.2) - -- Default: strict mode -- Behavior: `targets`-only config fails early with migration instructions -- Preview file: SDT writes `devtool.generated.workflows.json` for migration help -- Temporary rollback: set `SDT_LEGACY_MODE=compat` - -Permanent fix (recommended): - -1. Open `devtool.generated.workflows.json` -2. Copy its `workflows` into `devtool.json` -3. Remove or empty legacy `targets` -4. Run `sdt.exe` again in strict mode - -### Workflow shape (preferred) - -```json -{ - "id": "build", - "label": "Build", - "description": "Build project", - "group": "Build", - "dependsOn": [], - "steps": [ - { - "id": "dotnet-build", - "label": "dotnet build", - "action": "dotnet-build", - "actionArgs": [], - "workingDir": ".", - "requires": [ - { "tool": "dotnet", "installPolicy": "Prompt" } - ] - } - ] -} -``` - -### Extra sections - -- `tooling.tools[].preferredInstallCommands`: preferred install commands per tool -- `tooling.tools[].executables`: explicit executable candidates for non-standard PATH setups -- `project.rootHints`: files/folders that identify project root -- `env`: session-level environment variable editor values -- `debug.profiles[]`: run/attach debug profiles -- `debug.diagnostics`: diagnostics bundle policy (`.sdt/debug` by default) - - secure default: allowlist-only environment capture - - set `includeAllEnv=true` to opt into full environment capture - -### Workspace Defaults Layering - -If SDT finds `sdt-defaults.json` in the project directory tree (current project root or an ancestor), it merges it into the effective config before runtime: - -- base layer: `sdt-defaults.json` -- override layer: project `devtool.json` (project values win) - -Merge behavior: - -- objects merge recursively -- arrays/scalars are replaced when project provides the property - -This is useful for shared defaults like toolchains, diagnostics policies, and baseline env definitions across multiple projects in one workspace. - -## Execution Behavior - -For each workflow step: - -1. Resolve dependencies (topological order) -2. Probe required tools -3. If missing, show install commands and prompt (`Prompt` policy) -4. On decline/install failure/step failure, stop immediately -5. Render step summary table with exit code + elapsed time -6. On workflow/debug failure, generate diagnostics bundle when enabled - -Installer command precedence: - -1. `tooling.tools[].preferredInstallCommands` -2. `scripts/diag.py install-plan` -3. built-in C# fallback templates (used automatically if script planning fails) - -When a tool probe fails, SDT now prints probe diagnostics (including command resolution source/path) in run output before prompting for installs. - -## Scripts - -See [scripts/README.md](/e:/stansshit/csharp/DevTool-master/scripts/README.md). - -Primary Python entrypoints: - -- `scripts/diag.py` -- `scripts/build.py` -- `scripts/dotnet-min.py` -- `scripts/pip-min.py` -- `scripts/publish-*.py` - -## Workspace Support - -- Uses `sdt-workspace.json` when present -- If missing, can auto-discover nearby projects containing `devtool.json` -- Workspace screen can add external project roots (absolute paths supported) -- `projects[].disabled`, `projects[].tags`, and `projects[].toolFamilies` are supported - -## Dev Shell Bootstrap - -Python-first cross-shell dev environment bootstrap: - -```powershell -# PowerShell -. ./scripts/dev-shell.ps1 - -# cmd -scripts\dev-shell.cmd -``` - -```bash -# bash/zsh -source ./scripts/dev-shell.sh -``` - -Underlying implementation is `scripts/dev_shell.py`: - -- `python scripts/dev_shell.py export --shell pwsh --json` -- `python scripts/dev_shell.py doctor` - -## Legacy PowerShell Compatibility - -Legacy `.ps1` scripts remain for migration compatibility only. New functionality is implemented in Python scripts first. `script-common.ps1` is legacy-only. - -Legacy runtime behavior in v1.2: - -- strict mode rejects `targets`-only configs by default -- compat mode (`SDT_LEGACY_MODE=compat`) temporarily allows legacy execution -- TUI `SYSTEM` includes `Migrate legacy targets -> workflows` to apply migration in place (with backup) - -Deprecation target: - -- v1.x: compatibility only (no new behavior guarantees) -- v2.0: remove legacy `.ps1` scripts from default SDT workflows and docs - -## Testing - -Run unit/integration tests: - -```powershell -dotnet test tests/DevTool.Tests/DevTool.Tests.csproj -``` - -Run Python script smoke checks: - -```powershell -python -m py_compile scripts/*.py -``` diff --git a/Journal.DevTool/ROADMAP.md b/Journal.DevTool/ROADMAP.md deleted file mode 100644 index bb3044d..0000000 --- a/Journal.DevTool/ROADMAP.md +++ /dev/null @@ -1,50 +0,0 @@ -# SDT Roadmap / Kanban - -## Done (v1.2 Stabilization) - -- [x] Python-first runtime for diagnostics/build wrappers -- [x] Config bootstrap generator for missing `devtool.json` -- [x] Workflow-first execution + dual schema support -- [x] Strict legacy mode default (`targets`-only blocked) -- [x] Compatibility escape hatch (`SDT_LEGACY_MODE=compat`) -- [x] Auto-generated migration preview (`devtool.generated.workflows.json`) -- [x] In-app migration action: `Migrate legacy targets -> workflows` -- [x] Centralized requirement inference (`RequirementResolver`) -- [x] Installer planning precedence + fallback resilience -- [x] Windows resolver hardening (`%VAR%` PATH expansion) -- [x] Resolver tracing surfaced in probe details -- [x] Secure diagnostics default (allowlist-only env capture) -- [x] Workspace external project add flow -- [x] Shared run event stream (`RunEvent`) across workflow + debug execution -- [x] TUI event rendering layer wired on top of core run events (GUI-readiness slice) -- [x] Persist run-event stream to JSONL for external GUI/client consumption (`.sdt/events/*.jsonl`) -- [x] TUI events viewer for persisted run-event logs (`SYSTEM -> View run events`) -- [x] Config doctor (`SYSTEM -> Run config doctor`) -- [x] Doctor autofix actions (create missing working dirs + invoke legacy migration) -- [x] Rich probe diagnostics panel in workflow failure summary -- [x] Enhanced Tauri fallback guidance (Windows/macOS/Linux package manager aware) -- [x] Workspace-level defaults file layering (`sdt-defaults.json`) above per-project `devtool.json` - -## In Progress (next focus) - -- [x] Add dedicated TUI "Events" viewer for last run -- [x] Add doctor autofix actions for common issues (missing dirs, legacy schema migration) - -## Next (v1.3 candidates) - -- [ ] Setup wizard for first run (bootstrap + tool fixes) -- [ ] Env profiles (`dev`, `ci`, `release`) with deterministic merge order -- [ ] Managed secrets redaction policy for diagnostics bundles -- [ ] Favorites/quick actions across projects - -## Later - -- [ ] JSON event stream for GUI integration -- [ ] Native GUI shell over headless core services -- [ ] Remove legacy PowerShell wrappers in v2 - -## Current Milestone Status - -- Robustness Sprint v1.2: **complete** -- v1.1 expansion items shipped partially (debug profiles + diagnostics + workspace add external) -- Remaining gaps for broader AIO vision are mainly UX/workspace scale and advanced env orchestration diff --git a/Journal.DevTool/Runner/ProcessRunner.cs b/Journal.DevTool/Runner/ProcessRunner.cs deleted file mode 100644 index 3dbb3a5..0000000 --- a/Journal.DevTool/Runner/ProcessRunner.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Diagnostics; - -namespace Sdt.Runner; - -public sealed record RunResult(int ExitCode, TimeSpan Elapsed) -{ - public bool Success => ExitCode == 0; -} - -public static class ProcessRunner -{ - /// - /// Runs a command with the given args, streaming stdout/stderr via . - /// onOutput receives (line, isStderr). - /// - public static async Task RunAsync( - string command, - IEnumerable args, - string workingDir, - Action onOutput, - IReadOnlyDictionary? envOverrides = null, - CancellationToken cancellationToken = default) - { - var psi = new ProcessStartInfo - { - FileName = Core.CommandResolver.Resolve(command), - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = workingDir, - }; - - if (envOverrides is not null) - { - foreach (var kvp in envOverrides) - psi.Environment[kvp.Key] = kvp.Value; - } - - foreach (var arg in args) - psi.ArgumentList.Add(arg); - - var sw = Stopwatch.StartNew(); - - using var process = new Process { StartInfo = psi }; - - void OnCancel(object? sender, ConsoleCancelEventArgs e) - { - e.Cancel = true; // Prevent SDT from exiting immediately - try { process.Kill(entireProcessTree: true); } catch { } - } - - Console.CancelKeyPress += OnCancel; - - try - { - process.Start(); - - var stdoutTask = DrainAsync(process.StandardOutput, line => onOutput(line, false), cancellationToken); - var stderrTask = DrainAsync(process.StandardError, line => onOutput(line, true), cancellationToken); - - await Task.WhenAll(stdoutTask, stderrTask).ConfigureAwait(false); - await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); - } - finally - { - Console.CancelKeyPress -= OnCancel; - } - - sw.Stop(); - return new RunResult(process.ExitCode, sw.Elapsed); - } - - private static async Task DrainAsync(StreamReader reader, Action emit, CancellationToken ct) - { - string? line; - while ((line = await reader.ReadLineAsync(ct).ConfigureAwait(false)) is not null - && !ct.IsCancellationRequested) - { - emit(line); - } - } -} diff --git a/Journal.DevTool/Runner/TargetRunner.cs b/Journal.DevTool/Runner/TargetRunner.cs deleted file mode 100644 index b4c670e..0000000 --- a/Journal.DevTool/Runner/TargetRunner.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Sdt.Config; - -namespace Sdt.Runner; - -public static class TargetRunner -{ - /// - /// Returns the ordered list of real (non-virtual) steps needed to execute , - /// respecting DependsOn chains. Each step appears at most once. - /// - public static List ResolvePlan( - BuildTarget target, - IReadOnlyDictionary allTargets) - { - var visited = new HashSet(StringComparer.OrdinalIgnoreCase); - var plan = new List(); - Visit(target, allTargets, visited, plan); - return plan; - } - - private static void Visit( - BuildTarget target, - IReadOnlyDictionary allTargets, - HashSet visited, - List plan) - { - if (!visited.Add(target.Id)) - return; - - // Recurse into dependencies first (topological order) - foreach (var depId in target.DependsOn) - { - if (allTargets.TryGetValue(depId, out var dep)) - Visit(dep, allTargets, visited, plan); - } - - // Virtual aggregator targets (null Command) are just dependency collectors - if (target.Command is not null) - plan.Add(target); - } -} diff --git a/Journal.DevTool/Tui/App.cs b/Journal.DevTool/Tui/App.cs deleted file mode 100644 index 97f5461..0000000 --- a/Journal.DevTool/Tui/App.cs +++ /dev/null @@ -1,800 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Sdt.Core.Debug; -using Spectre.Console; - -namespace Sdt.Tui; - -internal sealed record MenuItem(string Display, string Value); - -public enum AppExitReason { Quit, SwitchProject } -public sealed record AppResult(AppExitReason Reason, string? NewProjectRoot = null); - -public sealed class App -{ - private DevToolConfig _config; - private string _projectRoot; - private readonly WorkspaceConfig? _workspace; - private readonly string? _workspaceRoot; - private List _workflows; - private readonly List _warnings; - private readonly IDebugProfileRunner _debugRunner; - private readonly IDiagnosticsBundleService _diagnostics; - private readonly IRequirementResolver _requirementResolver = new RequirementResolver(); - - private readonly WorkflowExecutor _executor = new( - new WorkflowPlanner(), - new ToolProbeService(), - new PrereqInstallerService(), - new ActionRunner(), - new RequirementResolver()); - - private IReadOnlyDictionary WorkflowMap => - _workflows.ToDictionary(t => t.Id, StringComparer.OrdinalIgnoreCase); - - public App( - DevToolConfig config, - string projectRoot, - IReadOnlyList? warnings = null, - WorkspaceConfig? workspace = null, - string? workspaceRoot = null) - { - _config = config; - _projectRoot = projectRoot; - _workspace = workspace; - _workspaceRoot = workspaceRoot; - var normalized = WorkflowModelBuilder.Normalize(config, ResolveLegacyMode(), _requirementResolver); - _workflows = normalized.Workflows.ToList(); - _warnings = []; - _debugRunner = new DebugProfileRunner(new ToolProbeService(), new PrereqInstallerService()); - _diagnostics = new DiagnosticsBundleService(); - if (warnings is not null) - _warnings.AddRange(warnings); - _warnings.AddRange(normalized.Warnings); - } - - private static LegacyMode ResolveLegacyMode() - { - var raw = Environment.GetEnvironmentVariable("SDT_LEGACY_MODE"); - return string.Equals(raw, "compat", StringComparison.OrdinalIgnoreCase) - ? LegacyMode.Compat - : LegacyMode.Strict; - } - - public async Task RunAsync() - { - while (true) - { - AnsiConsole.Clear(); - RenderBanner(); - - if (_warnings.Count > 0) - { - foreach (var warning in _warnings.Distinct(StringComparer.OrdinalIgnoreCase)) - AnsiConsole.MarkupLine(Theme.Warn("Config warning: " + warning)); - AnsiConsole.WriteLine(); - } - - var choice = ShowMainMenu(); - switch (choice) - { - case "__env__": - EditEnvironment(); - break; - - case "__toolchains__": - await new ToolchainScreen(_config, _projectRoot).RunAsync(); - break; - - case "__doctor__": - await RunConfigDoctorAsync(); - break; - - case "__events__": - new EventsScreen(_projectRoot, _config.Name, _config.Version).Run(); - break; - - case "__workspace__": - if (_workspace is not null && _workspaceRoot is not null) - { - var switcher = new WorkspaceScreen(_workspace, _workspaceRoot, _projectRoot); - var newRoot = switcher.SelectProject(); - if (newRoot is not null) - return new AppResult(AppExitReason.SwitchProject, newRoot); - } - break; - - case "__migrate_legacy__": - ApplyLegacyMigration(); - break; - - case "__quit__": - AnsiConsole.MarkupLine("\n" + Theme.Faint("Later.") + "\n"); - return new AppResult(AppExitReason.Quit); - - default: - if (choice.StartsWith("__debugattach__:", StringComparison.Ordinal)) - { - var profileId = choice["__debugattach__:".Length..]; - ShowAttachInstructions(profileId); - break; - } - - if (choice.StartsWith("__debug__:", StringComparison.Ordinal)) - { - var parts = choice.Split(':', 3, StringSplitOptions.None); - if (parts.Length == 3) - await RunDebugProfileAsync(parts[1], string.Equals(parts[2], "verbose", StringComparison.OrdinalIgnoreCase)); - break; - } - - var workflowMap = WorkflowMap; - if (workflowMap.TryGetValue(choice, out var workflow)) - await RunWorkflowAsync(workflow, workflowMap); - break; - } - - if (choice != "__quit__") - { - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to return to the menu...")); - Console.ReadKey(intercept: true); - } - } - } - - private void RenderBanner() - { - AnsiConsole.Write(new FigletText("SDT").Color(Theme.GreenColor)); - var wsInfo = _workspace is not null - ? $" [{Theme.GreenDim}]∙ {Markup.Escape(_workspace.Name)}[/]" - : string.Empty; - - AnsiConsole.Write( - new Rule($"[bold {Theme.GreenBold}]{Markup.Escape(_config.Name)}[/] [{Theme.GreenDim}]v{Markup.Escape(_config.Version)}[/]{wsInfo}") - .RuleStyle(Theme.DimStyle)); - AnsiConsole.MarkupLine(Theme.Faint($"root: {_projectRoot}") + "\n"); - } - - private string ShowMainMenu() - { - var prompt = new SelectionPrompt() - .Title($"[{Theme.Green}]What would you like to do?[/]") - .PageSize(28) - .MoreChoicesText(Theme.Faint("(scroll to see more)")) - .UseConverter(m => m.Display); - - var groups = _workflows - .Where(t => !string.IsNullOrWhiteSpace(t.Label)) - .GroupBy(t => string.IsNullOrWhiteSpace(t.Group) ? "General" : t.Group); - - foreach (var group in groups) - { - var header = new MenuItem( - $"[bold {Theme.Amber}]{Markup.Escape(group.Key.ToUpperInvariant())}[/]", - "__group__"); - - var items = group.Select(t => new MenuItem( - $"[{Theme.Green}]{Markup.Escape(t.Label)}[/] [{Theme.GreenDim}]{Markup.Escape(t.Description)}[/]", - t.Id)).ToList(); - - prompt.AddChoiceGroup(header, items); - } - - var debugProfiles = _config.Debug?.Profiles ?? []; - if (debugProfiles.Count > 0) - { - var debugItems = new List(); - foreach (var profile in debugProfiles) - { - debugItems.Add(new MenuItem( - $"[{Theme.Green}]Run {Markup.Escape(profile.Label)}[/] [{Theme.GreenDim}]debug profile[/]", - $"__debug__:{profile.Id}:normal")); - debugItems.Add(new MenuItem( - $"[{Theme.Green}]Run {Markup.Escape(profile.Label)} (verbose)[/] [{Theme.GreenDim}]stream full output[/]", - $"__debug__:{profile.Id}:verbose")); - if (profile.Attach is not null) - { - debugItems.Add(new MenuItem( - $"[{Theme.Green}]Attach instructions: {Markup.Escape(profile.Label)}[/]", - $"__debugattach__:{profile.Id}")); - } - } - - prompt.AddChoiceGroup( - new MenuItem($"[bold {Theme.Amber}]DEBUG[/]", "__group__"), - debugItems); - } - - var systemItems = new List - { - new($"[{Theme.Green}]🩺 Run config doctor[/] [{Theme.GreenDim}]validate config, tools, paths[/]", "__doctor__"), - new($"[{Theme.Green}]📜 View run events[/] [{Theme.GreenDim}].sdt/events JSONL viewer[/]", "__events__"), - new($"[{Theme.Green}]⚙ Edit environment variables[/]", "__env__"), - }; - - if (_config.Toolchains is not null) - systemItems.Insert(0, new MenuItem( - $"[{Theme.Green}]⬡ Toolchain management[/] [{Theme.GreenDim}]python / node[/]", - "__toolchains__")); - - if (_workspace is not null) - systemItems.Insert(0, new MenuItem( - $"[{Theme.Green}]⇄ Workspace projects[/] [{Theme.GreenDim}]{Markup.Escape(_workspace.Name)}[/]", - "__workspace__")); - - if (_config.Targets.Count > 0) - systemItems.Insert(0, new MenuItem( - $"[{Theme.Green}]⇪ Migrate legacy targets → workflows[/] [{Theme.GreenDim}]writes devtool.json + backup[/]", - "__migrate_legacy__")); - - systemItems.Add(new MenuItem($"[{Theme.GreenDim}]✗ Quit[/]", "__quit__")); - - prompt.AddChoiceGroup( - new MenuItem($"[bold {Theme.Amber}]SYSTEM[/]", "__group__"), - systemItems); - - return AnsiConsole.Prompt(prompt).Value; - } - - private async Task RunWorkflowAsync( - WorkflowDefinition workflow, - IReadOnlyDictionary workflowMap) - { - AnsiConsole.Clear(); - AnsiConsole.Write(Theme.SectionRule(workflow.Label)); - - var plan = new WorkflowPlanner().ResolvePlan(workflow, workflowMap); - if (plan.Count == 0) - { - AnsiConsole.MarkupLine(Theme.Warn("This workflow has no executable steps.")); - return; - } - - if (plan.Count > 1) - { - AnsiConsole.MarkupLine(Theme.Faint($"Execution plan — {plan.Count} workflow(s):")); - foreach (var item in plan) - AnsiConsole.MarkupLine($" [{Theme.GreenDim}]→[/] [{Theme.Green}]{Markup.Escape(item.Label)}[/]"); - AnsiConsole.WriteLine(); - } - - var outputLines = new List(); - using var eventRecorder = RunEventJsonlRecorder.Create(_projectRoot, "workflow"); - - var result = await _executor.ExecuteAsync( - workflow, - workflowMap, - _config, - _projectRoot, - confirmInstallAsync: ConfirmInstallAsync, - onOutput: (line, isErr) => - { - outputLines.Add((isErr ? "ERR: " : "OUT: ") + line); - var escaped = Markup.Escape(line); - AnsiConsole.MarkupLine(isErr - ? $"[{Theme.Amber}]{escaped}[/]" - : $"[{Theme.Green}]{escaped}[/]"); - }, - onEvent: evt => - { - eventRecorder.Write(evt); - RenderRunEvent(evt); - }); - - AnsiConsole.Write(Theme.SectionRule()); - RenderStepSummary(result); - RenderProbeDiagnosticsSummary(outputLines); - if (result.Success) - { - var totalSeconds = result.Steps.Sum(s => s.Result.Elapsed.TotalSeconds); - AnsiConsole.MarkupLine("\n" + Theme.Ok($"Done! Total: {totalSeconds:F1}s")); - AnsiConsole.MarkupLine(Theme.Faint($"Run events: {eventRecorder.FilePath}")); - return; - } - - AnsiConsole.MarkupLine("\n" + Theme.Fail($"{result.StopReason}: {result.Message}")); - AnsiConsole.MarkupLine(Theme.Faint($"Run events: {eventRecorder.FilePath}")); - await WriteWorkflowDiagnosticsAsync(workflow, workflowMap, result, outputLines); - } - - private static void RenderStepSummary(WorkflowExecutionResult result) - { - if (result.Steps.Count == 0) - return; - - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]Workflow[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Step[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Exit[/]").Width(8)) - .AddColumn(new TableColumn($"[{Theme.Amber}]Seconds[/]").Width(10)) - .AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(12)); - - foreach (var step in result.Steps) - { - var ok = step.Result.Success; - table.AddRow( - Theme.Faint(step.WorkflowId), - Theme.G(step.StepLabel), - Theme.Bold(step.Result.ExitCode.ToString()), - Theme.Faint($"{step.Result.Elapsed.TotalSeconds:F1}"), - ok ? Theme.Ok("ok") : Theme.Fail("failed")); - } - - AnsiConsole.Write(table); - } - - private async Task RunDebugProfileAsync(string profileId, bool verbose) - { - var profile = _config.Debug?.Profiles.FirstOrDefault(p => string.Equals(p.Id, profileId, StringComparison.OrdinalIgnoreCase)); - if (profile is null) - { - AnsiConsole.MarkupLine(Theme.Fail($"Debug profile not found: {profileId}")); - return; - } - - AnsiConsole.Clear(); - AnsiConsole.Write(Theme.SectionRule($"DEBUG — {profile.Label}")); - using var eventRecorder = RunEventJsonlRecorder.Create(_projectRoot, "debug"); - - if (profile.Attach is not null) - { - AnsiConsole.MarkupLine(Theme.Faint($"Attach: {profile.Attach.Kind}")); - if (profile.Attach.Port is not null) - AnsiConsole.MarkupLine(Theme.Faint($"Port: {profile.Attach.Port}")); - if (!string.IsNullOrWhiteSpace(profile.Attach.ProcessName)) - AnsiConsole.MarkupLine(Theme.Faint($"Process: {profile.Attach.ProcessName}")); - if (!string.IsNullOrWhiteSpace(profile.Attach.Note)) - AnsiConsole.MarkupLine(Theme.Faint(profile.Attach.Note!)); - AnsiConsole.WriteLine(); - } - - var result = await _debugRunner.RunAsync( - profile, - _config, - _projectRoot, - verbose, - ConfirmInstallAsync, - (line, isErr) => - { - var escaped = Markup.Escape(line); - AnsiConsole.MarkupLine(isErr - ? $"[{Theme.Amber}]{escaped}[/]" - : $"[{Theme.Green}]{escaped}[/]"); - }, - evt => - { - eventRecorder.Write(evt); - RenderRunEvent(evt); - }); - - if (result.Success) - { - var seconds = result.RunResult?.Elapsed.TotalSeconds ?? 0; - AnsiConsole.MarkupLine("\n" + Theme.Ok($"Debug run completed in {seconds:F1}s")); - AnsiConsole.MarkupLine(Theme.Faint($"Run events: {eventRecorder.FilePath}")); - return; - } - - AnsiConsole.MarkupLine("\n" + Theme.Fail($"{result.StopReason}: {result.Message}")); - AnsiConsole.MarkupLine(Theme.Faint($"Run events: {eventRecorder.FilePath}")); - - var diagnostics = _config.Debug?.Diagnostics ?? new DebugDiagnosticsOptions(); - if (!diagnostics.Enabled || !diagnostics.BundleOnFailure) - return; - - var bundle = await _diagnostics.WriteBundleAsync( - new DiagnosticsBundleRequest( - Category: "debug", - ProjectRoot: _projectRoot, - SummaryMessage: result.Message, - OutputLines: result.OutputLines, - WorkflowSteps: [], - Probes: result.Probes, - DiagnosticsOptions: diagnostics, - Config: _config, - StopReason: result.StopReason, - DebugRun: result.RunResult, - DebugProfile: result.Profile)); - - if (bundle.Success) - AnsiConsole.MarkupLine(Theme.Warn($"Diagnostics bundle: {bundle.BundleDirectory}")); - else - AnsiConsole.MarkupLine(Theme.Warn($"Diagnostics bundle failed: {bundle.Message}")); - } - - private static void RenderProbeDiagnosticsSummary(IReadOnlyList outputLines) - { - var diagnostics = new List<(string Tool, string Detail)>(); - foreach (var line in outputLines) - { - const string marker = "Probe detail ["; - var markerIndex = line.IndexOf(marker, StringComparison.OrdinalIgnoreCase); - if (markerIndex < 0) - continue; - - var toolStart = markerIndex + marker.Length; - var toolEnd = line.IndexOf(']', toolStart); - if (toolEnd <= toolStart) - continue; - - var tool = line[toolStart..toolEnd].Trim(); - var detailStart = line.IndexOf(':', toolEnd); - var detail = detailStart >= 0 && detailStart + 1 < line.Length - ? line[(detailStart + 1)..].Trim() - : ""; - - if (!string.IsNullOrWhiteSpace(tool)) - diagnostics.Add((tool, detail)); - } - - if (diagnostics.Count == 0) - return; - - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]Probe Tool[/]").Width(14)) - .AddColumn(new TableColumn($"[{Theme.Amber}]Resolver / Probe Detail[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Suggested Fix[/]")); - - foreach (var diag in diagnostics.Distinct()) - { - table.AddRow( - Theme.Warn(diag.Tool), - Theme.Faint(diag.Detail), - Theme.Faint(GetProbeFixHint(diag.Tool, diag.Detail))); - } - - AnsiConsole.WriteLine(); - AnsiConsole.Write(Theme.SectionRule("PROBE DIAGNOSTICS")); - AnsiConsole.Write(table); - } - - private static string GetProbeFixHint(string tool, string detail) - { - if (detail.Contains("ConfiguredOverride", StringComparison.OrdinalIgnoreCase)) - return "Check tooling.tools[].executables paths."; - if (detail.Contains("NodeAdjacentShim", StringComparison.OrdinalIgnoreCase)) - return "Node found; verify npm/yarn shim in node directory."; - if (detail.Contains("Fallback", StringComparison.OrdinalIgnoreCase)) - return $"Add {tool} to PATH or set tooling.tools[].executables."; - return $"Install/configure {tool} then rerun."; - } - - private static void RenderRunEvent(RunEvent evt) - { - var shouldRender = evt.Type is - RunEventType.WorkflowStarted or - RunEventType.WorkflowStepStarted or - RunEventType.WorkflowStepCompleted or - RunEventType.ProbeFailed or - RunEventType.InstallPlanPrepared or - RunEventType.DebugStarted or - RunEventType.DebugCommandStarted or - RunEventType.DebugCommandCompleted or - RunEventType.WorkflowCompleted or - RunEventType.DebugCompleted; - - if (!shouldRender) - return; - - var prefix = evt.Category.Equals("debug", StringComparison.OrdinalIgnoreCase) ? "DBG" : "RUN"; - var tone = evt.Success is false ? Theme.Amber : Theme.GreenDim; - AnsiConsole.MarkupLine($"[{tone}]{prefix} {Markup.Escape(evt.Message)}[/]"); - } - - private void ShowAttachInstructions(string profileId) - { - var profile = _config.Debug?.Profiles.FirstOrDefault(p => string.Equals(p.Id, profileId, StringComparison.OrdinalIgnoreCase)); - if (profile?.Attach is null) - { - AnsiConsole.MarkupLine(Theme.Warn("No attach instructions configured for this profile.")); - return; - } - - AnsiConsole.Clear(); - AnsiConsole.Write(Theme.SectionRule($"ATTACH — {profile.Label}")); - AnsiConsole.MarkupLine(Theme.Faint($"Kind: {profile.Attach.Kind}")); - if (profile.Attach.Port is not null) - AnsiConsole.MarkupLine(Theme.Faint($"Port: {profile.Attach.Port}")); - if (!string.IsNullOrWhiteSpace(profile.Attach.ProcessName)) - AnsiConsole.MarkupLine(Theme.Faint($"Process: {profile.Attach.ProcessName}")); - if (!string.IsNullOrWhiteSpace(profile.Attach.Note)) - AnsiConsole.MarkupLine(Theme.G(profile.Attach.Note!)); - } - - private async Task WriteWorkflowDiagnosticsAsync( - WorkflowDefinition workflow, - IReadOnlyDictionary workflowMap, - WorkflowExecutionResult result, - IReadOnlyList outputLines) - { - var diagnostics = _config.Debug?.Diagnostics ?? new DebugDiagnosticsOptions(); - if (!diagnostics.Enabled || !diagnostics.BundleOnFailure) - return; - - var probes = await SnapshotWorkflowToolsAsync(workflow, workflowMap); - - var bundle = await _diagnostics.WriteBundleAsync( - new DiagnosticsBundleRequest( - Category: "workflow", - ProjectRoot: _projectRoot, - SummaryMessage: result.Message, - OutputLines: outputLines, - WorkflowSteps: result.Steps, - Probes: probes, - DiagnosticsOptions: diagnostics, - Config: _config, - StopReason: result.StopReason)); - - if (bundle.Success) - AnsiConsole.MarkupLine(Theme.Warn($"Diagnostics bundle: {bundle.BundleDirectory}")); - else - AnsiConsole.MarkupLine(Theme.Warn($"Diagnostics bundle failed: {bundle.Message}")); - } - - private async Task> SnapshotWorkflowToolsAsync( - WorkflowDefinition workflow, - IReadOnlyDictionary workflowMap) - { - var plan = new WorkflowPlanner().ResolvePlan(workflow, workflowMap); - var tools = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var item in plan) - { - foreach (var step in item.Steps) - { - foreach (var req in _requirementResolver.Resolve(step)) - { - if (!string.IsNullOrWhiteSpace(req.Tool)) - tools.Add(req.Tool); - } - } - } - - var probeService = new ToolProbeService(); - var probes = new List(); - foreach (var tool in tools) - probes.Add(await probeService.ProbeAsync(tool, _projectRoot, _config)); - return probes; - } - - private Task ConfirmInstallAsync(string tool, InstallPlan plan) - { - AnsiConsole.MarkupLine(Theme.Warn($"Missing prerequisite: {tool}")); - AnsiConsole.MarkupLine(Theme.Faint(plan.Summary)); - foreach (var cmd in plan.Commands) - AnsiConsole.MarkupLine(Theme.Faint($" $ {cmd.Command} {string.Join(" ", cmd.Args)}")); - - var allow = AnsiConsole.Confirm( - $"[{Theme.Amber}]Run install commands for {Markup.Escape(tool)} now?[/]", - defaultValue: false); - return Task.FromResult(allow); - } - - private async Task RunConfigDoctorAsync() - { - AnsiConsole.Clear(); - AnsiConsole.Write(Theme.SectionRule("CONFIG DOCTOR")); - AnsiConsole.WriteLine(); - - var service = new ConfigDoctorService(new ToolProbeService(), _requirementResolver); - var report = await service.RunAsync(_config, _projectRoot); - - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]Check[/]").Width(26)) - .AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(10)) - .AddColumn(new TableColumn($"[{Theme.Amber}]Detail[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Fix[/]")); - - foreach (var check in report.Checks) - { - var statusText = check.Status switch - { - DoctorStatus.Pass => Theme.Ok("ok"), - DoctorStatus.Warn => Theme.Warn("warn"), - DoctorStatus.Fail => Theme.Fail("fail"), - _ => Theme.Faint("n/a"), - }; - - table.AddRow( - Theme.G(check.Name), - statusText, - Theme.Faint(check.Detail), - string.IsNullOrWhiteSpace(check.Fix) ? Theme.Faint("-") : Theme.Faint(check.Fix)); - } - - AnsiConsole.Write(table); - AnsiConsole.WriteLine(); - - if (report.HasFailures) - AnsiConsole.MarkupLine(Theme.Fail("Doctor found blocking issues.")); - else if (report.HasWarnings) - AnsiConsole.MarkupLine(Theme.Warn("Doctor completed with warnings.")); - else - AnsiConsole.MarkupLine(Theme.Ok("Doctor completed: no issues found.")); - - if (!report.HasFailures && !report.HasWarnings) - return; - - var applyFixes = AnsiConsole.Confirm( - $"[{Theme.Amber}]Apply common autofixes now?[/]", - defaultValue: false); - if (!applyFixes) - return; - - var fixer = new ConfigDoctorAutoFixService(); - var missingDirs = fixer.FindMissingWorkingDirectories(_config, _projectRoot); - if (missingDirs.Count > 0) - { - var createDirs = AnsiConsole.Confirm( - $"[{Theme.Amber}]Create {missingDirs.Count} missing working director{(missingDirs.Count == 1 ? "y" : "ies")}?[/]", - defaultValue: true); - if (createDirs) - { - var dirFix = fixer.CreateMissingWorkingDirectories(missingDirs); - AnsiConsole.MarkupLine(dirFix.Success ? Theme.Ok(dirFix.Message) : Theme.Fail(dirFix.Message)); - } - } - - if (_config.Targets.Count > 0) - { - var migrate = AnsiConsole.Confirm( - $"[{Theme.Amber}]Migrate legacy targets to workflows now?[/]", - defaultValue: true); - if (migrate) - { - var migration = fixer.ApplyLegacyMigration(_projectRoot); - if (migration.Success) - { - AnsiConsole.MarkupLine(Theme.Ok("Legacy migration applied from doctor.")); - if (!string.IsNullOrWhiteSpace(migration.BackupPath)) - AnsiConsole.MarkupLine(Theme.Faint($"Backup: {migration.BackupPath}")); - - var reloaded = ConfigLoader.FindAndLoad(_projectRoot); - if (reloaded is not null) - { - _config = reloaded.Config; - var normalized = WorkflowModelBuilder.Normalize(_config, ResolveLegacyMode(), _requirementResolver); - _workflows = normalized.Workflows.ToList(); - _warnings.Clear(); - _warnings.AddRange(reloaded.Warnings); - _warnings.AddRange(normalized.Warnings); - } - } - else - { - AnsiConsole.MarkupLine(Theme.Fail(migration.Message)); - } - } - } - } - - private void ApplyLegacyMigration() - { - if (_config.Targets.Count == 0) - { - AnsiConsole.MarkupLine(Theme.Warn("No legacy targets found in this config.")); - return; - } - - var configPath = ConfigLoader.FindConfigPath(_projectRoot); - if (string.IsNullOrWhiteSpace(configPath)) - { - AnsiConsole.MarkupLine(Theme.Fail("Could not locate devtool.json to migrate.")); - return; - } - - var confirm = AnsiConsole.Confirm( - $"[{Theme.Amber}]Migrate legacy targets to workflows and overwrite devtool.json (with backup)?[/]", - defaultValue: true); - if (!confirm) - return; - - var result = ConfigLoader.ApplyLegacyTargetMigration(configPath, createBackup: true); - if (!result.Success) - { - AnsiConsole.MarkupLine(Theme.Fail(result.Message)); - return; - } - - var reloaded = ConfigLoader.FindAndLoad(_projectRoot); - if (reloaded is null) - { - AnsiConsole.MarkupLine(Theme.Fail("Migration wrote config, but reload failed.")); - return; - } - - _config = reloaded.Config; - var normalized = WorkflowModelBuilder.Normalize(_config, ResolveLegacyMode(), _requirementResolver); - _workflows = normalized.Workflows.ToList(); - _warnings.Clear(); - _warnings.AddRange(reloaded.Warnings); - _warnings.AddRange(normalized.Warnings); - - AnsiConsole.MarkupLine(Theme.Ok("Migration complete.")); - if (!string.IsNullOrWhiteSpace(result.BackupPath)) - AnsiConsole.MarkupLine(Theme.Faint($"Backup: {result.BackupPath}")); - } - - private void EditEnvironment() - { - AnsiConsole.Clear(); - - if (_config.Env.Count == 0) - { - AnsiConsole.MarkupLine(Theme.Warn("No environment variables defined in devtool.json.")); - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back...")); - Console.ReadKey(intercept: true); - return; - } - - while (true) - { - AnsiConsole.Write(Theme.SectionRule("ENVIRONMENT")); - - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]Variable[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Current Value[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Description[/]")); - - foreach (var def in _config.Env) - { - var val = Environment.GetEnvironmentVariable(def.Key) ?? def.DefaultValue; - table.AddRow( - Theme.Warn(def.Key), - Theme.Bold(val.Length > 0 ? val : "(not set)"), - Theme.Faint(def.Description)); - } - - AnsiConsole.Write(table); - AnsiConsole.MarkupLine(Theme.Faint("Changes apply to this SDT session only.\n")); - - var choices = _config.Env - .Select(e => - { - var curr = Environment.GetEnvironmentVariable(e.Key) ?? e.DefaultValue; - return new MenuItem( - $"[{Theme.Amber}]{Markup.Escape(e.Key)}[/] [{Theme.GreenDim}]= {Markup.Escape(curr)}[/]", - e.Key); - }) - .Append(new MenuItem(Theme.Faint("← Back"), "__back__")) - .ToList(); - - var selected = AnsiConsole.Prompt( - new SelectionPrompt() - .Title($"[{Theme.Green}]Select a variable to edit:[/]") - .UseConverter(m => m.Display) - .AddChoices(choices)); - - if (selected.Value == "__back__") break; - - var envDef = _config.Env.First(e => e.Key == selected.Value); - var current = Environment.GetEnvironmentVariable(envDef.Key) ?? envDef.DefaultValue; - - string newVal; - if (envDef.Options.Count > 0) - { - newVal = AnsiConsole.Prompt( - new SelectionPrompt() - .Title($"[{Theme.Amber}]{Markup.Escape(envDef.Key)}[/] [{Theme.GreenDim}]current: {Markup.Escape(current)}[/]") - .AddChoices(envDef.Options)); - } - else - { - newVal = AnsiConsole.Ask( - $"[{Theme.Amber}]{Markup.Escape(envDef.Key)}[/]", current); - } - - Environment.SetEnvironmentVariable(envDef.Key, newVal); - AnsiConsole.MarkupLine("\n" + Theme.Ok($"{envDef.Key} = {newVal}") + "\n"); - Thread.Sleep(500); - AnsiConsole.Clear(); - } - } -} diff --git a/Journal.DevTool/Tui/EventsScreen.cs b/Journal.DevTool/Tui/EventsScreen.cs deleted file mode 100644 index 7af2a9a..0000000 --- a/Journal.DevTool/Tui/EventsScreen.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Sdt.Core; -using Spectre.Console; - -namespace Sdt.Tui; - -public sealed class EventsScreen(string projectRoot, string projectName, string version) -{ - private readonly string _projectRoot = projectRoot; - private readonly string _projectName = projectName; - private readonly string _version = version; - private readonly RunEventLogReader _reader = new(); - - public void Run() - { - while (true) - { - AnsiConsole.Clear(); - RenderHeader("EVENTS"); - var files = _reader.ListEventFiles(_projectRoot); - if (files.Count == 0) - { - AnsiConsole.MarkupLine(Theme.Warn("No event logs found yet.")); - AnsiConsole.MarkupLine(Theme.Faint("Run a workflow/debug action first, then return here.")); - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back...")); - Console.ReadKey(intercept: true); - return; - } - - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]File[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Updated[/]").Width(21)) - .AddColumn(new TableColumn($"[{Theme.Amber}]Size[/]").Width(10)); - foreach (var file in files.Take(12)) - { - table.AddRow( - Theme.G(file.Name), - Theme.Faint(file.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")), - Theme.Faint($"{Math.Max(1, file.SizeBytes / 1024)} KB")); - } - AnsiConsole.Write(table); - AnsiConsole.WriteLine(); - - var choices = files - .Take(20) - .Select(f => new MenuItem( - $"[{Theme.Green}]{Markup.Escape(f.Name)}[/] [{Theme.GreenDim}]{f.LastWriteTime:yyyy-MM-dd HH:mm:ss}[/]", - f.Path)) - .ToList(); - choices.Add(new MenuItem(Theme.Faint("← Back"), "__back__")); - - var selected = AnsiConsole.Prompt( - new SelectionPrompt() - .Title($"[{Theme.Green}]Select an event log to view:[/]") - .PageSize(20) - .UseConverter(m => m.Display) - .AddChoices(choices)); - - if (selected.Value == "__back__") - return; - - ShowEventFile(selected.Value); - } - } - - private void ShowEventFile(string filePath) - { - var events = _reader.ReadEvents(filePath); - AnsiConsole.Clear(); - RenderHeader("EVENTS VIEWER"); - AnsiConsole.MarkupLine(Theme.Faint(Path.GetFileName(filePath))); - AnsiConsole.MarkupLine(Theme.Faint(filePath)); - AnsiConsole.WriteLine(); - - if (events.Count == 0) - { - AnsiConsole.MarkupLine(Theme.Warn("No parseable events in selected file.")); - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to return...")); - Console.ReadKey(intercept: true); - return; - } - - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]Time[/]").Width(12)) - .AddColumn(new TableColumn($"[{Theme.Amber}]Type[/]").Width(26)) - .AddColumn(new TableColumn($"[{Theme.Amber}]Message[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(10)); - - foreach (var evt in events.TakeLast(120)) - { - var status = evt.Success switch - { - true => Theme.Ok("ok"), - false => Theme.Fail("fail"), - null => Theme.Faint("-"), - }; - var message = evt.Message; - if (!string.IsNullOrWhiteSpace(evt.Tool)) - message += $" [{evt.Tool}]"; - if (evt.ExitCode is not null) - message += $" (exit {evt.ExitCode})"; - - table.AddRow( - Theme.Faint(evt.OccurredAt.ToString("HH:mm:ss")), - Theme.G(evt.Type.ToString()), - Theme.Faint(message), - status); - } - - AnsiConsole.Write(table); - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to return...")); - Console.ReadKey(intercept: true); - } - - private void RenderHeader(string section) - { - AnsiConsole.Write(new FigletText("SDT").Color(Theme.GreenColor)); - AnsiConsole.Write( - new Rule($"[bold {Theme.GreenBold}]{Markup.Escape(_projectName)}[/] [{Theme.GreenDim}]v{Markup.Escape(_version)}[/] [{Theme.Amber}]{Markup.Escape(section)}[/]") - .RuleStyle(Theme.DimStyle)); - AnsiConsole.MarkupLine(Theme.Faint($"root: {_projectRoot}") + "\n"); - } -} diff --git a/Journal.DevTool/Tui/Theme.cs b/Journal.DevTool/Tui/Theme.cs deleted file mode 100644 index 3593e86..0000000 --- a/Journal.DevTool/Tui/Theme.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Spectre.Console; - -namespace Sdt.Tui; - -/// -/// SDT phosphor-green colour palette. -/// Primary text is classic terminal phosphor (#00FF41). -/// Modern accent colours are kept for highlights and status. -/// -internal static class Theme -{ - // ── Hex colour constants (use in Spectre markup strings) ───────────────── - public const string Green = "#00ff41"; // primary phosphor — all normal text - public const string GreenDim = "#005c1b"; // muted — borders, secondary info - public const string GreenBold = "#a8ff90"; // bright — selections, emphasis - public const string Amber = "#ffb300"; // warnings / group titles - public const string Red = "#ff4040"; // errors - public const string Ghost = "#003d12"; // near-invisible — decorative scanlines - - // ── Spectre Color instances (for FigletText, Rule styles, etc.) ────────── - public static readonly Color GreenColor = new(0, 255, 65); - public static readonly Color GreenDimColor = new(0, 92, 27); - public static readonly Color GreenBoldColor = new(168, 255, 144); - public static readonly Color AmberColor = new(255, 179, 0); - public static readonly Color RedColor = new(255, 64, 64); - - // ── Pre-built Style objects ─────────────────────────────────────────────── - public static readonly Style PrimaryStyle = new(GreenColor); - public static readonly Style DimStyle = new(GreenDimColor); - public static readonly Style BrightStyle = new(GreenBoldColor, decoration: Decoration.Bold); - public static readonly Style AmberStyle = new(AmberColor); - public static readonly Style RedStyle = new(RedColor, decoration: Decoration.Bold); - - // ── Markup helper methods (auto-escape user content) ───────────────────── - public static string G(string t) => $"[{Green}]{Markup.Escape(t)}[/]"; - public static string Faint(string t) => $"[{GreenDim}]{Markup.Escape(t)}[/]"; - public static string Bold(string t) => $"[bold {GreenBold}]{Markup.Escape(t)}[/]"; - public static string Warn(string t) => $"[{Amber}]{Markup.Escape(t)}[/]"; - public static string Err(string t) => $"[bold {Red}]{Markup.Escape(t)}[/]"; - public static string Ok(string t) => $"[bold {Green}]✓ {Markup.Escape(t)}[/]"; - public static string Fail(string t) => $"[bold {Red}]✗ {Markup.Escape(t)}[/]"; - - // ── Shared UI components ────────────────────────────────────────────────── - public static Rule SectionRule(string? title = null) => title is null - ? new Rule().RuleStyle(DimStyle) - : new Rule($"[bold {GreenBold}]{Markup.Escape(title)}[/]").RuleStyle(DimStyle); - - public static Rule DimRule() => new Rule().RuleStyle(new Style(new Color(0, 40, 12))); - - public static Panel StatusPanel(string markup) => - new Panel(markup) - .BorderStyle(DimStyle) - .Padding(1, 0); -} diff --git a/Journal.DevTool/Tui/ToolchainScreen.cs b/Journal.DevTool/Tui/ToolchainScreen.cs deleted file mode 100644 index 28bfa0f..0000000 --- a/Journal.DevTool/Tui/ToolchainScreen.cs +++ /dev/null @@ -1,347 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Sdt.Runner; -using Sdt.Tui; -using Spectre.Console; - -namespace Sdt.Tui; - -public sealed class ToolchainScreen(DevToolConfig config, string projectRoot) -{ - private readonly DevToolConfig _config = config; - private readonly string _projectRoot = projectRoot; - - public async Task RunAsync() - { - while (true) - { - AnsiConsole.Clear(); - AnsiConsole.Write(Theme.SectionRule("TOOLCHAINS")); - AnsiConsole.WriteLine(); - - var tc = _config.Toolchains; - if (tc is null) - { - AnsiConsole.MarkupLine(Theme.Warn("No toolchains configured in devtool.json.")); - AnsiConsole.MarkupLine(Theme.Faint("Add a \"toolchains\" section with \"python\" and/or \"node\" entries.")); - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back...")); - Console.ReadKey(intercept: true); - return; - } - - // Build menu from available toolchains - var choices = new List(); - - if (tc.Python is not null) - { - choices.Add(new MenuItem($"[bold {Theme.GreenBold}]PYTHON[/]", "__group__")); - choices.Add(new MenuItem($"{Theme.G("Check environment")} {Theme.Faint("detect python, venv, pip")}", "py:check")); - choices.Add(new MenuItem($"{Theme.G("Create / recreate venv")} {Theme.Faint($"python -m venv {tc.Python.VenvDir}")}", "py:venv")); - if (tc.Python.Profiles.Count > 0) - choices.Add(new MenuItem($"{Theme.G("Install requirements profile")} {Theme.Faint("select cpu / gpu / nlp...")}", "py:install")); - choices.Add(new MenuItem($"{Theme.G("Upgrade pip")} {Theme.Faint("pip install --upgrade pip")}", "py:upgradepip")); - } - - if (tc.Node is not null) - { - choices.Add(new MenuItem($"[bold {Theme.GreenBold}]NODE / NPM[/]", "__group__")); - choices.Add(new MenuItem($"{Theme.G("Check environment")} {Theme.Faint("detect node, npm, node_modules")}", "node:check")); - choices.Add(new MenuItem($"{Theme.G($"{tc.Node.PackageManager} install")} {Theme.Faint($"in {tc.Node.WorkingDir}")}", "node:install")); - } - - choices.Add(new MenuItem($"[bold {Theme.GreenBold}]──[/]", "__group__")); - choices.Add(new MenuItem(Theme.Faint("← Back"), "__back__")); - - var prompt = new SelectionPrompt() - .Title($"[{Theme.Green}]Select a toolchain action:[/]") - .PageSize(20) - .UseConverter(m => m.Display) - .AddChoices(choices); - - var selected = AnsiConsole.Prompt(prompt); - if (selected.Value == "__back__" || selected.Value == "__group__") return; - - AnsiConsole.Clear(); - AnsiConsole.Write(Theme.SectionRule(selected.Value.Split(':')[0].ToUpperInvariant() + " › " + selected.Value.Split(':')[1])); - AnsiConsole.WriteLine(); - - await HandleActionAsync(selected.Value, tc); - - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to continue...")); - Console.ReadKey(intercept: true); - } - } - - // ── Actions ─────────────────────────────────────────────────────────────── - - private async Task HandleActionAsync(string action, ToolchainConfig tc) - { - switch (action) - { - case "py:check": await CheckPythonAsync(tc.Python!); break; - case "py:venv": await CreateVenvAsync(tc.Python!); break; - case "py:install": await InstallProfileAsync(tc.Python!); break; - case "py:upgradepip": await UpgradePipAsync(tc.Python!); break; - case "node:check": await CheckNodeAsync(tc.Node!); break; - case "node:install": await NodeInstallAsync(tc.Node!); break; - } - } - - // ── Python ──────────────────────────────────────────────────────────────── - - private async Task CheckPythonAsync(PythonToolchain py) - { - var exe = ResolvePythonExe(py); - var venvPath = Path.GetFullPath(Path.Combine(_projectRoot, py.VenvDir)); - var venvPython = GetVenvPython(venvPath); - - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]Check[/]").Width(24)) - .AddColumn(new TableColumn($"[{Theme.GreenBold}]Result[/]")); - - // System Python - var pyVersion = await ProbeAsync(exe, "--version"); - table.AddRow(Theme.G("System Python"), pyVersion is not null - ? Theme.Ok(pyVersion.Trim()) - : Theme.Fail($"{exe} not found")); - - // Venv exists? - table.AddRow(Theme.G($"Venv ({py.VenvDir})"), Directory.Exists(venvPath) - ? Theme.Ok("exists " + venvPath) - : Theme.Warn("not found — use 'Create venv'")); - - // Venv Python - if (File.Exists(venvPython)) - { - var venvVersion = await ProbeAsync(venvPython, "--version"); - table.AddRow(Theme.G("Venv Python"), venvVersion is not null - ? Theme.Ok(venvVersion.Trim()) - : Theme.Fail("could not launch")); - } - - // Pip in venv - if (File.Exists(venvPython)) - { - var pipVersion = await ProbeAsync(venvPython, "-m", "pip", "--version"); - table.AddRow(Theme.G("Pip (venv)"), pipVersion is not null - ? Theme.Ok(pipVersion.Trim()) - : Theme.Fail("pip not available")); - } - - AnsiConsole.Write(table); - } - - private async Task CreateVenvAsync(PythonToolchain py) - { - var exe = ResolvePythonExe(py); - var venvDir = py.VenvDir; - var venvPath = Path.GetFullPath(Path.Combine(_projectRoot, venvDir)); - - if (Directory.Exists(venvPath)) - { - var overwrite = AnsiConsole.Confirm( - $"[{Theme.Amber}]Venv already exists at {venvDir}. Recreate it?[/]", defaultValue: false); - if (!overwrite) return; - Directory.Delete(venvPath, recursive: true); - } - - AnsiConsole.MarkupLine(Theme.G($"Creating venv: {exe} -m venv {venvDir}")); - AnsiConsole.WriteLine(); - - await RunLiveAsync(exe, ["-m", "venv", venvDir], _projectRoot); - } - - private async Task InstallProfileAsync(PythonToolchain py) - { - var venvPath = Path.GetFullPath(Path.Combine(_projectRoot, py.VenvDir)); - var venvPy = GetVenvPython(venvPath); - - if (!File.Exists(venvPy)) - { - AnsiConsole.MarkupLine(Theme.Warn("Venv not found. Create it first.")); - return; - } - - var profile = AnsiConsole.Prompt( - new SelectionPrompt() - .Title($"[{Theme.Green}]Select requirements profile:[/]") - .UseConverter(p => $"{Theme.Bold(p.Label)} {Theme.Faint(p.RequirementsFile)}") - .AddChoices(py.Profiles)); - - var reqFile = Path.GetFullPath(Path.Combine(_projectRoot, profile.RequirementsFile)); - if (!File.Exists(reqFile)) - { - AnsiConsole.MarkupLine(Theme.Fail($"Requirements file not found: {reqFile}")); - return; - } - - // Upgrade pip first - AnsiConsole.MarkupLine(Theme.Faint("Upgrading pip...")); - await RunPipAsync(py, venvPy, ["install", "--upgrade", "pip"]); - - // Build install args - var installArgs = new List { "-m", "pip", "install" }; - if (!string.IsNullOrWhiteSpace(profile.ExtraIndexUrl)) - { - installArgs.Add("--extra-index-url"); - installArgs.Add(profile.ExtraIndexUrl); - } - installArgs.Add("-r"); - installArgs.Add(reqFile); - - AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine(Theme.G($"Installing {profile.Label}...")); - AnsiConsole.WriteLine(); - await RunPipAsync(py, venvPy, installArgs.Skip(2)); // strip leading "-m pip" - - // Post-install commands - foreach (var cmd in profile.PostInstallCommands) - { - AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine(Theme.Faint($"Post-install: {cmd}")); - var parts = cmd.Split(' ', 2); - var postArgs = parts.Length > 1 ? parts[1].Split(' ') : Array.Empty(); - await RunLiveAsync(venvPy, ["-m", .. postArgs], _projectRoot); - } - } - - private async Task UpgradePipAsync(PythonToolchain py) - { - var venvPath = Path.GetFullPath(Path.Combine(_projectRoot, py.VenvDir)); - var venvPy = GetVenvPython(venvPath); - var exe = File.Exists(venvPy) ? venvPy : ResolvePythonExe(py); - AnsiConsole.MarkupLine(Theme.G($"Upgrading pip using: {exe}")); - AnsiConsole.WriteLine(); - await RunPipAsync(py, exe, ["install", "--upgrade", "pip"]); - } - - // ── Node ────────────────────────────────────────────────────────────────── - - private async Task CheckNodeAsync(NodeToolchain node) - { - var nodeModules = Path.GetFullPath( - Path.Combine(_projectRoot, node.WorkingDir, "node_modules")); - - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]Check[/]").Width(24)) - .AddColumn(new TableColumn($"[{Theme.GreenBold}]Result[/]")); - - var nodeVersion = await ProbeAsync("node", "--version"); - table.AddRow(Theme.G("Node.js"), nodeVersion is not null - ? Theme.Ok(nodeVersion.Trim()) - : Theme.Fail("node not found in PATH")); - - var npmVersion = await ProbeAsync(node.PackageManager, "--version"); - table.AddRow(Theme.G(node.PackageManager), npmVersion is not null - ? Theme.Ok(npmVersion.Trim()) - : Theme.Fail($"{node.PackageManager} not found in PATH")); - - table.AddRow(Theme.G("node_modules"), Directory.Exists(nodeModules) - ? Theme.Ok("exists") - : Theme.Warn($"not found — run {node.PackageManager} install")); - - AnsiConsole.Write(table); - } - - private async Task NodeInstallAsync(NodeToolchain node) - { - var workDir = Path.GetFullPath(Path.Combine(_projectRoot, node.WorkingDir)); - AnsiConsole.MarkupLine(Theme.G($"{node.PackageManager} install ({workDir})")); - AnsiConsole.WriteLine(); - await RunLiveAsync(node.PackageManager, ["install"], workDir); - } - - // ── Helpers ─────────────────────────────────────────────────────────────── - - private static string ResolvePythonExe(PythonToolchain py) - { - if (OperatingSystem.IsWindows() && !string.IsNullOrWhiteSpace(py.WindowsExecutable)) - return py.WindowsExecutable; - return py.Executable; - } - - private static string GetVenvPython(string venvPath) - { - // Windows: .venv\Scripts\python.exe | Linux/Mac: .venv/bin/python - return OperatingSystem.IsWindows() - ? Path.Combine(venvPath, "Scripts", "python.exe") - : Path.Combine(venvPath, "bin", "python"); - } - - private static async Task ProbeAsync(string command, params string[] args) - { - try - { - var psi = new System.Diagnostics.ProcessStartInfo - { - FileName = CommandResolver.Resolve(command), - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - foreach (var a in args) psi.ArgumentList.Add(a); - - using var p = new System.Diagnostics.Process { StartInfo = psi }; - p.Start(); - var output = await p.StandardOutput.ReadToEndAsync(); - var err = await p.StandardError.ReadToEndAsync(); - await p.WaitForExitAsync(); - return p.ExitCode == 0 ? (output + err) : null; - } - catch { return null; } - } - - private static async Task RunLiveAsync(string command, IEnumerable args, string workingDir) - { - var result = await ProcessRunner.RunAsync( - command, args, workingDir, - (line, isErr) => AnsiConsole.MarkupLine( - isErr - ? $"[{Theme.Amber}]{Markup.Escape(line)}[/]" - : $"[{Theme.Green}]{Markup.Escape(line)}[/]")); - - AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine(result.Success - ? Theme.Ok($"Done ({result.Elapsed.TotalSeconds:F1}s)") - : Theme.Fail($"Exited {result.ExitCode} ({result.Elapsed.TotalSeconds:F1}s)")); - } - - private async Task RunPipAsync(PythonToolchain py, string pythonExe, IEnumerable pipArgs) - { - if (!string.IsNullOrWhiteSpace(py.PipScript)) - { - var pipScriptPath = ResolvePipScriptPath(py.PipScript); - if (File.Exists(pipScriptPath)) - { - var ext = Path.GetExtension(pipScriptPath).ToLowerInvariant(); - if (ext == ".py") - { - await RunLiveAsync(ResolvePythonExe(py), [pipScriptPath, .. pipArgs], _projectRoot); - return; - } - - AnsiConsole.MarkupLine(Theme.Warn($"Ignoring non-Python pipScript: {pipScriptPath}")); - } - } - - await RunLiveAsync(pythonExe, ["-m", "pip", .. pipArgs], _projectRoot); - } - - private string ResolvePipScriptPath(string pipScriptConfigPath) - { - if (Path.IsPathRooted(pipScriptConfigPath)) - return pipScriptConfigPath; - - var fileName = Path.GetFileName(pipScriptConfigPath); - var bundled = ScriptLocator.FindHelperScript(_projectRoot, fileName); - if (bundled is not null) - return bundled; - - return Path.GetFullPath(Path.Combine(_projectRoot, pipScriptConfigPath)); - } -} diff --git a/Journal.DevTool/Tui/WorkspaceScreen.cs b/Journal.DevTool/Tui/WorkspaceScreen.cs deleted file mode 100644 index 55302cf..0000000 --- a/Journal.DevTool/Tui/WorkspaceScreen.cs +++ /dev/null @@ -1,174 +0,0 @@ -using Sdt.Config; -using Spectre.Console; - -namespace Sdt.Tui; - -public sealed class WorkspaceScreen(WorkspaceConfig workspace, string workspaceRoot, string currentProjectRoot) -{ - private readonly WorkspaceConfig _workspace = workspace; - private readonly string _workspaceRoot = workspaceRoot; - private readonly string _currentProjectRoot = currentProjectRoot; - - /// - /// Shows the project switcher. Returns the absolute path to the selected project root, - /// or null if the user cancelled. - /// - public string? SelectProject() - { - while (true) - { - AnsiConsole.Clear(); - AnsiConsole.Write(Theme.SectionRule("WORKSPACE — " + _workspace.Name)); - AnsiConsole.WriteLine(); - - var projects = _workspace.Projects; - if (projects.Count == 0) - { - AnsiConsole.MarkupLine(Theme.Warn("No projects defined in sdt-workspace.json.")); - AnsiConsole.MarkupLine(Theme.Faint("Add entries to the \"projects\" array.")); - if (AnsiConsole.Confirm($"[{Theme.Amber}]Add an external project now?[/]", defaultValue: true)) - { - AddExternalProject(); - continue; - } - - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back...")); - Console.ReadKey(intercept: true); - return null; - } - - // Build choice list with current project marked - var choices = new List(); - foreach (var proj in projects) - { - var absPath = WorkspaceLoader.ResolveProjectRoot(_workspaceRoot, proj); - var devtoolPath = Path.Combine(absPath, "devtool.json"); - var isCurrent = string.Equals(absPath, _currentProjectRoot, StringComparison.OrdinalIgnoreCase); - var exists = File.Exists(devtoolPath); - - var label = isCurrent - ? $"[bold {Theme.GreenBold}]► {Markup.Escape(proj.Name)}[/] [{Theme.GreenDim}](current)[/]" - : $"[{Theme.Green}] {Markup.Escape(proj.Name)}[/]"; - if (proj.Disabled) - label += $" [{Theme.Amber}](disabled)[/]"; - - var desc = !exists - ? $" [{Theme.Red}]devtool.json not found[/]" - : string.IsNullOrWhiteSpace(proj.Description) - ? $" [{Theme.GreenDim}]{Markup.Escape(absPath)}[/]" - : $" [{Theme.GreenDim}]{Markup.Escape(proj.Description)}[/]"; - if (proj.Tags.Count > 0) - desc += $" [{Theme.GreenDim}]tags: {Markup.Escape(string.Join(",", proj.Tags))}[/]"; - - choices.Add(new WorkspaceMenuItem(label + "\n" + desc, absPath, exists && !isCurrent && !proj.Disabled)); - } - - choices.Add(new WorkspaceMenuItem($"[{Theme.Green}]+ Add external project[/]", "__add__", true)); - choices.Add(new WorkspaceMenuItem($"[{Theme.GreenDim}]← Cancel[/]", null, true)); - - // Show project table for overview - var table = new Table() - .Border(TableBorder.Rounded) - .BorderStyle(Theme.DimStyle) - .AddColumn(new TableColumn($"[{Theme.Amber}]Project[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Path[/]")) - .AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(12)); - - foreach (var proj in projects) - { - var absPath = WorkspaceLoader.ResolveProjectRoot(_workspaceRoot, proj); - var isCurrent = string.Equals(absPath, _currentProjectRoot, StringComparison.OrdinalIgnoreCase); - var hasConfig = File.Exists(Path.Combine(absPath, "devtool.json")); - var status = proj.Disabled - ? Theme.Warn("disabled") - : hasConfig ? Theme.Ok("ready") : Theme.Fail("no config"); - - table.AddRow( - isCurrent - ? $"[bold {Theme.GreenBold}]► {Markup.Escape(proj.Name)}[/]" - : Theme.G(proj.Name), - Theme.Faint(proj.Path), - status); - } - - AnsiConsole.Write(table); - AnsiConsole.WriteLine(); - - var switchable = choices.Where(c => c.Selectable).ToList(); - if (switchable.Count == 1) // only Cancel - { - AnsiConsole.MarkupLine(Theme.Warn("No other projects available to switch to.")); - AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back...")); - Console.ReadKey(intercept: true); - return null; - } - - var selected = AnsiConsole.Prompt( - new SelectionPrompt() - .Title($"[{Theme.Green}]Switch to project:[/]") - .PageSize(15) - .UseConverter(m => m.Display) - .AddChoices(switchable)); - - if (selected.AbsPath == "__add__") - { - AddExternalProject(); - continue; - } - - return selected.AbsPath; // null = cancelled - } - } - - private void AddExternalProject() - { - var raw = AnsiConsole.Ask($"[{Theme.Amber}]Project root path[/]"); - if (string.IsNullOrWhiteSpace(raw)) - return; - - var absolutePath = Path.GetFullPath(raw.Trim()); - if (!Directory.Exists(absolutePath)) - { - AnsiConsole.MarkupLine(Theme.Fail("Directory does not exist.")); - Thread.Sleep(700); - return; - } - - var configPath = Path.Combine(absolutePath, "devtool.json"); - if (!File.Exists(configPath)) - { - var create = AnsiConsole.Confirm( - $"[{Theme.Amber}]No devtool.json found. Create a minimal template?[/]", - defaultValue: true); - if (!create) - return; - - File.WriteAllText(configPath, "{\n \"name\": \"SDT Project\",\n \"version\": \"0.1.0\",\n \"workflows\": []\n}\n"); - } - - if (_workspace.Projects.Any(p => - string.Equals(WorkspaceLoader.ResolveProjectRoot(_workspaceRoot, p), absolutePath, StringComparison.OrdinalIgnoreCase))) - { - AnsiConsole.MarkupLine(Theme.Warn("Project already exists in workspace.")); - Thread.Sleep(700); - return; - } - - var relativePath = Path.GetRelativePath(_workspaceRoot, absolutePath); - var useRelative = !relativePath.StartsWith("..", StringComparison.OrdinalIgnoreCase) && !Path.IsPathRooted(relativePath); - var projectEntry = new WorkspaceProject - { - Name = new DirectoryInfo(absolutePath).Name, - Description = $"External project at {absolutePath}", - Path = useRelative ? relativePath : absolutePath, - Disabled = false, - }; - - _workspace.Projects.Add(projectEntry); - WorkspaceLoader.Save(_workspaceRoot, _workspace); - AnsiConsole.MarkupLine(Theme.Ok("Project added to workspace.")); - Thread.Sleep(700); - } - - private sealed record WorkspaceMenuItem(string Display, string? AbsPath, bool Selectable); -} diff --git a/Journal.DevTool/package-lock.json b/Journal.DevTool/package-lock.json deleted file mode 100644 index 0e746ed..0000000 --- a/Journal.DevTool/package-lock.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "DevTool-master", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "tauri-plugin-mic-recorder-api": "^2.0.0" - } - }, - "node_modules/@tauri-apps/api": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", - "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/tauri-plugin-mic-recorder-api": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tauri-plugin-mic-recorder-api/-/tauri-plugin-mic-recorder-api-2.0.0.tgz", - "integrity": "sha512-04wqYCX4WIlYd6KUY7aS3+W4B5RtnSoVczaQCBSXKpQkEx9XdaaBN05XCee2unxGva0btSXBItFqQSdosnS4jQ==", - "license": "MIT", - "dependencies": { - "@tauri-apps/api": ">=2.0.0-beta.6" - } - } - } -} diff --git a/Journal.DevTool/package.json b/Journal.DevTool/package.json deleted file mode 100644 index 59d0978..0000000 --- a/Journal.DevTool/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "tauri-plugin-mic-recorder-api": "^2.0.0" - } -} diff --git a/Journal.DevTool/scripts/README.md b/Journal.DevTool/scripts/README.md deleted file mode 100644 index 8a7d099..0000000 --- a/Journal.DevTool/scripts/README.md +++ /dev/null @@ -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 -``` diff --git a/Journal.DevTool/scripts/WORKFLOWS.md b/Journal.DevTool/scripts/WORKFLOWS.md deleted file mode 100644 index 67852d2..0000000 --- a/Journal.DevTool/scripts/WORKFLOWS.md +++ /dev/null @@ -1,57 +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 -``` - -## 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 -``` diff --git a/Journal.DevTool/scripts/_pwsh-python-shim.ps1 b/Journal.DevTool/scripts/_pwsh-python-shim.ps1 deleted file mode 100644 index b07c4dc..0000000 --- a/Journal.DevTool/scripts/_pwsh-python-shim.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -function Resolve-SdtPython { - $candidates = @('python') - if ($IsWindows) { $candidates += 'py' } else { $candidates += 'python3' } - foreach ($c in $candidates) { - try { - & $c --version *> $null - if ($LASTEXITCODE -eq 0) { return $c } - } catch {} - } - return 'python' -} - -function Resolve-SdtScriptPath { - param([Parameter(Mandatory=$true)][string]$ScriptName) - - $bundled = Join-Path $PSScriptRoot $ScriptName - if (Test-Path $bundled) { return $bundled } - - $project = Join-Path (Join-Path $PSScriptRoot '..') ('scripts\\' + $ScriptName) - if (Test-Path $project) { return (Resolve-Path $project).Path } - - throw "Python helper script not found: $ScriptName" -} - -function Invoke-SdtPythonScript { - param( - [Parameter(Mandatory=$true)][string]$ScriptName, - [string[]]$ForwardArgs = @() - ) - - $python = Resolve-SdtPython - $scriptPath = Resolve-SdtScriptPath -ScriptName $ScriptName - - & $python $scriptPath @ForwardArgs - exit $LASTEXITCODE -} diff --git a/Journal.DevTool/scripts/build.py b/Journal.DevTool/scripts/build.py deleted file mode 100644 index d71b5cc..0000000 --- a/Journal.DevTool/scripts/build.py +++ /dev/null @@ -1,419 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import hashlib -import json -import os -import pathlib -import shutil -import subprocess -import sys -import time -from script_common import resolve_command - - -def run_step(command, args, cwd): - resolved = resolve_command(command) - if shutil.which(resolved) is None and not pathlib.Path(resolved).exists(): - return { - "command": resolved, - "args": args, - "cwd": cwd, - "exit_code": 127, - "elapsed_seconds": 0.0, - "status": "failed", - "failure_reason": f"command_not_found:{resolved}", - } - - started = time.time() - proc = subprocess.run([resolved, *args], cwd=cwd, check=False) - elapsed = round(time.time() - started, 3) - return { - "command": resolved, - "args": args, - "cwd": cwd, - "exit_code": proc.returncode, - "elapsed_seconds": elapsed, - "status": "ok" if proc.returncode == 0 else "failed", - "failure_reason": None if proc.returncode == 0 else f"non_zero_exit:{proc.returncode}", - } - - -def resolve_python_executable(): - candidates = ["py", "python"] if os.name == "nt" else ["python3", "python"] - for c in candidates: - if shutil.which(c): - return c - return "python" - - -def parse_common(parser): - parser.add_argument("--project-root", required=True) - parser.add_argument("--working-dir", default=".") - parser.add_argument("--json", action="store_true") - - -def resolve_cwd(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"} - - -def discover_dotnet_target(project_root: str, cwd: str): - # Prefer nearest/top-level solution from cwd, then csproj, then bounded scan from project root. - local_sln = sorted(pathlib.Path(cwd).glob("*.sln")) - if len(local_sln) == 1: - return str(local_sln[0]) - - local_csproj = sorted(pathlib.Path(cwd).glob("*.csproj")) - if len(local_csproj) == 1: - return str(local_csproj[0]) - - sln_hits = bounded_find_files(project_root, ".sln", max_depth=4) - if len(sln_hits) == 1: - return sln_hits[0] - - csproj_hits = bounded_find_files(project_root, ".csproj", max_depth=4) - if len(csproj_hits) == 1: - return csproj_hits[0] - - return None - - -def bounded_find_files(root: str, extension: str, max_depth: int): - root_path = pathlib.Path(root).resolve() - results = [] - for current_root, dirs, files in os.walk(root_path): - rel = pathlib.Path(current_root).resolve().relative_to(root_path) - depth = len(rel.parts) - dirs[:] = [d for d in dirs if d not in EXCLUDED_SCAN_DIRS] - if depth > max_depth: - dirs[:] = [] - continue - - for name in files: - if name.lower().endswith(extension.lower()): - results.append(str(pathlib.Path(current_root) / name)) - return sorted(results) - - -def run_dotnet_action(project_root, working_dir, verb): - cwd = resolve_cwd(project_root, working_dir) - args = [verb] - target = discover_dotnet_target(project_root, cwd) - if target: - args.append(target) - step = run_step("dotnet", args, cwd) - if target: - step["resolved_target"] = target - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def _deps_hash(app_root): - h = hashlib.sha256() - for name in ("package.json", "package-lock.json"): - p = pathlib.Path(app_root) / name - if p.exists(): - h.update(p.read_bytes()) - return h.hexdigest() - - -def ensure_npm_dependencies(app_root): - node_modules = pathlib.Path(app_root) / "node_modules" - deps_hash_file = node_modules / ".sdt-deps.sha256" - expected = _deps_hash(app_root) - - should_install = not node_modules.exists() - if not should_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 - - if not should_install: - return {"installed": False, "reason": "deps_unchanged"} - - lock_exists = (pathlib.Path(app_root) / "package-lock.json").exists() - install_args = ["ci", "--no-audit", "--fund=false"] if lock_exists else ["install", "--no-audit", "--fund=false"] - install_step = run_step("npm", install_args, app_root) - if install_step["exit_code"] != 0: - if lock_exists and install_args[0] == "ci": - fallback = run_step("npm", ["install", "--no-audit", "--fund=false"], app_root) - if fallback["exit_code"] != 0: - fallback["failure_reason"] = "deps_install_failed_after_ci_fallback" - return {"installed": True, "reason": "install_failed", "step": fallback} - install_step = fallback - else: - return {"installed": True, "reason": "install_failed", "step": install_step} - - node_modules.mkdir(parents=True, exist_ok=True) - deps_hash_file.write_text(expected, encoding="utf-8") - return {"installed": True, "reason": "installed", "step": install_step} - - -def action_dotnet_build(args): - return run_dotnet_action(args.project_root, args.working_dir, "build") - - -def action_dotnet_restore(args): - return run_dotnet_action(args.project_root, args.working_dir, "restore") - - -def action_dotnet_test(args): - return run_dotnet_action(args.project_root, args.working_dir, "test") - - -def action_dotnet_publish(args): - return run_dotnet_action(args.project_root, args.working_dir, "publish") - - -def action_npm_install(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("npm", ["install"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_npm_ci(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("npm", ["ci"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_npm_build(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - deps = ensure_npm_dependencies(cwd) - if deps.get("reason") == "install_failed": - step = deps["step"] - step["failure_reason"] = "deps_install_failed" - return step["exit_code"], step - step = run_step("npm", ["run", "build"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_npm_test(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - deps = ensure_npm_dependencies(cwd) - if deps.get("reason") == "install_failed": - step = deps["step"] - step["failure_reason"] = "deps_install_failed" - return step["exit_code"], step - step = run_step("npm", ["test"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_npm_audit(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("npm", ["audit"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_python_venv_create(args): - cwd = resolve_cwd(args.project_root, ".") - venv_dir = args.venv_dir or ".venv" - step = run_step(resolve_python_executable(), ["-m", "venv", venv_dir], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_python_pip_install(args): - cwd = resolve_cwd(args.project_root, ".") - req = args.requirements - step = run_step(resolve_python_executable(), ["-m", "pip", "install", "-r", req], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_python_pip_sync(args): - cwd = resolve_cwd(args.project_root, ".") - req = args.requirements - step = run_step(resolve_python_executable(), ["-m", "pip", "install", "-r", req], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_python_pytest(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step(resolve_python_executable(), ["-m", "pytest"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_cargo_build(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("cargo", ["build"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_cargo_test(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("cargo", ["test"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_tauri_build(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - deps = ensure_npm_dependencies(cwd) - if deps.get("reason") == "install_failed": - step = deps["step"] - step["failure_reason"] = "deps_install_failed" - return step["exit_code"], step - - tauri_args = ["run", "tauri", "build"] - if args.no_bundle: - tauri_args.extend(["--", "--no-bundle"]) - step = run_step("npm", tauri_args, cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_git_status(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("git", ["status"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_git_fetch(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("git", ["fetch"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_git_pull(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("git", ["pull"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_git_clean(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("git", ["clean", "-fd"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_docker_build(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("docker", ["build", "."], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_docker_compose_up(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("docker", ["compose", "up", "-d"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def action_docker_compose_down(args): - cwd = resolve_cwd(args.project_root, args.working_dir) - step = run_step("docker", ["compose", "down"], cwd) - return 0 if step["exit_code"] == 0 else step["exit_code"], step - - -def main(): - parser = argparse.ArgumentParser(description="SDT normalized build actions") - sub = parser.add_subparsers(dest="action", required=True) - - p0 = sub.add_parser("dotnet-restore") - parse_common(p0) - - p1 = sub.add_parser("dotnet-build") - parse_common(p1) - - p1b = sub.add_parser("dotnet-test") - parse_common(p1b) - - p1c = sub.add_parser("dotnet-publish") - parse_common(p1c) - - p2 = sub.add_parser("npm-install") - parse_common(p2) - - p2b = sub.add_parser("npm-ci") - parse_common(p2b) - - p3 = sub.add_parser("npm-build") - parse_common(p3) - - p3b = sub.add_parser("npm-test") - parse_common(p3b) - - p3c = sub.add_parser("npm-audit") - parse_common(p3c) - - 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() - - handlers = { - "dotnet-restore": action_dotnet_restore, - "dotnet-build": action_dotnet_build, - "dotnet-test": action_dotnet_test, - "dotnet-publish": action_dotnet_publish, - "npm-install": action_npm_install, - "npm-ci": action_npm_ci, - "npm-build": action_npm_build, - "npm-test": action_npm_test, - "npm-audit": action_npm_audit, - "python-venv-create": action_python_venv_create, - "python-pip-install": action_python_pip_install, - "python-pip-sync": action_python_pip_sync, - "python-pytest": action_python_pytest, - "cargo-build": action_cargo_build, - "cargo-test": action_cargo_test, - "tauri-build": action_tauri_build, - "git-status": action_git_status, - "git-fetch": action_git_fetch, - "git-pull": action_git_pull, - "git-clean": action_git_clean, - "docker-build": action_docker_build, - "docker-compose-up": action_docker_compose_up, - "docker-compose-down": action_docker_compose_down, - } - - code, summary = handlers[args.action](args) - if args.json: - print(json.dumps(summary)) - return code - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Journal.DevTool/scripts/dev-shell.cmd b/Journal.DevTool/scripts/dev-shell.cmd deleted file mode 100644 index b1614b7..0000000 --- a/Journal.DevTool/scripts/dev-shell.cmd +++ /dev/null @@ -1,17 +0,0 @@ -@echo off -set "SCRIPT_DIR=%~dp0" - -where py >nul 2>nul -if %ERRORLEVEL%==0 ( - set "PYEXE=py" -) else ( - where python >nul 2>nul - if not %ERRORLEVEL%==0 ( - echo python not found. - exit /b 1 - ) - set "PYEXE=python" -) - -for /f "usebackq delims=" %%L in (`"%PYEXE%" "%SCRIPT_DIR%dev_shell.py" export --shell cmd`) do %%L -echo Development shell initialized from Python bootstrap script. diff --git a/Journal.DevTool/scripts/dev-shell.ps1 b/Journal.DevTool/scripts/dev-shell.ps1 deleted file mode 100644 index 7f4a3f7..0000000 --- a/Journal.DevTool/scripts/dev-shell.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -# Run this in PowerShell before development commands: -# . ./scripts/dev-shell.ps1 - -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" - -. (Join-Path $PSScriptRoot '_pwsh-python-shim.ps1') - -$scriptPath = Resolve-SdtScriptPath -ScriptName 'dev_shell.py' -$python = Resolve-SdtPython - -$lines = & $python $scriptPath export --shell pwsh -if ($LASTEXITCODE -ne 0) { - throw "Failed to initialize development shell via dev_shell.py" -} - -foreach ($line in $lines) { - Invoke-Expression $line -} - -Write-Host "Development shell initialized from Python bootstrap script." diff --git a/Journal.DevTool/scripts/dev-shell.sh b/Journal.DevTool/scripts/dev-shell.sh deleted file mode 100644 index 83468f7..0000000 --- a/Journal.DevTool/scripts/dev-shell.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env sh -set -eu - -SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" - -if command -v python3 >/dev/null 2>&1; then - PYTHON_EXE="python3" -elif command -v python >/dev/null 2>&1; then - PYTHON_EXE="python" -else - echo "python3/python not found." >&2 - exit 1 -fi - -eval "$("$PYTHON_EXE" "$SCRIPT_DIR/dev_shell.py" export --shell bash)" -echo "Development shell initialized from Python bootstrap script." diff --git a/Journal.DevTool/scripts/dev_shell.py b/Journal.DevTool/scripts/dev_shell.py deleted file mode 100644 index 1a5d8ea..0000000 --- a/Journal.DevTool/scripts/dev_shell.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import json -import pathlib -import sys - -from script_common import PROXY_VARS, clean_proxy_env, dotnet_env, ensure_dirs, pip_env, resolve_repo_root - - -def huggingface_env(repo_root: pathlib.Path) -> dict[str, str]: - env = {} - hf_home = repo_root / ".cache" / "huggingface" - hf_hub_cache = hf_home / "hub" - ensure_dirs([hf_hub_cache]) - env["HF_HOME"] = str(hf_home) - env["HUGGINGFACE_HUB_CACHE"] = str(hf_hub_cache) - env["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1" - return env - - -def resolved_env(repo_root: pathlib.Path) -> dict[str, str]: - env = {} - dotnet = dotnet_env(repo_root) - pip = pip_env(repo_root) - hf = huggingface_env(repo_root) - - dotnet_keys = [ - "DOTNET_CLI_HOME", - "NUGET_PACKAGES", - "NUGET_HTTP_CACHE_PATH", - "DOTNET_SKIP_FIRST_TIME_EXPERIENCE", - "DOTNET_ADD_GLOBAL_TOOLS_TO_PATH", - "DOTNET_GENERATE_ASPNET_CERTIFICATE", - "DOTNET_CLI_TELEMETRY_OPTOUT", - "NUGET_CERT_REVOCATION_MODE", - ] - pip_keys = [ - "PIP_CACHE_DIR", - "PIP_DISABLE_PIP_VERSION_CHECK", - "PIP_DEFAULT_TIMEOUT", - "PIP_RETRIES", - "TEMP", - "TMP", - ] - for key in dotnet_keys: - env[key] = dotnet[key] - for key in pip_keys: - env[key] = pip[key] - env.update(hf) - clean_proxy_env(env) - return env - - -def export_lines(shell: str, env_map: dict[str, str]) -> list[str]: - def sh_quote(value: str) -> str: - return "'" + value.replace("'", "'\"'\"'") + "'" - - if shell == "pwsh": - lines = [f"Remove-Item Env:{k} -ErrorAction SilentlyContinue" for k in PROXY_VARS] - lines.extend(f"$env:{k} = \"{v.replace('\"', '`\"')}\"" for k, v in env_map.items()) - return lines - if shell in ("bash", "zsh"): - lines = [f"unset {k}" for k in PROXY_VARS] - lines.extend(f"export {k}={sh_quote(v)}" for k, v in env_map.items()) - return lines - if shell == "cmd": - lines = [f"set {k}=" for k in PROXY_VARS] - lines.extend(f"set {k}={v}" for k, v in env_map.items()) - return lines - raise ValueError(shell) - - -def cmd_export(args): - try: - repo_root = resolve_repo_root(args.project_root) - except Exception as ex: - print(f"Failed to resolve project root: {ex}", file=sys.stderr) - return 2 - - env_map = resolved_env(repo_root) - payload = { - "projectRoot": str(repo_root), - "env": env_map, - "createdDirs": [ - str(repo_root / ".dotnet_home"), - str(repo_root / ".nuget" / "packages"), - str(repo_root / ".nuget" / "http-cache"), - str(repo_root / ".pip" / "cache"), - str(repo_root / ".tmp" / "pip-temp"), - str(repo_root / ".cache" / "huggingface" / "hub"), - ], - "warnings": [], - } - - try: - lines = export_lines(args.shell, env_map) - except ValueError: - print(f"Unsupported shell target: {args.shell}", file=sys.stderr) - return 3 - - if args.json: - print(json.dumps(payload)) - else: - for line in lines: - print(line) - return 0 - - -def cmd_doctor(args): - try: - repo_root = resolve_repo_root(args.project_root) - except Exception as ex: - print(f"Failed to resolve project root: {ex}", file=sys.stderr) - return 2 - - env_map = resolved_env(repo_root) - checks = { - "repo_root": str(repo_root), - "dotnet_home_exists": (repo_root / ".dotnet_home").exists(), - "nuget_cache_exists": (repo_root / ".nuget" / "packages").exists(), - "pip_cache_exists": (repo_root / ".pip" / "cache").exists(), - "hf_cache_exists": (repo_root / ".cache" / "huggingface" / "hub").exists(), - "env_count": len(env_map), - } - print(json.dumps(checks)) - return 0 - - -def main(): - parser = argparse.ArgumentParser(description="SDT cross-platform shell bootstrap helper") - sub = parser.add_subparsers(dest="command", required=True) - - p_export = sub.add_parser("export", help="Print env exports for a shell") - p_export.add_argument("--shell", required=True) - p_export.add_argument("--project-root") - p_export.add_argument("--json", action="store_true") - - p_doctor = sub.add_parser("doctor", help="Validate env bootstrap paths") - p_doctor.add_argument("--project-root") - - args = parser.parse_args() - if args.command == "export": - return cmd_export(args) - return cmd_doctor(args) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Journal.DevTool/scripts/diag.py b/Journal.DevTool/scripts/diag.py deleted file mode 100644 index 20bf41b..0000000 --- a/Journal.DevTool/scripts/diag.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import json -import os -import platform -import shutil -import subprocess -import sys -from script_common import resolve_command - - -def run_capture(cmd): - try: - proc = subprocess.run(cmd, capture_output=True, text=True, check=False) - out = (proc.stdout or "").strip() - err = (proc.stderr or "").strip() - text = out if out else err - return proc.returncode == 0, text - except Exception as ex: - return False, str(ex) - - -def probe_tool(tool): - mapping = { - "dotnet": ["dotnet", "--version"], - "node": ["node", "--version"], - "npm": ["npm", "--version"], - "python": ["python", "--version"], - "cargo": ["cargo", "--version"], - "tauri": ["tauri", "--version"], - "git": ["git", "--version"], - "docker": ["docker", "--version"], - } - cmd = mapping.get(tool, [tool, "--version"]) - resolved = resolve_command(cmd[0]) - if shutil.which(resolved) is None and not os.path.exists(resolved): - return {"tool": tool, "available": False, "version": None, "details": f"{cmd[0]} not found in PATH"} - cmd = [resolved, *cmd[1:]] - ok, text = run_capture(cmd) - return {"tool": tool, "available": ok, "version": text if ok else None, "details": None if ok else text} - - -def install_plan(tool): - is_windows = platform.system().lower().startswith("win") - if is_windows: - plans = { - "dotnet": [("winget", ["install", "Microsoft.DotNet.SDK.10"])], - "node": [("winget", ["install", "OpenJS.NodeJS.LTS"])], - "npm": [("winget", ["install", "OpenJS.NodeJS.LTS"])], - "python": [("winget", ["install", "Python.Python.3.12"])], - "cargo": [("winget", ["install", "Rustlang.Rustup"])], - "tauri": [("npm", ["install", "-g", "@tauri-apps/cli"])], - "git": [("winget", ["install", "Git.Git"])], - "docker": [("winget", ["install", "Docker.DockerDesktop"])], - } - else: - plans = { - "dotnet": [("sh", ["-c", "echo install dotnet sdk with your package manager"])], - "node": [("sh", ["-c", "echo install nodejs with your package manager"])], - "npm": [("sh", ["-c", "echo install npm with your package manager"])], - "python": [("sh", ["-c", "echo install python3 with your package manager"])], - "cargo": [("sh", ["-c", "curl https://sh.rustup.rs -sSf | sh"])], - "tauri": [("npm", ["install", "-g", "@tauri-apps/cli"])], - "git": [("sh", ["-c", "echo install git with your package manager"])], - "docker": [("sh", ["-c", "echo install docker with your package manager"])], - } - - cmds = plans.get(tool, []) - return { - "tool": tool, - "supported": len(cmds) > 0, - "summary": f"Install plan for {tool} on {platform.system()}", - "commands": [{"command": c, "args": a} for c, a in cmds], - } - - -def run_install(tool): - plan = install_plan(tool) - if not plan["supported"]: - return 2 - for cmd in plan["commands"]: - proc = subprocess.run([cmd["command"], *cmd["args"]], check=False) - if proc.returncode != 0: - return proc.returncode - return 0 - - -def main(): - parser = argparse.ArgumentParser(description="SDT diagnostics and install planner") - sub = parser.add_subparsers(dest="cmd", required=True) - - p_probe = sub.add_parser("probe") - p_probe.add_argument("--tool", required=True) - p_probe.add_argument("--json", action="store_true") - - p_plan = sub.add_parser("install-plan") - p_plan.add_argument("--tool", required=True) - p_plan.add_argument("--json", action="store_true") - - p_run = sub.add_parser("install-run") - p_run.add_argument("--tool", required=True) - - args = parser.parse_args() - - if args.cmd == "probe": - result = probe_tool(args.tool.lower()) - if args.json: - print(json.dumps(result)) - else: - print(result) - return 0 if result["available"] else 1 - - if args.cmd == "install-plan": - result = install_plan(args.tool.lower()) - if args.json: - print(json.dumps(result)) - else: - print(result) - return 0 if result["supported"] else 2 - - if args.cmd == "install-run": - return run_install(args.tool.lower()) - - return 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Journal.DevTool/scripts/dotnet-min.py b/Journal.DevTool/scripts/dotnet-min.py deleted file mode 100644 index c8aa0f4..0000000 --- a/Journal.DevTool/scripts/dotnet-min.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import sys - -from script_common import dotnet_env, resolve_repo_root, run - - -DOTNET_SAFE_CMDS = {"restore", "build", "run", "test", "publish", "pack"} - - -def main() -> int: - parser = argparse.ArgumentParser(description="Cross-platform minimal dotnet wrapper") - parser.add_argument("dotnet_args", nargs=argparse.REMAINDER) - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - - if not args.dotnet_args: - print("Usage: python scripts/dotnet-min.py ", file=sys.stderr) - return 2 - - repo_root = resolve_repo_root(args.repo_root) - dotnet_args = list(args.dotnet_args) - cmd = dotnet_args[0].lower() - - if cmd in DOTNET_SAFE_CMDS: - dotnet_args.extend(["-p:RestoreIgnoreFailedSources=true", "-p:NuGetAudit=false"]) - if cmd == "restore": - dotnet_args.append("--ignore-failed-sources") - - return run("dotnet", dotnet_args, repo_root, env=dotnet_env(repo_root)) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/legacy/dotnet-min.legacy.ps1 b/Journal.DevTool/scripts/legacy/dotnet-min.legacy.ps1 deleted file mode 100644 index 9b36e3b..0000000 --- a/Journal.DevTool/scripts/legacy/dotnet-min.legacy.ps1 +++ /dev/null @@ -1,62 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$DotnetArgs -) - -$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path - -# Keep dotnet and NuGet artifacts local to the repo for easy cleanup. -$env:DOTNET_CLI_HOME = Join-Path $repoRoot ".dotnet_home" -$env:NUGET_PACKAGES = Join-Path $repoRoot ".nuget\packages" -$env:NUGET_HTTP_CACHE_PATH = Join-Path $repoRoot ".nuget\http-cache" - -# Keep setup minimal and non-interactive. -$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" - -# Clear proxy env vars for this process. The host machine currently points them -# to 127.0.0.1:9, which breaks NuGet restore. -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 - -# Prefer offline cert revocation checks to reduce flaky TLS behavior on constrained hosts. -$env:NUGET_CERT_REVOCATION_MODE = "offline" - -New-Item -ItemType Directory -Force -Path $env:DOTNET_CLI_HOME | Out-Null -New-Item -ItemType Directory -Force -Path $env:NUGET_PACKAGES | Out-Null -New-Item -ItemType Directory -Force -Path $env:NUGET_HTTP_CACHE_PATH | Out-Null - -if (-not $DotnetArgs -or $DotnetArgs.Count -eq 0) { - Write-Host "Usage: ./scripts/dotnet-min.ps1 " - Write-Host "Example: ./scripts/dotnet-min.ps1 build Journal.Sidecar/Journal.Sidecar.csproj" - exit 2 -} - -$firstArg = $DotnetArgs[0].ToLowerInvariant() -$effectiveArgs = @($DotnetArgs) - -if ($firstArg -in @("restore", "build", "run", "test", "publish", "pack")) { - if (-not ($effectiveArgs -contains "-p:RestoreIgnoreFailedSources=true")) { - $effectiveArgs += "-p:RestoreIgnoreFailedSources=true" - } - if (-not ($effectiveArgs -contains "-p:NuGetAudit=false")) { - $effectiveArgs += "-p:NuGetAudit=false" - } -} - -if ($firstArg -eq "restore") { - if (-not ($effectiveArgs -contains "--ignore-failed-sources")) { - $effectiveArgs += "--ignore-failed-sources" - } -} - -& dotnet @effectiveArgs -exit $LASTEXITCODE diff --git a/Journal.DevTool/scripts/legacy/migration-gate.legacy.ps1 b/Journal.DevTool/scripts/legacy/migration-gate.legacy.ps1 deleted file mode 100644 index c0d2e09..0000000 --- a/Journal.DevTool/scripts/legacy/migration-gate.legacy.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -param( - [switch]$SkipSmoke, - [switch]$SkipApi -) - -$ErrorActionPreference = "Stop" -$repoRoot = Split-Path -Parent $PSScriptRoot -$parityReport = Join-Path $repoRoot "logs\parity_harness_results.json" - -Write-Host "migration-gate: repo root = $repoRoot" - -Push-Location $repoRoot -try { - Write-Host "migration-gate: building sidecar binary..." - & "$repoRoot\scripts\dotnet-min.ps1" build Journal.Sidecar/Journal.Sidecar.csproj - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - if (-not $SkipSmoke) { - Write-Host "migration-gate: running csharp smoke tests..." - & "$repoRoot\scripts\dotnet-min.ps1" run --project Journal.SmokeTests/Journal.SmokeTests.csproj - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - } - else { - Write-Host "migration-gate: skipping smoke tests (--SkipSmoke)." - } - - Write-Host "migration-gate: running parity harness + fixture matrix..." - $testsDir = Join-Path $repoRoot "tests" - if (Test-Path $testsDir) { - $env:PARITY_HARNESS_REPORT = $parityReport - & python -m unittest discover -s tests -p "test_parity_harness.py" -v - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - } - else { - Write-Host "migration-gate: skipping parity harness — tests/ directory not found." - } - - if (-not $SkipApi) { - Write-Host "migration-gate: running API contract tests..." - if (Test-Path $testsDir) { - & python -m unittest discover -s tests -p "test_api_contract.py" -v - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - } - else { - Write-Host "migration-gate: skipping API contract tests — tests/ directory not found." - } - } - else { - Write-Host "migration-gate: skipping API contract tests (--SkipApi)." - } - - Write-Host "migration-gate: PASS" - Write-Host "migration-gate: parity report => $parityReport" -} -finally { - Pop-Location -} diff --git a/Journal.DevTool/scripts/legacy/npm-clean.legacy.ps1 b/Journal.DevTool/scripts/legacy/npm-clean.legacy.ps1 deleted file mode 100644 index ce838cd..0000000 --- a/Journal.DevTool/scripts/legacy/npm-clean.legacy.ps1 +++ /dev/null @@ -1,62 +0,0 @@ -param( - [switch]$RemoveLockfile, - [switch]$Force -) - -$commonScript = Join-Path $PSScriptRoot "script-common.ps1" -if (-not (Test-Path $commonScript)) { - throw "Missing helper script: $commonScript" -} -. $commonScript - -$repoRoot = Resolve-JournalRepoRoot -StartPath $PSScriptRoot -$appRoot = Resolve-JournalAppRoot -RepoRoot $repoRoot - -Write-Host "Cleaning npm artifacts for Journal.App" -ForegroundColor Cyan -Write-Host "Using app root: $appRoot" -ForegroundColor DarkGray - -$processNames = @("node", "journalapp", "tauri") -Get-Process -ErrorAction SilentlyContinue | - Where-Object { $processNames -contains $_.ProcessName } | - ForEach-Object { - try { - Stop-Process -Id $_.Id -Force -ErrorAction Stop - Write-Host "Stopped process: $($_.ProcessName) ($($_.Id))" -ForegroundColor DarkGray - } - catch { - Write-Warning "Failed to stop process $($_.ProcessName) ($($_.Id)): $($_.Exception.Message)" - } - } - -Push-Location $appRoot -try { - $nodeModulesPath = Join-Path $appRoot "node_modules" - $lockfilePath = Join-Path $appRoot "package-lock.json" - - if (Test-Path $nodeModulesPath) { - if (-not $Force) { - Write-Host "Removing node_modules (use -Force to suppress prompt)..." -ForegroundColor Yellow - } - Remove-Item -Recurse -Force $nodeModulesPath - Write-Host "Removed node_modules." -ForegroundColor Green - } - else { - Write-Host "node_modules not found; nothing to remove." -ForegroundColor DarkGray - } - - if ($RemoveLockfile) { - if (Test-Path $lockfilePath) { - Remove-Item -Force $lockfilePath - Write-Host "Removed package-lock.json." -ForegroundColor Green - } - else { - Write-Host "package-lock.json not found; nothing to remove." -ForegroundColor DarkGray - } - } - else { - Write-Host "Keeping package-lock.json (pass -RemoveLockfile to delete)." -ForegroundColor DarkGray - } -} -finally { - Pop-Location -} diff --git a/Journal.DevTool/scripts/legacy/nuget-export-cache.legacy.ps1 b/Journal.DevTool/scripts/legacy/nuget-export-cache.legacy.ps1 deleted file mode 100644 index bee1a64..0000000 --- a/Journal.DevTool/scripts/legacy/nuget-export-cache.legacy.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -param( - [string]$OutputZip = "nuget-cache-export.zip", - [switch]$IncludeDotnetHome -) - -$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path -$outputPath = if ([System.IO.Path]::IsPathRooted($OutputZip)) { $OutputZip } else { Join-Path $repoRoot $OutputZip } -$outputDir = Split-Path -Parent $outputPath -if (-not (Test-Path $outputDir)) { - New-Item -ItemType Directory -Force -Path $outputDir | Out-Null -} - -Write-Host "Priming restore cache..." -& (Join-Path $PSScriptRoot "dotnet-min.ps1") restore "Journal.Sidecar/Journal.Sidecar.csproj" -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -& (Join-Path $PSScriptRoot "dotnet-min.ps1") restore "Journal.WebGateway/Journal.WebGateway.csproj" -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -& (Join-Path $PSScriptRoot "dotnet-min.ps1") restore "Journal.SmokeTests/Journal.SmokeTests.csproj" -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - -$staging = Join-Path $repoRoot ".nuget-export-staging" -if (Test-Path $staging) { - Remove-Item -Recurse -Force $staging -} -New-Item -ItemType Directory -Force -Path $staging | Out-Null - -$nugetRoot = Join-Path $repoRoot ".nuget" -if (-not (Test-Path $nugetRoot)) { - Write-Error "No .nuget directory found under $repoRoot" - exit 1 -} - -Copy-Item -Recurse -Force -Path $nugetRoot -Destination (Join-Path $staging ".nuget") -if ($IncludeDotnetHome) { - $dotnetHome = Join-Path $repoRoot ".dotnet_home" - if (Test-Path $dotnetHome) { - Copy-Item -Recurse -Force -Path $dotnetHome -Destination (Join-Path $staging ".dotnet_home") - } -} - -$manifest = @( - "exported_utc=$([DateTime]::UtcNow.ToString("o"))" - "repo_root=$repoRoot" - "include_dotnet_home=$($IncludeDotnetHome.IsPresent)" - "note=Copy this zip to target machine and run scripts/nuget-import-cache.ps1" -) -$manifest | Set-Content -Encoding UTF8 -Path (Join-Path $staging "nuget-cache-manifest.txt") - -if (Test-Path $outputPath) { - Remove-Item -Force $outputPath -} - -Compress-Archive -Path (Join-Path $staging "*") -DestinationPath $outputPath -Force -Remove-Item -Recurse -Force $staging - -Write-Host "NuGet cache export created at: $outputPath" - diff --git a/Journal.DevTool/scripts/legacy/nuget-import-cache.legacy.ps1 b/Journal.DevTool/scripts/legacy/nuget-import-cache.legacy.ps1 deleted file mode 100644 index dad8f04..0000000 --- a/Journal.DevTool/scripts/legacy/nuget-import-cache.legacy.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -param( - [string]$InputZip = "nuget-cache-export.zip" -) - -$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path -$inputPath = if ([System.IO.Path]::IsPathRooted($InputZip)) { $InputZip } else { Join-Path $repoRoot $InputZip } - -if (-not (Test-Path $inputPath)) { - Write-Error "Input zip not found: $inputPath" - exit 1 -} - -Write-Host "Importing cache from: $inputPath" -Expand-Archive -Path $inputPath -DestinationPath $repoRoot -Force - -Write-Host "Running restore with local cache..." -& (Join-Path $PSScriptRoot "dotnet-min.ps1") restore "Journal.Sidecar/Journal.Sidecar.csproj" -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -& (Join-Path $PSScriptRoot "dotnet-min.ps1") restore "Journal.WebGateway/Journal.WebGateway.csproj" -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -& (Join-Path $PSScriptRoot "dotnet-min.ps1") restore "Journal.SmokeTests/Journal.SmokeTests.csproj" -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - -Write-Host "Cache import complete." - diff --git a/Journal.DevTool/scripts/legacy/pip-min.legacy.ps1 b/Journal.DevTool/scripts/legacy/pip-min.legacy.ps1 deleted file mode 100644 index 6bf7f2c..0000000 --- a/Journal.DevTool/scripts/legacy/pip-min.legacy.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$PipArgs -) - -$commonScript = Join-Path $PSScriptRoot "script-common.ps1" -if (-not (Test-Path $commonScript)) { - throw "Missing helper script: $commonScript" -} -. $commonScript - -$repoRoot = Resolve-JournalRepoRoot -StartPath $PSScriptRoot - -Initialize-JournalPipEnv -RepoRoot $repoRoot -Clear-JournalProxyEnv - -if (-not $PipArgs -or $PipArgs.Count -eq 0) { - Write-Host "Usage: ./scripts/pip-min.ps1 " - Write-Host "Example: ./scripts/pip-min.ps1 install --index-url https://pypi.org/simple faster-whisper" - exit 2 -} - -# Default install target to a repo-local directory so installs do not require -# user/site-packages write access on constrained hosts. -$effectiveArgs = @($PipArgs) -$firstArg = $effectiveArgs[0].ToLowerInvariant() -if ($firstArg -eq "install") { - # On Windows, map PyAudio to pyaudiowpatch (wheel available for newer CPython), - # avoiding source builds that require PortAudio headers/toolchain wiring. - for ($i = 0; $i -lt $effectiveArgs.Count; $i++) { - $arg = $effectiveArgs[$i] - if ($arg -match '^(?i)pyaudio($|[<>=!~].*)') { - $suffix = $arg.Substring(7) - $effectiveArgs[$i] = "pyaudiowpatch$suffix" - Write-Host "pip-min: mapped '$arg' -> '$($effectiveArgs[$i])' on Windows." - } - } - - $hasTarget = $effectiveArgs -contains "--target" -or $effectiveArgs -contains "-t" -or $effectiveArgs -contains "--prefix" - if (-not $hasTarget) { - $effectiveArgs = $effectiveArgs | Where-Object { $_ -ne "--user" } - $localTarget = Join-Path $repoRoot ".pydeps\py314" - New-Item -ItemType Directory -Force -Path $localTarget | Out-Null - $effectiveArgs += @("--target", $localTarget) - Write-Host "pip-min: using local target $localTarget" - } -} - -$pipWrapper = Join-Path $PSScriptRoot "pip_safe.py" -if (Test-Path $pipWrapper) { - & python $pipWrapper @effectiveArgs -} -else { - & python -m pip @effectiveArgs -} -exit $LASTEXITCODE diff --git a/Journal.DevTool/scripts/legacy/publish-app.legacy.ps1 b/Journal.DevTool/scripts/legacy/publish-app.legacy.ps1 deleted file mode 100644 index 7d3a783..0000000 --- a/Journal.DevTool/scripts/legacy/publish-app.legacy.ps1 +++ /dev/null @@ -1,216 +0,0 @@ -param( - [ValidateSet("web", "tauri")] - [string]$Target = "web", - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", - [ValidateSet("none", "nsis", "msi")] - [string]$TauriBundles = "none", - [switch]$InstallDeps, - [switch]$SkipInstall, - [switch]$DryRun -) - -$commonScript = Join-Path $PSScriptRoot "script-common.ps1" -if (-not (Test-Path $commonScript)) { - throw "Missing helper script: $commonScript" -} -. $commonScript - -$repoRoot = Resolve-JournalRepoRoot -StartPath $PSScriptRoot -$appRoot = Resolve-JournalAppRoot -RepoRoot $repoRoot - -Clear-JournalProxyEnv - -# Keep npm cache and temp local to the repo. -$npmCacheDir = Join-Path $repoRoot ".npm\cache" -$npmTempDir = Join-Path $repoRoot ".tmp\npm-temp" -New-Item -ItemType Directory -Force -Path $npmCacheDir, $npmTempDir | Out-Null -$env:npm_config_cache = $npmCacheDir -$env:npm_config_update_notifier = "false" -$env:npm_config_fund = "false" -$env:npm_config_audit = "false" -$env:npm_config_offline = "false" -$env:npm_config_prefer_offline = "false" -$env:npm_config_prefer_online = "true" -$env:TEMP = $npmTempDir -$env:TMP = $npmTempDir - -if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { - throw "npm is required but was not found in PATH." -} - -$nodeModulesPath = Join-Path $appRoot "node_modules" -$packageJsonPath = Join-Path $appRoot "package.json" -$packageLockPath = Join-Path $appRoot "package-lock.json" -$depsHashPath = Join-Path $appRoot "node_modules\.journal-deps.sha256" - -function Get-JournalNodeDepsHash { - param( - [Parameter(Mandatory = $true)] - [string[]]$Paths - ) - - $hashLines = foreach ($path in $Paths) { - if (-not (Test-Path $path)) { - continue - } - (Get-FileHash -Algorithm SHA256 -Path $path).Hash - } - return ($hashLines -join "`n").Trim() -} - -$hashInputs = @() -if (Test-Path $packageJsonPath) { - $hashInputs += $packageJsonPath -} -if (Test-Path $packageLockPath) { - $hashInputs += $packageLockPath -} -if ($hashInputs.Count -eq 0) { - throw "package.json not found under $appRoot." -} - -$expectedDepsHash = Get-JournalNodeDepsHash -Paths $hashInputs -$shouldInstall = $InstallDeps -or (-not (Test-Path $nodeModulesPath)) -$installReason = $null - -if (-not $shouldInstall -and -not $SkipInstall) { - if (-not (Test-Path $depsHashPath)) { - $shouldInstall = $true - $installReason = "dependency hash missing" - } - else { - $currentDepsHash = (Get-Content $depsHashPath -Raw).Trim() - if ($currentDepsHash -ne $expectedDepsHash) { - $shouldInstall = $true - $installReason = "package.json/lockfile changed" - } - } -} - -if ($SkipInstall) { - $shouldInstall = $false - if ($installReason) { - Write-Host "SkipInstall set; dependencies may be stale ($installReason)." -ForegroundColor Yellow - } -} - -Write-Host "Building Journal.App target '$Target' ($Configuration)..." -ForegroundColor Cyan -Write-Host "Using app root: $appRoot" -ForegroundColor DarkGray - -Push-Location $appRoot -try { - if ($shouldInstall) { - $installArgs = if (Test-Path $packageLockPath) { - @("ci", "--no-audit", "--fund=false") - } - else { - @("install", "--no-audit", "--fund=false") - } - - if ($installReason) { - Write-Host "Dependencies changed ($installReason). Installing..." -ForegroundColor Yellow - } - - Write-Host "> npm $($installArgs -join ' ')" -ForegroundColor DarkGray - if (-not $DryRun) { - & npm @installArgs - if ($LASTEXITCODE -ne 0) { - throw "Dependency install failed with exit code $LASTEXITCODE." - } - - $depsDir = Split-Path $depsHashPath -Parent - if (-not (Test-Path $depsDir)) { - New-Item -ItemType Directory -Force -Path $depsDir | Out-Null - } - $expectedDepsHash | Set-Content -Path $depsHashPath -NoNewline - } - } - else { - Write-Host "Skipping dependency install (node_modules present and deps unchanged)." -ForegroundColor DarkGray - } - - if ($Target -eq "web") { - $buildArgs = @("run", "build") - Write-Host "> npm $($buildArgs -join ' ')" -ForegroundColor DarkGray - if (-not $DryRun) { - & npm @buildArgs - if ($LASTEXITCODE -ne 0) { - throw "Frontend build failed with exit code $LASTEXITCODE." - } - } - - $outputPath = Join-Path $appRoot "build" - if ($DryRun) { - Write-Host "`nDry run complete (no commands executed)." -ForegroundColor Yellow - Write-Host "Expected output: $outputPath" -ForegroundColor Gray - } - else { - Write-Host "`nFrontend build successful." -ForegroundColor Green - Write-Host "Output: $outputPath" -ForegroundColor Gray - } - } - else { - $tauriArgs = @("run", "tauri", "build") - $tauriCliArgs = @() - if ($TauriBundles -eq "none") { - $tauriCliArgs += "--no-bundle" - } - else { - $tauriCliArgs += @("--bundles", $TauriBundles) - } - if ($Configuration -eq "Debug") { - $tauriCliArgs += "--debug" - } - if ($tauriCliArgs.Count -gt 0) { - $tauriArgs += "--" - $tauriArgs += $tauriCliArgs - } - - Write-Host "> npm $($tauriArgs -join ' ')" -ForegroundColor DarkGray - if (-not $DryRun) { - & npm @tauriArgs - if ($LASTEXITCODE -ne 0) { - throw "Tauri build failed with exit code $LASTEXITCODE." - } - } - - $targetConfigDir = if ($Configuration -eq "Debug") { "debug" } else { "release" } - $tauriTargetPath = Join-Path $appRoot "src-tauri\target" - $rawExePath = Join-Path $tauriTargetPath "$targetConfigDir\journalapp.exe" - if ($DryRun) { - Write-Host "`nDry run complete (no commands executed)." -ForegroundColor Yellow - if ($TauriBundles -eq "none") { - Write-Host "Expected executable: $rawExePath" -ForegroundColor Gray - } - else { - Write-Host "Expected output root: $tauriTargetPath" -ForegroundColor Gray - } - } - else { - Write-Host "`nTauri build successful." -ForegroundColor Green - if ($TauriBundles -eq "none") { - if (Test-Path $rawExePath) { - Write-Host "Executable location: $rawExePath" -ForegroundColor Gray - } - else { - $exeCandidates = Get-ChildItem -Path (Join-Path $tauriTargetPath $targetConfigDir) -File -Filter *.exe -ErrorAction SilentlyContinue | - Sort-Object LastWriteTime -Descending - if ($exeCandidates -and $exeCandidates.Count -gt 0) { - Write-Host "Executable location: $($exeCandidates[0].FullName)" -ForegroundColor Gray - } - else { - Write-Host "Output root: $tauriTargetPath" -ForegroundColor Gray - } - } - } - else { - Write-Host "Output root: $tauriTargetPath" -ForegroundColor Gray - } - } - } -} -finally { - Pop-Location -} - diff --git a/Journal.DevTool/scripts/legacy/publish-output.legacy.ps1 b/Journal.DevTool/scripts/legacy/publish-output.legacy.ps1 deleted file mode 100644 index 29663f6..0000000 --- a/Journal.DevTool/scripts/legacy/publish-output.legacy.ps1 +++ /dev/null @@ -1,103 +0,0 @@ -param( - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", - [string]$Runtime = "win-x64", - [switch]$SkipSidecar, - [switch]$SkipWeb, - [switch]$SkipWebGateway, - [switch]$SkipTauri, - [switch]$DryRun -) - -$commonScript = Join-Path $PSScriptRoot "script-common.ps1" -if (-not (Test-Path $commonScript)) { - throw "Missing helper script: $commonScript" -} -. $commonScript - -$repoRoot = Resolve-JournalRepoRoot -StartPath $PSScriptRoot -$appRoot = Resolve-JournalAppRoot -RepoRoot $repoRoot -$outputRoot = Join-Path $repoRoot "output" - -$publishSidecar = Join-Path $PSScriptRoot "publish-sidecar.ps1" -$publishApp = Join-Path $PSScriptRoot "publish-app.ps1" -$publishGateway = Join-Path $PSScriptRoot "publish-webgateway.ps1" - -Write-Host "Publishing all outputs to: $outputRoot" -ForegroundColor Cyan -Write-Host "Configuration: $Configuration Runtime: $Runtime" -ForegroundColor DarkGray - -if (-not (Test-Path $outputRoot)) { - New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null -} - -function Invoke-Step { - param( - [string]$Label, - [string]$ScriptPath, - [string[]]$Args - ) - - Write-Host "`n> $Label" -ForegroundColor Cyan - Write-Host " $ScriptPath $($Args -join ' ')" -ForegroundColor DarkGray - if (-not $DryRun) { - & $ScriptPath @Args - } -} - -if (-not $SkipSidecar) { - Invoke-Step "Publish Sidecar" $publishSidecar @( - "-Configuration", $Configuration, - "-Runtime", $Runtime - ) -} -else { - Write-Host "Skipping sidecar publish." -ForegroundColor DarkGray -} - -if (-not $SkipWeb) { - Invoke-Step "Build Web UI" $publishApp @( - "-Target", "web", - "-Configuration", $Configuration - ) -} -else { - Write-Host "Skipping web build." -ForegroundColor DarkGray -} - -if (-not $SkipWebGateway) { - Invoke-Step "Publish WebGateway" $publishGateway @( - "-Configuration", $Configuration, - "-Runtime", $Runtime - ) -} -else { - Write-Host "Skipping WebGateway publish." -ForegroundColor DarkGray -} - -if (-not $SkipTauri) { - Invoke-Step "Build Tauri Desktop App" $publishApp @( - "-Target", "tauri", - "-Configuration", $Configuration, - "-TauriBundles", "none" - ) - - $targetConfigDir = if ($Configuration -eq "Debug") { "debug" } else { "release" } - $tauriExePath = Join-Path $appRoot "src-tauri\\target\\$targetConfigDir\\journalapp.exe" - $stagedExePath = Join-Path $outputRoot "journalapp.exe" - - if (Test-Path $tauriExePath) { - if ($DryRun) { - Write-Host "Would copy: $tauriExePath -> $stagedExePath" -ForegroundColor Yellow - } - else { - Copy-Item -Force $tauriExePath $stagedExePath - Write-Host "Staged desktop exe: $stagedExePath" -ForegroundColor Green - } - } - else { - Write-Warning "Tauri exe not found at $tauriExePath" - } -} -else { - Write-Host "Skipping Tauri build." -ForegroundColor DarkGray -} diff --git a/Journal.DevTool/scripts/legacy/publish-sidecar.legacy.ps1 b/Journal.DevTool/scripts/legacy/publish-sidecar.legacy.ps1 deleted file mode 100644 index ab2a855..0000000 --- a/Journal.DevTool/scripts/legacy/publish-sidecar.legacy.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -param( - [string]$Configuration = "Release", - [string]$Runtime = "win-x64" -) - -$commonScript = Join-Path $PSScriptRoot "script-common.ps1" -if (-not (Test-Path $commonScript)) { - throw "Missing helper script: $commonScript" -} -. $commonScript - -$repoRoot = Resolve-JournalRepoRoot -StartPath $PSScriptRoot -$csproj = Resolve-JournalSidecarProjectPath -RepoRoot $repoRoot -$outputDir = Join-Path $repoRoot "output" - -# Setup local dotnet environment (matches dotnet-min.ps1 logic) -Clear-JournalProxyEnv -Initialize-JournalDotnetEnv -RepoRoot $repoRoot - -Write-Host "Publishing Journal.Sidecar ($Configuration, $Runtime)..." -ForegroundColor Cyan -Write-Host "Using project: $csproj" -ForegroundColor DarkGray - -$publishArgs = @( - "publish", $csproj, - "-c", $Configuration, - "-r", $Runtime, - "--self-contained", - "-p:PublishSingleFile=true", - "-p:IncludeNativeLibrariesForSelfExtract=true", - "-p:RestoreIgnoreFailedSources=true", - "-p:NuGetAudit=false", - "-o", $outputDir -) - -& dotnet @publishArgs - -if ($LASTEXITCODE -eq 0) { - $binaryName = [System.IO.Path]::GetFileNameWithoutExtension($csproj) - $isWindowsRuntime = $Runtime -like "win-*" - $binaryFile = if ($isWindowsRuntime) { "$binaryName.exe" } else { $binaryName } - $binaryPath = Join-Path $outputDir $binaryFile - - Write-Host "`nPublish successful!" -ForegroundColor Green - if (Test-Path $binaryPath) { - Write-Host "Executable location: $binaryPath" -ForegroundColor Gray - } - else { - Write-Host "Output directory: $outputDir" -ForegroundColor Gray - } -} -else { - Write-Host "`nPublish failed with exit code $LASTEXITCODE" -ForegroundColor Red - exit $LASTEXITCODE -} diff --git a/Journal.DevTool/scripts/legacy/publish-webgateway.legacy.ps1 b/Journal.DevTool/scripts/legacy/publish-webgateway.legacy.ps1 deleted file mode 100644 index b3e3322..0000000 --- a/Journal.DevTool/scripts/legacy/publish-webgateway.legacy.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -param( - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", - [string]$Runtime = "win-x64", - [switch]$SelfContained, - [switch]$SkipWebAssets -) - -$commonScript = Join-Path $PSScriptRoot "script-common.ps1" -if (-not (Test-Path $commonScript)) { - throw "Missing helper script: $commonScript" -} -. $commonScript - -$repoRoot = Resolve-JournalRepoRoot -StartPath $PSScriptRoot -$gatewayProject = Resolve-JournalWebGatewayProjectPath -RepoRoot $repoRoot -$outputDir = Join-Path $repoRoot "output\webgateway" -$webBuildDir = Join-Path $repoRoot "Journal.App\build" -$webOutputDir = Join-Path $outputDir "wwwroot" - -Clear-JournalProxyEnv -Initialize-JournalDotnetEnv -RepoRoot $repoRoot - -Write-Host "Publishing Journal.WebGateway ($Configuration, $Runtime)..." -ForegroundColor Cyan -Write-Host "Project: $gatewayProject" -ForegroundColor DarkGray - -$publishArgs = @( - "publish", $gatewayProject, - "-c", $Configuration, - "-r", $Runtime, - "--self-contained", ($SelfContained.IsPresent.ToString().ToLowerInvariant()), - "-p:RestoreIgnoreFailedSources=true", - "-p:NuGetAudit=false", - "-o", $outputDir -) - -& dotnet @publishArgs -if ($LASTEXITCODE -ne 0) { - Write-Host "`nPublish failed with exit code $LASTEXITCODE" -ForegroundColor Red - exit $LASTEXITCODE -} - -if (-not $SkipWebAssets) { - if (Test-Path $webBuildDir) { - New-Item -ItemType Directory -Force -Path $webOutputDir | Out-Null - Copy-Item -Path (Join-Path $webBuildDir "*") -Destination $webOutputDir -Recurse -Force - Write-Host "Copied web assets to: $webOutputDir" -ForegroundColor Gray - } - else { - Write-Warning "Journal.App build output not found at $webBuildDir. Run ./scripts/publish-app.ps1 -Target web first." - } -} - -Write-Host "`nPublish successful." -ForegroundColor Green -Write-Host "Output directory: $outputDir" -ForegroundColor Gray diff --git a/Journal.DevTool/scripts/legacy/run-webgateway.legacy.ps1 b/Journal.DevTool/scripts/legacy/run-webgateway.legacy.ps1 deleted file mode 100644 index c76b3a7..0000000 --- a/Journal.DevTool/scripts/legacy/run-webgateway.legacy.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -param( - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", - [string]$Urls = "http://0.0.0.0:5180", - [string]$ProjectRoot, - [ValidateSet("Dev", "Output")] - [string]$Mode = "Dev" -) - -$commonScript = Join-Path $PSScriptRoot "script-common.ps1" -if (-not (Test-Path $commonScript)) { - throw "Missing helper script: $commonScript" -} -. $commonScript - -$repoRoot = Resolve-JournalRepoRoot -StartPath $PSScriptRoot -$gatewayProject = Resolve-JournalWebGatewayProjectPath -RepoRoot $repoRoot - -$effectiveProjectRoot = if ([string]::IsNullOrWhiteSpace($ProjectRoot)) { - $repoRoot -} -else { - [System.IO.Path]::GetFullPath($ProjectRoot) -} - -if (-not (Test-Path $effectiveProjectRoot)) { - throw "ProjectRoot does not exist: $effectiveProjectRoot" -} - -Clear-JournalProxyEnv -Initialize-JournalDotnetEnv -RepoRoot $repoRoot -$env:JOURNAL_PROJECT_ROOT = $effectiveProjectRoot - -if ($Mode -eq "Output") { - $exeName = if ([System.Environment]::OSVersion.Platform -eq "Win32NT") { "Journal.WebGateway.exe" } else { "Journal.WebGateway" } - $exePath = Join-Path $repoRoot "output\webgateway\$exeName" - - if (-not (Test-Path $exePath)) { - Write-Host "Output executable not found at $exePath" -ForegroundColor Red - Write-Host "Please build WebGateway first (e.g. scripts\publish-webgateway.ps1)" -ForegroundColor Yellow - exit 1 - } - - Write-Host "Running Journal.WebGateway (Published Output)..." -ForegroundColor Cyan - Write-Host "Executable: $exePath" -ForegroundColor DarkGray - Write-Host "URLs: $Urls" -ForegroundColor DarkGray - Write-Host "JOURNAL_PROJECT_ROOT: $effectiveProjectRoot" -ForegroundColor DarkGray - - & $exePath --urls $Urls -} -else { - Write-Host "Running Journal.WebGateway ($Configuration Dev Server)..." -ForegroundColor Cyan - Write-Host "Project: $gatewayProject" -ForegroundColor DarkGray - Write-Host "URLs: $Urls" -ForegroundColor DarkGray - Write-Host "JOURNAL_PROJECT_ROOT: $effectiveProjectRoot" -ForegroundColor DarkGray - - $runArgs = @( - "run", - "--project", $gatewayProject, - "-c", $Configuration, - "--no-launch-profile", - "--urls", $Urls, - "-p:RestoreIgnoreFailedSources=true", - "-p:NuGetAudit=false" - ) - - & dotnet @runArgs -} -exit $LASTEXITCODE diff --git a/Journal.DevTool/scripts/legacy/sync-output.legacy.ps1 b/Journal.DevTool/scripts/legacy/sync-output.legacy.ps1 deleted file mode 100644 index bb00429..0000000 --- a/Journal.DevTool/scripts/legacy/sync-output.legacy.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -param() - -$commonScript = Join-Path $PSScriptRoot "script-common.ps1" -if (-not (Test-Path $commonScript)) { - throw "Missing helper script: $commonScript" -} -. $commonScript - -$repoRoot = Resolve-JournalRepoRoot -StartPath $PSScriptRoot -$outputDir = Join-Path $repoRoot "output" - -# Ensure output exists -New-Item -ItemType Directory -Force -Path $outputDir | Out-Null - -Write-Host "Syncing all recent built assets to output directory..." -ForegroundColor Cyan - -# Helper to find the newest compiled binary -function Find-NewestBin([string]$SearchPath, [string]$Pattern) { - if (-not (Test-Path $SearchPath)) { return $null } - $files = Get-ChildItem -Path $SearchPath -Filter $Pattern -Recurse -File -ErrorAction SilentlyContinue | - Where-Object { $_.FullName -notmatch '\\obj\\' } | - Sort-Object LastWriteTime -Descending - if ($files) { return $files[0] } - return $null -} - -# 1. Front-end Web Assets -$webBuildDir = Join-Path $repoRoot "Journal.App\build" -if (Test-Path $webBuildDir) { - $webOutputDir = Join-Path $outputDir "webgateway\wwwroot" - New-Item -ItemType Directory -Force -Path $webOutputDir | Out-Null - Copy-Item -Path (Join-Path $webBuildDir "*") -Destination $webOutputDir -Recurse -Force - Write-Host "Synced web assets -> $webOutputDir" -ForegroundColor Green -} - -# 2. Sidecar -$sidecarExeName = if ([System.Environment]::OSVersion.Platform -eq "Win32NT") { "Journal.Sidecar.exe" } else { "Journal.Sidecar" } -$sidecarExe = Find-NewestBin -SearchPath (Join-Path $repoRoot "Journal.Sidecar\bin") -Pattern $sidecarExeName -if ($sidecarExe) { - Copy-Item -Path (Join-Path $sidecarExe.DirectoryName "*") -Destination $outputDir -Recurse -Force - Write-Host "Synced Journal.Sidecar -> $outputDir" -ForegroundColor Green -} - -# 3. WebGateway -$gwExeName = if ([System.Environment]::OSVersion.Platform -eq "Win32NT") { "Journal.WebGateway.exe" } else { "Journal.WebGateway" } -$gwExe = Find-NewestBin -SearchPath (Join-Path $repoRoot "Journal.WebGateway\bin") -Pattern $gwExeName -if ($gwExe) { - $gwOutputDir = Join-Path $outputDir "webgateway" - New-Item -ItemType Directory -Force -Path $gwOutputDir | Out-Null - Copy-Item -Path (Join-Path $gwExe.DirectoryName "*") -Destination $gwOutputDir -Recurse -Force - Write-Host "Synced Journal.WebGateway -> $gwOutputDir" -ForegroundColor Green -} - -# 4. Tauri Desktop App -$tauriExe = Find-NewestBin -SearchPath (Join-Path $repoRoot "Journal.App\src-tauri\target") -Pattern "*.exe" -if ($tauriExe) { - # Don't try to copy sidecar.exe again if it ended up in tauri target dir - if ($tauriExe.Name -ne "Journal.Sidecar.exe") { - Copy-Item -Path $tauriExe.FullName -Destination $outputDir -Force - Write-Host "Synced Tauri App ($($tauriExe.Name)) -> $outputDir" -ForegroundColor Green - } -} - -Write-Host "Sync complete!" -ForegroundColor Cyan diff --git a/Journal.DevTool/scripts/migration-gate.py b/Journal.DevTool/scripts/migration-gate.py deleted file mode 100644 index 398cf09..0000000 --- a/Journal.DevTool/scripts/migration-gate.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import subprocess -import sys -from pathlib import Path - -from script_common import resolve_repo_root - - -def run_step(repo_root: Path, title: str, command: list[str]) -> int: - print(f"\n== {title} ==") - print("$", " ".join(command)) - proc = subprocess.run(command, cwd=str(repo_root), check=False) - return proc.returncode - - -def main() -> int: - parser = argparse.ArgumentParser(description="Cross-platform migration quality gate") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--skip-tests", action="store_true") - parser.add_argument("--test-project", default=None, help="Optional test csproj path") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - - code = run_step(repo_root, "Build", [sys.executable, "scripts/dotnet-min.py", "build"]) - if code != 0: - return code - - if not args.skip_tests: - if args.test_project: - test_cmd = [sys.executable, "scripts/dotnet-min.py", "test", args.test_project] - else: - test_cmd = [sys.executable, "scripts/dotnet-min.py", "test"] - code = run_step(repo_root, "Tests", test_cmd) - if code != 0: - return code - - print("\nMigration gate passed.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/npm-clean.py b/Journal.DevTool/scripts/npm-clean.py deleted file mode 100644 index 0ea899f..0000000 --- a/Journal.DevTool/scripts/npm-clean.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import shutil -from pathlib import Path - -from script_common import resolve_repo_root - - -def main() -> int: - parser = argparse.ArgumentParser(description="Cross-platform node_modules cleanup") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--working-dir", default=".") - parser.add_argument("--also-cache", action="store_true") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - work_dir = (repo_root / args.working_dir).resolve() - node_modules = work_dir / "node_modules" - if node_modules.exists(): - shutil.rmtree(node_modules) - print(f"Removed: {node_modules}") - else: - print(f"Not found: {node_modules}") - - if args.also_cache: - npm_cache = repo_root / ".npm" / "cache" - if npm_cache.exists(): - shutil.rmtree(npm_cache) - print(f"Removed: {npm_cache}") - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/nuget-export-cache.py b/Journal.DevTool/scripts/nuget-export-cache.py deleted file mode 100644 index 17720f7..0000000 --- a/Journal.DevTool/scripts/nuget-export-cache.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import shutil -import tempfile -from pathlib import Path - -from script_common import resolve_repo_root - - -def main() -> int: - parser = argparse.ArgumentParser(description="Export local NuGet cache to zip") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--output-zip", default="nuget-cache-export.zip") - parser.add_argument("--include-dotnet-home", action="store_true") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - output_zip = (repo_root / args.output_zip).resolve() - - nuget_dir = repo_root / ".nuget" - dotnet_home = repo_root / ".dotnet_home" - if not nuget_dir.exists(): - print(f"NuGet cache not found: {nuget_dir}") - return 2 - - with tempfile.TemporaryDirectory() as td: - stage = Path(td) / "cache-export" - stage.mkdir(parents=True, exist_ok=True) - shutil.copytree(nuget_dir, stage / ".nuget") - if args.include_dotnet_home and dotnet_home.exists(): - shutil.copytree(dotnet_home, stage / ".dotnet_home") - manifest = stage / "nuget-cache-manifest.txt" - manifest.write_text("exported_by=nuget-export-cache.py\n", encoding="utf-8") - archive_base = str(output_zip.with_suffix("")) - shutil.make_archive(archive_base, "zip", root_dir=str(stage)) - - print(f"Exported cache: {output_zip}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/nuget-import-cache.py b/Journal.DevTool/scripts/nuget-import-cache.py deleted file mode 100644 index ec0fee1..0000000 --- a/Journal.DevTool/scripts/nuget-import-cache.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import shutil -from pathlib import Path - -from script_common import resolve_repo_root - - -def main() -> int: - parser = argparse.ArgumentParser(description="Import NuGet cache from zip") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--input-zip", default="nuget-cache-export.zip") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - input_zip = (repo_root / args.input_zip).resolve() - if not input_zip.exists(): - print(f"Input zip not found: {input_zip}") - return 2 - - shutil.unpack_archive(str(input_zip), extract_dir=str(repo_root)) - print(f"Imported cache from: {input_zip}") - print("Run `python scripts/dotnet-min.py restore` to validate restore in this repo.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/pip-min.py b/Journal.DevTool/scripts/pip-min.py deleted file mode 100644 index fd03343..0000000 --- a/Journal.DevTool/scripts/pip-min.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import sys - -from script_common import pip_env, resolve_repo_root, run - - -def main() -> int: - parser = argparse.ArgumentParser(description="Cross-platform minimal pip wrapper") - parser.add_argument("pip_args", nargs=argparse.REMAINDER) - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - - if not args.pip_args: - print("Usage: python scripts/pip-min.py ", file=sys.stderr) - return 2 - - repo_root = resolve_repo_root(args.repo_root) - pip_args = list(args.pip_args) - - # Preserve legacy behavior: for bare install, default target to repo-local deps. - if pip_args and pip_args[0].lower() == "install": - has_target = any(a in ("--target", "--prefix") for a in pip_args) - if not has_target: - pip_args = [a for a in pip_args if a != "--user"] - target = repo_root / ".pydeps" / f"py{sys.version_info.major}{sys.version_info.minor}" - os.makedirs(target, exist_ok=True) - pip_args.extend(["--target", str(target)]) - - return run(sys.executable, ["-m", "pip", *pip_args], repo_root, env=pip_env(repo_root)) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/pip_safe.py b/Journal.DevTool/scripts/pip_safe.py deleted file mode 100644 index a02a307..0000000 --- a/Journal.DevTool/scripts/pip_safe.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -import os -import tempfile -from typing import Callable - - -def _mkdtemp_compat( - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, -) -> str: - # Python 3.14 on some Windows hosts creates mkdtemp dirs that are - # immediately non-writable by the same process when mode=0o700 is used. - # pip relies heavily on tempfile; force 0o777 for compatibility. - if dir is None: - dir = tempfile.gettempdir() - if prefix is None: - prefix = tempfile.template - if suffix is None: - suffix = "" - - names = tempfile._get_candidate_names() - for _ in range(tempfile.TMP_MAX): - name = next(names) - path = os.path.join(dir, f"{prefix}{name}{suffix}") - try: - os.mkdir(path, 0o777) - return path - except FileExistsError: - continue - - raise FileExistsError("No usable temporary directory name found.") - - -def main(argv: list[str]) -> int: - tempfile.mkdtemp = _mkdtemp_compat # type: ignore[assignment] - - from pip._internal.cli.main import main as pip_main - - return int(pip_main(argv)) - - -if __name__ == "__main__": - raise SystemExit(main(__import__("sys").argv[1:])) - diff --git a/Journal.DevTool/scripts/publish-app.py b/Journal.DevTool/scripts/publish-app.py deleted file mode 100644 index b85ada7..0000000 --- a/Journal.DevTool/scripts/publish-app.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 -import argparse -from pathlib import Path - -from script_common import find_node_app_root, resolve_repo_root, run, sha256_files - - -def main() -> int: - parser = argparse.ArgumentParser(description="Cross-platform web/tauri publish helper") - parser.add_argument("--target", choices=["web", "tauri"], default="web") - parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release") - parser.add_argument("--tauri-bundles", choices=["none", "nsis", "msi"], default="none") - parser.add_argument("--install-deps", action="store_true") - parser.add_argument("--skip-install", action="store_true") - parser.add_argument("--dry-run", action="store_true") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--app-root", default=None, help="Relative or absolute app root with package.json") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - app_root = find_node_app_root(repo_root, args.app_root) - if app_root is None: - print("Unable to locate app root (no unique package.json found).") - return 2 - - package_json = app_root / "package.json" - lock_file = app_root / "package-lock.json" - node_modules = app_root / "node_modules" - deps_hash_file = node_modules / ".sdt-deps.sha256" - expected_hash = sha256_files([package_json, lock_file]) - - should_install = args.install_deps or not node_modules.exists() - if not should_install and not args.skip_install: - if not deps_hash_file.exists(): - should_install = True - else: - current = deps_hash_file.read_text(encoding="utf-8").strip() - should_install = current != expected_hash - if args.skip_install: - should_install = False - - print(f"App root: {app_root}") - print(f"Target: {args.target} ({args.configuration})") - - if should_install: - install_args = ["ci", "--no-audit", "--fund=false"] if lock_file.exists() else ["install", "--no-audit", "--fund=false"] - print("$ npm " + " ".join(install_args)) - if not args.dry_run: - code = run("npm", install_args, app_root) - if code != 0: - if lock_file.exists() and install_args[0] == "ci": - print("npm ci failed (likely lockfile out of sync). Falling back to npm install...") - fallback_args = ["install", "--no-audit", "--fund=false"] - print("$ npm " + " ".join(fallback_args)) - code = run("npm", fallback_args, app_root) - if code != 0: - return code - else: - return code - node_modules.mkdir(parents=True, exist_ok=True) - deps_hash_file.write_text(expected_hash, encoding="utf-8") - else: - print("Skipping dependency install.") - - if args.target == "web": - cmd = ["run", "build"] - print("$ npm " + " ".join(cmd)) - if not args.dry_run: - return run("npm", cmd, app_root) - return 0 - - tauri_cmd = ["run", "tauri", "build"] - tauri_tail: list[str] = [] - if args.tauri_bundles == "none": - tauri_tail.extend(["--no-bundle"]) - else: - tauri_tail.extend(["--bundles", args.tauri_bundles]) - if args.configuration == "Debug": - tauri_tail.append("--debug") - if tauri_tail: - tauri_cmd.extend(["--", *tauri_tail]) - - print("$ npm " + " ".join(tauri_cmd)) - if not args.dry_run: - return run("npm", tauri_cmd, app_root) - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/publish-output.py b/Journal.DevTool/scripts/publish-output.py deleted file mode 100644 index bb87c9c..0000000 --- a/Journal.DevTool/scripts/publish-output.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import shutil -import subprocess -import sys -from pathlib import Path - -from script_common import 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 main() -> int: - parser = argparse.ArgumentParser(description="Publish bundled outputs using Python script entrypoints") - parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release") - parser.add_argument("--runtime", default="win-x64") - parser.add_argument("--skip-sidecar", action="store_true") - parser.add_argument("--skip-web", action="store_true") - parser.add_argument("--skip-webgateway", action="store_true") - parser.add_argument("--skip-tauri", action="store_true") - parser.add_argument("--dry-run", action="store_true") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--sidecar-project", default=None) - parser.add_argument("--gateway-project", default=None) - parser.add_argument("--app-root", default=None) - parser.add_argument("--output-dir", default="output") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - output_root = (repo_root / args.output_dir).resolve() - output_root.mkdir(parents=True, exist_ok=True) - - py = sys.executable - if not args.skip_sidecar: - cmd = [py, "scripts/publish-sidecar.py", "--configuration", args.configuration, "--runtime", args.runtime] - if args.sidecar_project: - cmd.extend(["--project", args.sidecar_project]) - code = run_step("Publish sidecar", cmd, repo_root, args.dry_run) - if code != 0: - return code - - if not args.skip_web: - cmd = [py, "scripts/publish-app.py", "--target", "web", "--configuration", args.configuration] - if args.app_root: - cmd.extend(["--app-root", args.app_root]) - code = run_step("Build web", cmd, repo_root, args.dry_run) - if code != 0: - return code - - if not args.skip_webgateway: - cmd = [py, "scripts/publish-webgateway.py", "--configuration", args.configuration, "--runtime", args.runtime] - if args.gateway_project: - cmd.extend(["--project", args.gateway_project]) - code = run_step("Publish web gateway", cmd, repo_root, args.dry_run) - if code != 0: - return code - - if not args.skip_tauri: - cmd = [py, "scripts/publish-app.py", "--target", "tauri", "--configuration", args.configuration, "--tauri-bundles", "none"] - if args.app_root: - cmd.extend(["--app-root", args.app_root]) - code = run_step("Build tauri", cmd, repo_root, args.dry_run) - if code != 0: - return code - - app_root = find_node_app_root(repo_root, args.app_root) - if app_root is not None: - target_dir = app_root / "src-tauri" / "target" / ("debug" if args.configuration == "Debug" else "release") - exes = sorted(target_dir.glob("*.exe"), key=lambda p: p.stat().st_mtime, reverse=True) - if exes: - staged = output_root / exes[0].name - if args.dry_run: - print(f"Would copy: {exes[0]} -> {staged}") - else: - shutil.copy2(exes[0], staged) - print(f"Staged desktop executable: {staged}") - - print("\nPublish output workflow complete.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/publish-sidecar.ps1 b/Journal.DevTool/scripts/publish-sidecar.ps1 deleted file mode 100644 index 692ff55..0000000 --- a/Journal.DevTool/scripts/publish-sidecar.ps1 +++ /dev/null @@ -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 diff --git a/Journal.DevTool/scripts/publish-sidecar.py b/Journal.DevTool/scripts/publish-sidecar.py deleted file mode 100644 index c25a44a..0000000 --- a/Journal.DevTool/scripts/publish-sidecar.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os - -from script_common import dotnet_env, find_csproj_by_keyword, resolve_repo_root, run - - -def main() -> int: - parser = argparse.ArgumentParser(description="Cross-platform .NET sidecar publish helper") - parser.add_argument("--configuration", default="Release") - parser.add_argument("--runtime", default="win-x64") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--project", default=None, help="Relative/absolute path to sidecar csproj") - parser.add_argument("--output-dir", default="output") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - output_dir = (repo_root / args.output_dir).resolve() - output_dir.mkdir(parents=True, exist_ok=True) - - if args.project: - csproj = (repo_root / args.project).resolve() - else: - csproj = find_csproj_by_keyword(repo_root, ["sidecar"]) - if csproj is None or not csproj.exists(): - print("Could not locate sidecar project. Pass --project .") - return 2 - - publish_args = [ - "publish", - str(csproj), - "-c", - args.configuration, - "-r", - args.runtime, - "--self-contained", - "-p:PublishSingleFile=true", - "-p:IncludeNativeLibrariesForSelfExtract=true", - "-p:RestoreIgnoreFailedSources=true", - "-p:NuGetAudit=false", - "-o", - str(output_dir), - ] - code = run("dotnet", publish_args, repo_root, env=dotnet_env(repo_root)) - if code != 0: - return code - - binary_name = csproj.stem + (".exe" if args.runtime.startswith("win-") else "") - binary_path = output_dir / binary_name - if binary_path.exists(): - print(f"Published executable: {binary_path}") - else: - print(f"Publish completed. Output directory: {output_dir}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/publish-webgateway.py b/Journal.DevTool/scripts/publish-webgateway.py deleted file mode 100644 index 6a2c9c0..0000000 --- a/Journal.DevTool/scripts/publish-webgateway.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import shutil - -from script_common import dotnet_env, find_csproj_by_keyword, resolve_repo_root, run - - -def main() -> int: - parser = argparse.ArgumentParser(description="Cross-platform ASP.NET gateway publish helper") - parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release") - parser.add_argument("--runtime", default="win-x64") - parser.add_argument("--self-contained", action="store_true") - parser.add_argument("--skip-web-assets", action="store_true") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--project", default=None, help="Relative/absolute path to gateway csproj") - parser.add_argument("--web-build-dir", default=None, help="Relative path to web build assets root") - parser.add_argument("--output-dir", default="output/webgateway") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - output_dir = (repo_root / args.output_dir).resolve() - output_dir.mkdir(parents=True, exist_ok=True) - - if args.project: - csproj = (repo_root / args.project).resolve() - else: - csproj = find_csproj_by_keyword(repo_root, ["webgateway", "gateway"]) - if csproj is None or not csproj.exists(): - print("Could not locate web gateway project. Pass --project .") - return 2 - - publish_args = [ - "publish", - str(csproj), - "-c", - args.configuration, - "-r", - args.runtime, - "--self-contained", - "true" if args.self_contained else "false", - "-p:RestoreIgnoreFailedSources=true", - "-p:NuGetAudit=false", - "-o", - str(output_dir), - ] - code = run("dotnet", publish_args, repo_root, env=dotnet_env(repo_root)) - if code != 0: - return code - - if not args.skip_web_assets: - if args.web_build_dir: - web_build_dir = (repo_root / args.web_build_dir).resolve() - else: - web_build_dir = next((p.parent for p in repo_root.rglob("package.json") if (p.parent / "build").exists()), None) - if web_build_dir is not None: - web_build_dir = web_build_dir / "build" - - if web_build_dir is None or not web_build_dir.exists(): - print("Web assets not found. Skip with --skip-web-assets or pass --web-build-dir.") - else: - web_out = output_dir / "wwwroot" - web_out.mkdir(parents=True, exist_ok=True) - for item in web_build_dir.iterdir(): - dst = web_out / item.name - if item.is_dir(): - if dst.exists(): - shutil.rmtree(dst) - shutil.copytree(item, dst) - else: - shutil.copy2(item, dst) - print(f"Copied web assets: {web_out}") - - print(f"Publish completed: {output_dir}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/run-webgateway.py b/Journal.DevTool/scripts/run-webgateway.py deleted file mode 100644 index 3f35dbc..0000000 --- a/Journal.DevTool/scripts/run-webgateway.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -from pathlib import Path - -from script_common import dotnet_env, find_csproj_by_keyword, resolve_repo_root, run - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run gateway in dev or output mode") - parser.add_argument("--configuration", choices=["Release", "Debug"], default="Release") - parser.add_argument("--urls", default="http://0.0.0.0:5180") - parser.add_argument("--project-root", default=None, help="Runtime project root exposed via SDT_PROJECT_ROOT") - parser.add_argument("--mode", choices=["Dev", "Output"], default="Dev") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--project", default=None, help="Gateway csproj path") - parser.add_argument("--output-exe", default=None, help="Published gateway executable path") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - effective_project_root = Path(args.project_root).resolve() if args.project_root else repo_root - if not effective_project_root.exists(): - print(f"Project root does not exist: {effective_project_root}") - return 2 - - env = dotnet_env(repo_root) - env["SDT_PROJECT_ROOT"] = str(effective_project_root) - - if args.mode == "Output": - exe_path = Path(args.output_exe).resolve() if args.output_exe else (repo_root / "output" / "webgateway" / ("webgateway.exe" if os.name == "nt" else "webgateway")) - if not exe_path.exists(): - print(f"Output executable not found: {exe_path}") - return 2 - return run(str(exe_path), ["--urls", args.urls], repo_root, env=env) - - if args.project: - csproj = (repo_root / args.project).resolve() - else: - csproj = find_csproj_by_keyword(repo_root, ["webgateway", "gateway"]) - if csproj is None or not csproj.exists(): - print("Could not locate gateway project. Pass --project .") - return 2 - - run_args = [ - "run", - "--project", - str(csproj), - "-c", - args.configuration, - "--no-launch-profile", - "--urls", - args.urls, - "-p:RestoreIgnoreFailedSources=true", - "-p:NuGetAudit=false", - ] - return run("dotnet", run_args, repo_root, env=env) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/scripts/script-common.ps1 b/Journal.DevTool/scripts/script-common.ps1 deleted file mode 100644 index 36e681e..0000000 --- a/Journal.DevTool/scripts/script-common.ps1 +++ /dev/null @@ -1,124 +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 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-Path (Join-Path $overridePath "devtool.json")) { - return $overridePath - } - } - - foreach ($start in $candidateStarts) { - $cursor = [System.IO.Path]::GetFullPath($start) - while (-not [string]::IsNullOrWhiteSpace($cursor)) { - if (Test-Path (Join-Path $cursor "devtool.json")) { - 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 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 diff --git a/Journal.DevTool/scripts/script_common.py b/Journal.DevTool/scripts/script_common.py deleted file mode 100644 index 76ac87d..0000000 --- a/Journal.DevTool/scripts/script_common.py +++ /dev/null @@ -1,315 +0,0 @@ -#!/usr/bin/env python3 -import hashlib -import json -import os -import pathlib -import shutil -import subprocess -import sys -from typing import Dict, Iterable, List, Sequence - - -PROXY_VARS = [ - "HTTP_PROXY", - "HTTPS_PROXY", - "ALL_PROXY", - "http_proxy", - "https_proxy", - "all_proxy", - "GIT_HTTP_PROXY", - "GIT_HTTPS_PROXY", - "PIP_NO_INDEX", -] - - -def resolve_repo_root(start: str | None = None) -> pathlib.Path: - base = pathlib.Path(start or os.getcwd()).resolve() - - # 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: - p.mkdir(parents=True, exist_ok=True) - - -def clean_proxy_env(env: Dict[str, str]) -> None: - for k in PROXY_VARS: - env.pop(k, None) - - -def dotnet_env(repo_root: pathlib.Path) -> Dict[str, str]: - env = dict(os.environ) - clean_proxy_env(env) - dotnet_cli_home = repo_root / ".dotnet_home" - nuget_packages = repo_root / ".nuget" / "packages" - nuget_http_cache = repo_root / ".nuget" / "http-cache" - ensure_dirs([dotnet_cli_home, nuget_packages, nuget_http_cache]) - env["DOTNET_CLI_HOME"] = str(dotnet_cli_home) - env["NUGET_PACKAGES"] = str(nuget_packages) - env["NUGET_HTTP_CACHE_PATH"] = str(nuget_http_cache) - env["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1" - env["DOTNET_ADD_GLOBAL_TOOLS_TO_PATH"] = "0" - env["DOTNET_GENERATE_ASPNET_CERTIFICATE"] = "0" - env["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1" - env["NUGET_CERT_REVOCATION_MODE"] = "offline" - return env - - -def pip_env(repo_root: pathlib.Path) -> Dict[str, str]: - env = dict(os.environ) - clean_proxy_env(env) - pip_cache = repo_root / ".pip" / "cache" - pip_tmp = repo_root / ".tmp" / "pip-temp" - ensure_dirs([pip_cache, pip_tmp]) - env["PIP_CACHE_DIR"] = str(pip_cache) - env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" - env["PIP_DEFAULT_TIMEOUT"] = "30" - env["PIP_RETRIES"] = "2" - env["TEMP"] = str(pip_tmp) - env["TMP"] = str(pip_tmp) - return env - - -def run(command: str, args: List[str], cwd: pathlib.Path, env: Dict[str, str] | None = None) -> int: - resolved = resolve_command(command) - try: - proc = subprocess.run([resolved, *args], cwd=str(cwd), env=env, check=False) - return proc.returncode - except FileNotFoundError: - print(f"Command not found: {resolved}", file=sys.stderr) - return 127 - - -def run_capture(command: str, args: Sequence[str], cwd: pathlib.Path, env: Dict[str, str] | None = None) -> tuple[int, str, str]: - resolved = resolve_command(command) - try: - proc = subprocess.run( - [resolved, *args], - cwd=str(cwd), - env=env, - capture_output=True, - text=True, - check=False, - ) - return proc.returncode, proc.stdout, proc.stderr - except FileNotFoundError: - return 127, "", f"Command not found: {resolved}" - - -def resolve_command(command: str) -> str: - if not command: - return command - - if os.name != "nt": - return command - - if any(sep in command for sep in ("\\", "/")): - return command - - if pathlib.Path(command).suffix: - found = shutil.which(command) - return found or command - - candidates = [] - lowered = command.lower() - if lowered in ("npm", "npx", "pnpm", "yarn", "tauri"): - candidates.extend([f"{command}.cmd", f"{command}.exe", f"{command}.bat", command]) - else: - candidates.append(command) - - for c in candidates: - found = _which_windows(c) - if found: - name = pathlib.Path(found).name.lower() - if name in ("npm", "npx", "pnpm", "yarn", "tauri"): - shim = pathlib.Path(found).with_name(name + ".cmd") - if shim.exists(): - return str(shim) - return found - - if lowered in ("npm", "npx", "pnpm", "yarn"): - node = _which_windows("node.exe") or _which_windows("node") - if node: - node_dir = pathlib.Path(node).parent - shim = node_dir / f"{lowered}.cmd" - if shim.exists(): - return str(shim) - - return candidates[-1] - - -def _hint_matches(root: pathlib.Path, hint: str) -> bool: - h = hint.strip() - if not h: - return False - - has_glob = any(ch in h for ch in ("*", "?", "[")) - if has_glob: - # Match both anywhere in root and directly at root-level for common hints like "*.sln". - if any(root.glob(h)): - return True - return any(root.rglob(h)) - - marker = root / h - if marker.exists(): - return True - - # If hint is just a filename marker, look bounded in tree. - if not any(sep in h for sep in ("\\", "/")): - return any(p.name == h for p in root.rglob(h)) - - return False - - -def _expand_windows_path_segment(segment: str) -> str: - expanded = segment - # Expand %VAR% tokens repeatedly for nested references. - for _ in range(4): - next_value = os.path.expandvars(expanded) - if next_value == expanded: - break - expanded = next_value - return expanded - - -def _which_windows(command: str) -> str | None: - found = shutil.which(command) - if found: - return found - - if os.name != "nt": - return None - - path_value = os.environ.get("PATH", "") - pathext = os.environ.get("PATHEXT", ".COM;.EXE;.BAT;.CMD") - exts = [e.lower() for e in pathext.split(";") if e] - - has_ext = pathlib.Path(command).suffix != "" - names = [command] if has_ext else [command, *(command + e.lower() for e in exts)] - - for raw_segment in path_value.split(os.pathsep): - segment = _expand_windows_path_segment(raw_segment.strip()) - if not segment: - continue - base = pathlib.Path(segment) - for name in names: - candidate = base / name - if candidate.exists(): - return str(candidate) - - return None - - -def sha256_files(paths: Iterable[pathlib.Path]) -> str: - h = hashlib.sha256() - for p in paths: - if not p.exists(): - continue - h.update(p.read_bytes()) - return h.hexdigest() - - -def first_existing(paths: Iterable[pathlib.Path]) -> pathlib.Path | None: - for p in paths: - if p.exists(): - return p - return None - - -def find_csproj(repo_root: pathlib.Path, hints: Sequence[str] | None = None) -> pathlib.Path | None: - if hints: - for hint in hints: - candidate = (repo_root / hint).resolve() - if candidate.exists() and candidate.suffix.lower() == ".csproj": - return candidate - - csprojs = sorted(repo_root.rglob("*.csproj")) - if not csprojs: - return None - if len(csprojs) == 1: - return csprojs[0] - return None - - -def find_csproj_by_keyword(repo_root: pathlib.Path, keywords: Sequence[str]) -> pathlib.Path | None: - kws = [k.lower() for k in keywords] - matches: list[pathlib.Path] = [] - for p in repo_root.rglob("*.csproj"): - text = str(p).lower() - if any(k in text for k in kws): - matches.append(p) - if len(matches) == 1: - return matches[0] - return None - - -def find_node_app_root(repo_root: pathlib.Path, preferred: str | None = None) -> pathlib.Path | None: - if preferred: - p = (repo_root / preferred).resolve() - if (p / "package.json").exists(): - return p - - direct = repo_root / "package.json" - if direct.exists(): - return repo_root - - tauri_candidates = [] - for package_json in repo_root.rglob("package.json"): - d = package_json.parent - if (d / "src-tauri" / "tauri.conf.json").exists(): - tauri_candidates.append(d) - if len(tauri_candidates) == 1: - return tauri_candidates[0] - - all_candidates = [p.parent for p in repo_root.rglob("package.json")] - if len(all_candidates) == 1: - return all_candidates[0] - return None - - -def newest_file(search_root: pathlib.Path, pattern: str) -> pathlib.Path | None: - if not search_root.exists(): - return None - files = [p for p in search_root.rglob(pattern) if p.is_file() and "\\obj\\" not in str(p).replace("/", "\\")] - if not files: - return None - files.sort(key=lambda p: p.stat().st_mtime, reverse=True) - return files[0] diff --git a/Journal.DevTool/scripts/sync-output.py b/Journal.DevTool/scripts/sync-output.py deleted file mode 100644 index 8a28a2b..0000000 --- a/Journal.DevTool/scripts/sync-output.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import shutil -from pathlib import Path - -from script_common import newest_file, resolve_repo_root - - -def copy_tree_contents(src: Path, dst: Path) -> None: - dst.mkdir(parents=True, exist_ok=True) - for item in src.iterdir(): - target = dst / item.name - if item.is_dir(): - if target.exists(): - shutil.rmtree(target) - shutil.copytree(item, target) - else: - shutil.copy2(item, target) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Sync newest built assets into output folder") - parser.add_argument("--repo-root", default=None) - parser.add_argument("--output-dir", default="output") - parser.add_argument("--web-build-dir", default=None, help="Path to web build output") - parser.add_argument("--sidecar-bin-dir", default=None, help="Path to sidecar bin root") - parser.add_argument("--gateway-bin-dir", default=None, help="Path to gateway bin root") - parser.add_argument("--tauri-target-dir", default=None, help="Path to tauri target root") - args = parser.parse_args() - - repo_root = resolve_repo_root(args.repo_root) - output_dir = (repo_root / args.output_dir).resolve() - output_dir.mkdir(parents=True, exist_ok=True) - - web_build = (repo_root / args.web_build_dir).resolve() if args.web_build_dir else None - if web_build is None: - web_build = next((p for p in repo_root.rglob("build") if (p.parent / "package.json").exists()), None) - if web_build is not None and web_build.exists(): - web_out = output_dir / "webgateway" / "wwwroot" - copy_tree_contents(web_build, web_out) - print(f"Synced web assets -> {web_out}") - - sidecar_bin = (repo_root / args.sidecar_bin_dir).resolve() if args.sidecar_bin_dir else None - if sidecar_bin is None: - sidecar_proj = next((p.parent for p in repo_root.rglob("*.csproj") if "sidecar" in str(p).lower()), None) - sidecar_bin = sidecar_proj / "bin" if sidecar_proj else None - if sidecar_bin is not None: - sidecar_pattern = "*.exe" if os.name == "nt" else "*" - sidecar_exe = newest_file(sidecar_bin, sidecar_pattern) - if sidecar_exe is not None: - copy_tree_contents(sidecar_exe.parent, output_dir) - print(f"Synced sidecar -> {output_dir}") - - gateway_bin = (repo_root / args.gateway_bin_dir).resolve() if args.gateway_bin_dir else None - if gateway_bin is None: - gateway_proj = next((p.parent for p in repo_root.rglob("*.csproj") if "gateway" in str(p).lower()), None) - gateway_bin = gateway_proj / "bin" if gateway_proj else None - if gateway_bin is not None: - gateway_pattern = "*.exe" if os.name == "nt" else "*" - gw_exe = newest_file(gateway_bin, gateway_pattern) - if gw_exe is not None: - gw_out = output_dir / "webgateway" - copy_tree_contents(gw_exe.parent, gw_out) - print(f"Synced gateway -> {gw_out}") - - tauri_target = (repo_root / args.tauri_target_dir).resolve() if args.tauri_target_dir else None - if tauri_target is None: - tauri_target = next((p for p in repo_root.rglob("src-tauri") if (p / "target").exists()), None) - tauri_target = tauri_target / "target" if tauri_target else None - if tauri_target is not None: - app_exe = newest_file(tauri_target, "*.exe") - if app_exe is not None: - shutil.copy2(app_exe, output_dir / app_exe.name) - print(f"Synced desktop app ({app_exe.name}) -> {output_dir}") - - print("Sync complete.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/Journal.DevTool/tests/DevTool.Tests/ActionRunnerLegacyPwshTests.cs b/Journal.DevTool/tests/DevTool.Tests/ActionRunnerLegacyPwshTests.cs deleted file mode 100644 index af62e45..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/ActionRunnerLegacyPwshTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Xunit; - -namespace DevTool.Tests; - -public sealed class ActionRunnerLegacyPwshTests -{ - [Fact] - public async Task LegacyPwshTarget_ReroutesToPythonScript_WhenPs1Missing() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-actionrunner-" + Guid.NewGuid().ToString("N")); - var scripts = Path.Combine(root, "scripts"); - Directory.CreateDirectory(scripts); - File.WriteAllText(Path.Combine(scripts, "publish-app.py"), "print('rerouted')"); - - var step = new WorkflowStep - { - Id = "legacy", - Label = "legacy", - Command = "pwsh", - Args = ["-NoProfile", "-File", "scripts/publish-app.ps1", "-Target", "web"], - WorkingDir = "." - }; - - var runner = new ActionRunner(); - var run = await runner.RunStepAsync(step, root, (_, _) => { }); - - Assert.True(run.Success); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/CommandResolverTests.cs b/Journal.DevTool/tests/DevTool.Tests/CommandResolverTests.cs deleted file mode 100644 index ac995eb..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/CommandResolverTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Sdt.Core; -using Sdt.Config; -using Xunit; - -namespace DevTool.Tests; - -public sealed class CommandResolverTests -{ - [Fact] - public void Resolve_Npm_OnWindows_UsesCmdShim() - { - var resolved = CommandResolver.Resolve("npm"); - if (OperatingSystem.IsWindows()) - Assert.True( - resolved.EndsWith("npm.cmd", StringComparison.OrdinalIgnoreCase) || - resolved.EndsWith("\\npm", StringComparison.OrdinalIgnoreCase) || - string.Equals(resolved, "npm.cmd", StringComparison.OrdinalIgnoreCase), - $"Resolved npm path was '{resolved}'"); - else - Assert.Equal("npm", resolved); - } - - [Fact] - public void Resolve_PathOrExtension_Unchanged() - { - Assert.Equal("C:\\tools\\npm.cmd", CommandResolver.Resolve("C:\\tools\\npm.cmd")); - var resolved = CommandResolver.Resolve("dotnet.exe"); - if (OperatingSystem.IsWindows() && Path.IsPathRooted(resolved)) - Assert.EndsWith("dotnet.exe", resolved, StringComparison.OrdinalIgnoreCase); - else - Assert.Equal("dotnet.exe", resolved); - } - - [Fact] - public void Resolve_Npm_PrefersCmdShim_WhenBothBareAndCmdExist() - { - if (!OperatingSystem.IsWindows()) - return; - - var temp = Path.Combine(Path.GetTempPath(), "sdt-cmdresolve-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(temp); - File.WriteAllText(Path.Combine(temp, "npm"), ""); - File.WriteAllText(Path.Combine(temp, "npm.cmd"), "@echo off"); - - var originalPath = Environment.GetEnvironmentVariable("PATH"); - try - { - Environment.SetEnvironmentVariable("PATH", temp); - var resolved = CommandResolver.Resolve("npm"); - Assert.EndsWith("npm.cmd", resolved, StringComparison.OrdinalIgnoreCase); - } - finally - { - Environment.SetEnvironmentVariable("PATH", originalPath); - try { Directory.Delete(temp, recursive: true); } catch { } - } - } - - [Fact] - public void ResolveWithTrace_ConfiguredOverride_IsUsed() - { - if (!OperatingSystem.IsWindows()) - return; - - var temp = Path.Combine(Path.GetTempPath(), "sdt-cmdresolve-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(temp); - var overridePath = Path.Combine(temp, "npm.cmd"); - File.WriteAllText(overridePath, "@echo off"); - - var cfg = new DevToolConfig - { - Tooling = new ToolingConfig - { - Tools = - [ - new ToolInstallDefinition - { - Tool = "npm", - Executables = [overridePath] - } - ] - } - }; - - var resolved = CommandResolver.ResolveWithTrace("npm", cfg, "npm"); - Assert.Equal(CommandResolutionSource.ConfiguredOverride, resolved.Source); - Assert.EndsWith("npm.cmd", resolved.Resolved, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void ResolveWithTrace_ExpandsWindowsPathTokens() - { - if (!OperatingSystem.IsWindows()) - return; - - var nvmHome = Path.Combine(Path.GetTempPath(), "sdt-nvmhome-" + Guid.NewGuid().ToString("N")); - var nvmLink = Path.Combine(Path.GetTempPath(), "sdt-nvmlink-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(nvmHome); - Directory.CreateDirectory(nvmLink); - File.WriteAllText(Path.Combine(nvmLink, "npm.cmd"), "@echo off"); - - var originalPath = Environment.GetEnvironmentVariable("PATH"); - var originalHome = Environment.GetEnvironmentVariable("NVM_HOME"); - var originalLink = Environment.GetEnvironmentVariable("NVM_SYMLINK"); - try - { - Environment.SetEnvironmentVariable("NVM_HOME", nvmHome); - Environment.SetEnvironmentVariable("NVM_SYMLINK", nvmLink); - Environment.SetEnvironmentVariable("PATH", "%NVM_HOME%;%NVM_SYMLINK%"); - - var result = CommandResolver.ResolveWithTrace("npm"); - Assert.True(result.Source is CommandResolutionSource.Shim or CommandResolutionSource.Path); - Assert.EndsWith("npm.cmd", result.Resolved, StringComparison.OrdinalIgnoreCase); - } - finally - { - Environment.SetEnvironmentVariable("PATH", originalPath); - Environment.SetEnvironmentVariable("NVM_HOME", originalHome); - Environment.SetEnvironmentVariable("NVM_SYMLINK", originalLink); - try { Directory.Delete(nvmHome, recursive: true); } catch { } - try { Directory.Delete(nvmLink, recursive: true); } catch { } - } - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/ConfigBootstrapperTests.cs b/Journal.DevTool/tests/DevTool.Tests/ConfigBootstrapperTests.cs deleted file mode 100644 index e0124f5..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/ConfigBootstrapperTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Sdt.Config; -using Xunit; - -namespace DevTool.Tests; - -public sealed class ConfigBootstrapperTests -{ - [Fact] - public void Scan_DetectsDotnetAndNode() - { - var root = CreateTempDir(); - File.WriteAllText(Path.Combine(root, "sample.sln"), ""); - File.WriteAllText(Path.Combine(root, "package.json"), "{}"); - - var scan = ConfigBootstrapper.Scan(root); - - Assert.Contains("dotnet", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase); - Assert.Contains("node", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase); - Assert.Contains("npm", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase); - } - - [Fact] - public void Scan_DetectsPythonFromScriptsDirectory() - { - var root = CreateTempDir(); - var scripts = Path.Combine(root, "scripts"); - Directory.CreateDirectory(scripts); - File.WriteAllText(Path.Combine(scripts, "publish-app.py"), "print('ok')"); - - var scan = ConfigBootstrapper.Scan(root); - Assert.Contains("python", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase); - } - - [Fact] - public void BuildDefaultConfig_ProducesWorkflowsAndDebugSection() - { - var scan = new BootstrapScanResult( - ProjectRoot: Path.GetTempPath(), - ProjectName: "demo", - ToolFamilies: ["dotnet", "git"], - NodeWorkingDir: null, - PythonRequirementsFile: null, - HasDockerCompose: false, - RootHints: ["*.sln"]); - - var cfg = ConfigBootstrapper.BuildDefaultConfig(scan); - - Assert.NotNull(cfg.Debug); - Assert.Contains(cfg.Workflows, w => w.Id == "build"); - Assert.Contains(cfg.Workflows, w => w.Id == "repo-health"); - Assert.False(cfg.Debug!.Diagnostics.IncludeAllEnv); - Assert.Contains("SDT_LOG_LEVEL", cfg.Debug.Diagnostics.CaptureEnvKeys); - } - - [Fact] - public void BuildDefaultConfig_IncludesScriptDrivenWorkflow_WhenHelpersExist() - { - var root = CreateTempDir(); - var scripts = Path.Combine(root, "scripts"); - Directory.CreateDirectory(scripts); - File.WriteAllText(Path.Combine(scripts, "publish-app.py"), "print('ok')"); - File.WriteAllText(Path.Combine(scripts, "publish-sidecar.py"), "print('ok')"); - - var scan = ConfigBootstrapper.Scan(root); - var cfg = ConfigBootstrapper.BuildDefaultConfig(scan); - - Assert.Contains(cfg.Workflows, w => w.Id == "web"); - Assert.Contains(cfg.Workflows, w => w.Id == "sidecar"); - } - - [Fact] - public void WriteDefaultConfig_WritesDevtoolJson() - { - var root = CreateTempDir(); - var scan = new BootstrapScanResult( - ProjectRoot: root, - ProjectName: "demo", - ToolFamilies: ["dotnet"], - NodeWorkingDir: null, - PythonRequirementsFile: null, - HasDockerCompose: false, - RootHints: ["*.sln"]); - var cfg = ConfigBootstrapper.BuildDefaultConfig(scan); - - var path = ConfigBootstrapper.WriteDefaultConfig(root, cfg); - - Assert.True(File.Exists(path)); - } - - [Fact] - public void Scan_IgnoresExcludedDirectories_ForToolDetection() - { - var root = CreateTempDir(); - var nodeModules = Path.Combine(root, "node_modules", "nested"); - Directory.CreateDirectory(nodeModules); - File.WriteAllText(Path.Combine(nodeModules, "package.json"), "{}"); - - var scan = ConfigBootstrapper.Scan(root); - Assert.DoesNotContain("node", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase); - } - - private static string CreateTempDir() - { - var path = Path.Combine(Path.GetTempPath(), "sdt-bootstrap-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(path); - return path; - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/ConfigDoctorAutoFixServiceTests.cs b/Journal.DevTool/tests/DevTool.Tests/ConfigDoctorAutoFixServiceTests.cs deleted file mode 100644 index 9e51eec..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/ConfigDoctorAutoFixServiceTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Xunit; - -namespace DevTool.Tests; - -public sealed class ConfigDoctorAutoFixServiceTests -{ - [Fact] - public void FindMissingWorkingDirectories_ReturnsMissingPaths() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-autofix-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - Directory.CreateDirectory(Path.Combine(root, "exists")); - - var cfg = new DevToolConfig - { - Workflows = - [ - new WorkflowDefinition - { - Id = "build", - Label = "Build", - Steps = - [ - new WorkflowStep { Id = "s1", Label = "S1", Action = "dotnet-build", WorkingDir = "exists" }, - new WorkflowStep { Id = "s2", Label = "S2", Action = "dotnet-build", WorkingDir = "missing/sub" } - ] - } - ] - }; - - var service = new ConfigDoctorAutoFixService(); - var missing = service.FindMissingWorkingDirectories(cfg, root); - - Assert.Single(missing); - Assert.EndsWith(Path.Combine("missing", "sub"), missing[0], StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void CreateMissingWorkingDirectories_CreatesPaths() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-autofix-" + Guid.NewGuid().ToString("N")); - var path = Path.Combine(root, "a", "b", "c"); - Directory.CreateDirectory(root); - - var service = new ConfigDoctorAutoFixService(); - var result = service.CreateMissingWorkingDirectories([path]); - - Assert.True(result.Success); - Assert.True(Directory.Exists(path)); - Assert.Equal(1, result.CreatedDirectories); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/ConfigDoctorServiceTests.cs b/Journal.DevTool/tests/DevTool.Tests/ConfigDoctorServiceTests.cs deleted file mode 100644 index 0b05562..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/ConfigDoctorServiceTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Xunit; - -namespace DevTool.Tests; - -public sealed class ConfigDoctorServiceTests -{ - [Fact] - public async Task TargetsOnly_Config_IsFlaggedAsFail() - { - var config = new DevToolConfig - { - Targets = - [ - new BuildTarget - { - Id = "build", - Label = "Build", - Command = "dotnet", - Args = ["build"] - } - ], - Workflows = [] - }; - - var doctor = new ConfigDoctorService(new AlwaysAvailableProbe(), new RequirementResolver()); - var report = await doctor.RunAsync(config, Directory.GetCurrentDirectory()); - - Assert.Contains(report.Checks, c => c.Name == "Legacy schema" && c.Status == DoctorStatus.Fail); - } - - [Fact] - public async Task MissingTool_IsReportedWithFix() - { - var config = new DevToolConfig - { - Workflows = - [ - new WorkflowDefinition - { - Id = "build", - Label = "Build", - Steps = - [ - new WorkflowStep { Id = "s1", Label = "Build", Command = "dotnet", Args = ["build"] } - ] - } - ] - }; - - var doctor = new ConfigDoctorService(new AlwaysMissingProbe(), new RequirementResolver()); - var report = await doctor.RunAsync(config, Directory.GetCurrentDirectory()); - - Assert.Contains(report.Checks, c => - c.Name.Equals("Tool: dotnet", StringComparison.OrdinalIgnoreCase) && - c.Status == DoctorStatus.Fail && - !string.IsNullOrWhiteSpace(c.Fix)); - } - - private sealed class AlwaysAvailableProbe : IToolProbe - { - public Task ProbeAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - => Task.FromResult(new ProbeResult(tool, true, Version: "1.0.0")); - } - - private sealed class AlwaysMissingProbe : IToolProbe - { - public Task ProbeAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - => Task.FromResult(new ProbeResult(tool, false, Details: "Fallback: unresolved command")); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/DebugConfigTests.cs b/Journal.DevTool/tests/DevTool.Tests/DebugConfigTests.cs deleted file mode 100644 index e77a53a..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/DebugConfigTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Text.Json; -using Sdt.Config; -using Xunit; - -namespace DevTool.Tests; - -public sealed class DebugConfigTests -{ - [Fact] - public void DebugSectionAbsent_DeserializesWithSafeDefaults() - { - const string json = """ - { - "name": "Test", - "version": "1.0.0", - "workflows": [] - } - """; - - var cfg = JsonSerializer.Deserialize(json, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - AllowTrailingCommas = true, - ReadCommentHandling = JsonCommentHandling.Skip - }); - - Assert.NotNull(cfg); - Assert.Null(cfg!.Debug); - } - - [Fact] - public void DebugDiagnostics_DefaultOutputDir_IsSdtDebug() - { - var options = new DebugDiagnosticsOptions(); - Assert.True(options.Enabled); - Assert.True(options.BundleOnFailure); - Assert.Equal(".sdt/debug", options.OutputDir); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/DebugServicesTests.cs b/Journal.DevTool/tests/DevTool.Tests/DebugServicesTests.cs deleted file mode 100644 index 31b5f7f..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/DebugServicesTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Sdt.Core.Debug; -using Sdt.Runner; -using System.Text.Json; -using Xunit; - -namespace DevTool.Tests; - -public sealed class DebugServicesTests -{ - [Fact] - public async Task DebugRunner_MissingPrereqDeclined_ReturnsUserDeclined() - { - var runner = new DebugProfileRunner( - new FakeProbeService(false), - new FakeInstallerService(true)); - - var profile = new DebugProfileDefinition - { - Id = "p1", - Label = "Profile", - Type = "python", - Command = "python", - Args = ["--version"], - Requires = [new ToolRequirement { Tool = "python", InstallPolicy = InstallPolicy.Prompt }] - }; - - var result = await runner.RunAsync( - profile, - new DevToolConfig(), - Directory.GetCurrentDirectory(), - verbose: false, - confirmInstallAsync: (_, _) => Task.FromResult(false), - onOutput: (_, _) => { }); - - Assert.False(result.Success); - Assert.Equal(ExecutionStopReason.UserDeclined, result.StopReason); - } - - [Fact] - public async Task DiagnosticsBundle_WritesFiles() - { - var service = new DiagnosticsBundleService(); - var root = Path.Combine(Path.GetTempPath(), "sdt-diag-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - - var request = new DiagnosticsBundleRequest( - Category: "workflow", - ProjectRoot: root, - SummaryMessage: "failed", - OutputLines: ["hello"], - WorkflowSteps: [], - Probes: [], - DiagnosticsOptions: new DebugDiagnosticsOptions { OutputDir = ".sdt/debug" }, - Config: new DevToolConfig(), - StopReason: ExecutionStopReason.CommandFailed); - - var result = await service.WriteBundleAsync(request); - Assert.True(result.Success); - Assert.True(File.Exists(Path.Combine(result.BundleDirectory, "summary.json"))); - Assert.True(File.Exists(Path.Combine(result.BundleDirectory, "output.log"))); - } - - [Fact] - public async Task DiagnosticsBundle_EmptyAllowlist_CapturesNoEnvByDefault() - { - var service = new DiagnosticsBundleService(); - var root = Path.Combine(Path.GetTempPath(), "sdt-diag-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - - var request = new DiagnosticsBundleRequest( - Category: "workflow", - ProjectRoot: root, - SummaryMessage: "failed", - OutputLines: [], - WorkflowSteps: [], - Probes: [], - DiagnosticsOptions: new DebugDiagnosticsOptions - { - OutputDir = ".sdt/debug", - IncludeAllEnv = false, - CaptureEnvKeys = [] - }, - Config: new DevToolConfig(), - StopReason: ExecutionStopReason.CommandFailed); - - var result = await service.WriteBundleAsync(request); - Assert.True(result.Success); - - var envJson = await File.ReadAllTextAsync(Path.Combine(result.BundleDirectory, "env.json")); - using var doc = JsonDocument.Parse(envJson); - Assert.Empty(doc.RootElement.EnumerateObject()); - } - - [Fact] - public async Task DiagnosticsBundle_Allowlist_CapturesOnlyListedKeys() - { - var service = new DiagnosticsBundleService(); - var root = Path.Combine(Path.GetTempPath(), "sdt-diag-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - - Environment.SetEnvironmentVariable("SDT_TEST_ENV_A", "A"); - Environment.SetEnvironmentVariable("SDT_TEST_ENV_B", "B"); - - var request = new DiagnosticsBundleRequest( - Category: "workflow", - ProjectRoot: root, - SummaryMessage: "failed", - OutputLines: [], - WorkflowSteps: [], - Probes: [], - DiagnosticsOptions: new DebugDiagnosticsOptions - { - OutputDir = ".sdt/debug", - IncludeAllEnv = false, - CaptureEnvKeys = ["SDT_TEST_ENV_A"] - }, - Config: new DevToolConfig(), - StopReason: ExecutionStopReason.CommandFailed); - - var result = await service.WriteBundleAsync(request); - Assert.True(result.Success); - - var envJson = await File.ReadAllTextAsync(Path.Combine(result.BundleDirectory, "env.json")); - using var doc = JsonDocument.Parse(envJson); - Assert.True(doc.RootElement.TryGetProperty("SDT_TEST_ENV_A", out _)); - Assert.False(doc.RootElement.TryGetProperty("SDT_TEST_ENV_B", out _)); - } - - [Fact] - public async Task DebugRunner_EmitsRunEvents() - { - var runner = new DebugProfileRunner( - new FakeProbeService(true), - new FakeInstallerService(true)); - - var profile = new DebugProfileDefinition - { - Id = "p1", - Label = "Profile", - Type = "python", - Command = "python", - Args = ["--version"], - Requires = [new ToolRequirement { Tool = "python", InstallPolicy = InstallPolicy.Prompt }] - }; - - var events = new List(); - var result = await runner.RunAsync( - profile, - new DevToolConfig(), - Directory.GetCurrentDirectory(), - verbose: false, - confirmInstallAsync: (_, _) => Task.FromResult(false), - onOutput: (_, _) => { }, - onEvent: events.Add); - - Assert.True(result.Success); - Assert.Contains(events, e => e.Type == RunEventType.DebugStarted); - Assert.Contains(events, e => e.Type == RunEventType.DebugCommandStarted); - Assert.Contains(events, e => e.Type == RunEventType.DebugCommandCompleted); - Assert.Contains(events, e => e.Type == RunEventType.DebugCompleted && e.Success == true); - } - - private sealed class FakeProbeService(bool isAvailable) : IToolProbe - { - public Task ProbeAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - => Task.FromResult(new ProbeResult(tool, isAvailable, Version: isAvailable ? "1.0.0" : null)); - } - - private sealed class FakeInstallerService(bool success) : IPrereqInstaller - { - public Task GetInstallPlanAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - => Task.FromResult(new InstallPlan(tool, Supported: true, "test", [new InstallCommand("echo", ["ok"])])); - - public Task RunInstallAsync( - InstallCommand command, - string projectRoot, - Action onOutput, - CancellationToken cancellationToken = default) - => Task.FromResult(new RunResult(success ? 0 : 1, TimeSpan.FromMilliseconds(5))); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/DevShellScriptTests.cs b/Journal.DevTool/tests/DevTool.Tests/DevShellScriptTests.cs deleted file mode 100644 index 9e5e582..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/DevShellScriptTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Diagnostics; -using System.Text.Json; -using Xunit; - -namespace DevTool.Tests; - -public sealed class DevShellScriptTests -{ - [Theory] - [InlineData("pwsh")] - [InlineData("bash")] - [InlineData("zsh")] - [InlineData("cmd")] - public async Task DevShellExport_ReturnsSuccess_ForSupportedShells(string shell) - { - var python = ResolvePython(); - var result = await RunAsync( - python, - ["scripts/dev_shell.py", "export", "--shell", shell, "--json"]); - - Assert.Equal(0, result.ExitCode); - using var doc = JsonDocument.Parse(result.StdOut); - Assert.True(doc.RootElement.TryGetProperty("projectRoot", out _)); - Assert.True(doc.RootElement.TryGetProperty("env", out _)); - } - - [Fact] - public async Task DevShellExport_InvalidShell_ReturnsExitCode3() - { - var python = ResolvePython(); - var result = await RunAsync( - python, - ["scripts/dev_shell.py", "export", "--shell", "fish"]); - - Assert.Equal(3, result.ExitCode); - } - - [Fact] - public async Task DevShellDoctor_ReturnsJson() - { - var python = ResolvePython(); - var result = await RunAsync( - python, - ["scripts/dev_shell.py", "doctor"]); - - Assert.Equal(0, result.ExitCode); - using var doc = JsonDocument.Parse(result.StdOut); - Assert.True(doc.RootElement.TryGetProperty("repo_root", out _)); - } - - private static string ResolvePython() - { - var candidates = OperatingSystem.IsWindows() - ? new[] { "python", "py" } - : new[] { "python3", "python" }; - - foreach (var candidate in candidates) - { - try - { - using var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = candidate, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - } - }; - process.StartInfo.ArgumentList.Add("--version"); - process.Start(); - process.WaitForExit(2000); - if (process.ExitCode == 0) - return candidate; - } - catch - { - } - } - - throw new InvalidOperationException("Python executable not found."); - } - - private static async Task<(int ExitCode, string StdOut, string StdErr)> RunAsync(string command, IReadOnlyList args) - { - var psi = new ProcessStartInfo - { - FileName = command, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = RepoRoot(), - }; - foreach (var arg in args) - psi.ArgumentList.Add(arg); - - using var process = new Process { StartInfo = psi }; - process.Start(); - var stdout = await process.StandardOutput.ReadToEndAsync(); - var stderr = await process.StandardError.ReadToEndAsync(); - await process.WaitForExitAsync(); - return (process.ExitCode, stdout, stderr); - } - - private static string RepoRoot() - { - var dir = new DirectoryInfo(AppContext.BaseDirectory); - while (dir is not null) - { - if (File.Exists(Path.Combine(dir.FullName, "devtool.json"))) - return dir.FullName; - dir = dir.Parent; - } - - throw new InvalidOperationException("Could not locate repo root."); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/DevTool.Tests.csproj b/Journal.DevTool/tests/DevTool.Tests/DevTool.Tests.csproj deleted file mode 100644 index 957d8c0..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/DevTool.Tests.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - net10.0 - enable - enable - false - - - - - - - - - - - - diff --git a/Journal.DevTool/tests/DevTool.Tests/LegacyModeTests.cs b/Journal.DevTool/tests/DevTool.Tests/LegacyModeTests.cs deleted file mode 100644 index f4f5a1b..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/LegacyModeTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Text.Json; -using Sdt.Config; -using Xunit; - -namespace DevTool.Tests; - -public sealed class LegacyModeTests -{ - [Fact] - public void ConfigLoader_StrictMode_TargetsOnly_FailsAndWritesPreview() - { - var root = CreateTempDir(); - WriteLegacyTargetsOnlyConfig(root); - Environment.SetEnvironmentVariable("SDT_LEGACY_MODE", null); - - var ex = Assert.Throws(() => ConfigLoader.FindAndLoad(root)); - Assert.Contains("Strict mode requires workflows", ex.Message, StringComparison.OrdinalIgnoreCase); - Assert.True(File.Exists(Path.Combine(root, "devtool.generated.workflows.json"))); - } - - [Fact] - public void ConfigLoader_CompatMode_TargetsOnly_Loads() - { - var root = CreateTempDir(); - WriteLegacyTargetsOnlyConfig(root); - Environment.SetEnvironmentVariable("SDT_LEGACY_MODE", "compat"); - try - { - var loaded = ConfigLoader.FindAndLoad(root); - Assert.NotNull(loaded); - Assert.Contains(loaded!.Warnings, w => w.Contains("legacy 'targets'", StringComparison.OrdinalIgnoreCase)); - } - finally - { - Environment.SetEnvironmentVariable("SDT_LEGACY_MODE", null); - } - } - - [Fact] - public void ApplyLegacyTargetMigration_RewritesConfigAndCreatesBackup() - { - var root = CreateTempDir(); - WriteLegacyTargetsOnlyConfig(root); - var path = Path.Combine(root, "devtool.json"); - - var result = ConfigLoader.ApplyLegacyTargetMigration(path, createBackup: true); - - Assert.True(result.Success); - Assert.True(File.Exists(path)); - Assert.False(string.IsNullOrWhiteSpace(result.BackupPath)); - Assert.True(File.Exists(result.BackupPath!)); - - var loaded = ConfigLoader.FindAndLoad(root); - Assert.NotNull(loaded); - Assert.NotEmpty(loaded!.Config.Workflows); - Assert.Empty(loaded.Config.Targets); - } - - private static void WriteLegacyTargetsOnlyConfig(string root) - { - var cfg = new DevToolConfig - { - Name = "legacy", - Version = "0.1.0", - Targets = - [ - new BuildTarget - { - Id = "build", - Label = "Build", - Command = "dotnet", - Args = ["build"] - } - ], - Workflows = [] - }; - - var json = JsonSerializer.Serialize(cfg, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }); - File.WriteAllText(Path.Combine(root, "devtool.json"), json); - } - - private static string CreateTempDir() - { - var path = Path.Combine(Path.GetTempPath(), "sdt-legacy-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(path); - return path; - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/PrereqInstallerServiceTests.cs b/Journal.DevTool/tests/DevTool.Tests/PrereqInstallerServiceTests.cs deleted file mode 100644 index 3bc0186..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/PrereqInstallerServiceTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Xunit; - -namespace DevTool.Tests; - -public sealed class PrereqInstallerServiceTests -{ - [Fact] - public async Task PreferredInstallCommands_AreUsedFirst() - { - var svc = new PrereqInstallerService(); - var cfg = new DevToolConfig - { - Tooling = new ToolingConfig - { - Tools = - [ - new ToolInstallDefinition - { - Tool = "dotnet", - PreferredInstallCommands = - [ - "echo install dotnet", - "dotnet --info" - ] - } - ] - } - }; - - var plan = await svc.GetInstallPlanAsync("dotnet", Directory.GetCurrentDirectory(), cfg); - - Assert.True(plan.Supported); - Assert.Equal("dotnet", plan.Tool); - Assert.Equal(2, plan.Commands.Count); - Assert.Equal("echo", plan.Commands[0].Command); - Assert.Equal("dotnet", plan.Commands[1].Command); - } - - [Fact] - public async Task DiagInstallPlanFailure_FallsBackToTemplatePlan() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-prereq-" + Guid.NewGuid().ToString("N")); - var scripts = Path.Combine(root, "scripts"); - Directory.CreateDirectory(scripts); - await File.WriteAllTextAsync(Path.Combine(scripts, "diag.py"), "import sys\nsys.exit(2)\n"); - - var svc = new PrereqInstallerService(); - var plan = await svc.GetInstallPlanAsync("npm", root, new DevToolConfig()); - - Assert.True(plan.Supported); - Assert.NotEmpty(plan.Commands); - Assert.Contains("fallback", plan.Summary, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task DiagInstallPlanInvalidJson_FallsBackToTemplatePlan() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-prereq-" + Guid.NewGuid().ToString("N")); - var scripts = Path.Combine(root, "scripts"); - Directory.CreateDirectory(scripts); - await File.WriteAllTextAsync(Path.Combine(scripts, "diag.py"), "print('not-json')\n"); - - var svc = new PrereqInstallerService(); - var plan = await svc.GetInstallPlanAsync("dotnet", root, new DevToolConfig()); - - Assert.True(plan.Supported); - Assert.NotEmpty(plan.Commands); - Assert.Contains("fallback", plan.Summary, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task TauriFallbackPlan_IsMultiStepAndClear() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-prereq-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - - var svc = new PrereqInstallerService(); - var plan = await svc.GetInstallPlanAsync("tauri", root, new DevToolConfig()); - - Assert.True(plan.Supported); - Assert.True(plan.Commands.Count >= 3); - Assert.Contains("tauri", plan.Summary, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task TauriFallbackPlan_IncludesRustAndCliCommands() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-prereq-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - - var svc = new PrereqInstallerService(); - var plan = await svc.GetInstallPlanAsync("tauri", root, new DevToolConfig()); - - Assert.Contains(plan.Commands, c => c.Args.Any(a => a.Contains("rustup", StringComparison.OrdinalIgnoreCase))); - Assert.Contains(plan.Commands, c => c.Args.Any(a => a.Contains("@tauri-apps/cli", StringComparison.OrdinalIgnoreCase))); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/RequirementResolverTests.cs b/Journal.DevTool/tests/DevTool.Tests/RequirementResolverTests.cs deleted file mode 100644 index 9404dbe..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/RequirementResolverTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Xunit; - -namespace DevTool.Tests; - -public sealed class RequirementResolverTests -{ - private readonly RequirementResolver _resolver = new(); - - [Fact] - public void TauriBuildAction_RequiresNodeNpmCargo_NotGlobalTauri() - { - var step = new WorkflowStep - { - Id = "tauri", - Label = "tauri", - Action = "tauri-build", - }; - - var tools = _resolver.Resolve(step).Select(r => r.Tool).ToHashSet(StringComparer.OrdinalIgnoreCase); - Assert.Contains("node", tools); - Assert.Contains("npm", tools); - Assert.Contains("cargo", tools); - Assert.DoesNotContain("tauri", tools); - } - - [Fact] - public void LegacyPwshTarget_InferenceMatchesExpected() - { - var target = new BuildTarget - { - Id = "web", - Label = "Web", - Command = "pwsh", - Args = ["-NoProfile", "-File", "scripts/publish-app.ps1", "-Target", "web"], - }; - - var tools = _resolver.Resolve(target).Select(r => r.Tool).ToHashSet(StringComparer.OrdinalIgnoreCase); - Assert.Contains("python", tools); - Assert.Contains("node", tools); - Assert.Contains("npm", tools); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/RunEventJsonlRecorderTests.cs b/Journal.DevTool/tests/DevTool.Tests/RunEventJsonlRecorderTests.cs deleted file mode 100644 index a085a3f..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/RunEventJsonlRecorderTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Text.Json; -using Sdt.Core; -using Xunit; - -namespace DevTool.Tests; - -public sealed class RunEventJsonlRecorderTests -{ - [Fact] - public void Recorder_WritesJsonlEvents() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-events-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - - string path; - using (var recorder = RunEventJsonlRecorder.Create(root, "workflow")) - { - path = recorder.FilePath; - recorder.Write(new RunEvent("workflow", RunEventType.WorkflowStarted, "started", WorkflowId: "build")); - recorder.Write(new RunEvent("workflow", RunEventType.WorkflowCompleted, "done", WorkflowId: "build", Success: true)); - } - - Assert.True(File.Exists(path)); - var lines = File.ReadAllLines(path); - Assert.Equal(2, lines.Length); - - using var doc = JsonDocument.Parse(lines[0]); - Assert.Equal("workflow", doc.RootElement.GetProperty("category").GetString()); - Assert.Equal("WorkflowStarted", doc.RootElement.GetProperty("type").GetString()); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/RunEventLogReaderTests.cs b/Journal.DevTool/tests/DevTool.Tests/RunEventLogReaderTests.cs deleted file mode 100644 index f4b68aa..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/RunEventLogReaderTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Sdt.Core; -using Xunit; - -namespace DevTool.Tests; - -public sealed class RunEventLogReaderTests -{ - [Fact] - public void ReadEvents_ParsesValidJsonlLines() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-logreader-" + Guid.NewGuid().ToString("N")); - var dir = Path.Combine(root, ".sdt", "events"); - Directory.CreateDirectory(dir); - var file = Path.Combine(dir, "workflow-test.jsonl"); - File.WriteAllLines(file, - [ - """{"category":"workflow","type":"WorkflowStarted","message":"start","workflowId":"build","occurredAt":"2026-03-01T10:00:00Z"}""", - """{"category":"workflow","type":"WorkflowCompleted","message":"done","workflowId":"build","success":true,"exitCode":0,"occurredAt":"2026-03-01T10:00:01Z"}""" - ]); - - var reader = new RunEventLogReader(); - var events = reader.ReadEvents(file); - - Assert.Equal(2, events.Count); - Assert.Equal(RunEventType.WorkflowStarted, events[0].Type); - Assert.Equal(RunEventType.WorkflowCompleted, events[1].Type); - Assert.True(events[1].Success); - } - - [Fact] - public void ListEventFiles_ReturnsNewestFirst() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-logreader-" + Guid.NewGuid().ToString("N")); - var dir = Path.Combine(root, ".sdt", "events"); - Directory.CreateDirectory(dir); - var older = Path.Combine(dir, "older.jsonl"); - var newer = Path.Combine(dir, "newer.jsonl"); - File.WriteAllText(older, "{}"); - Thread.Sleep(20); - File.WriteAllText(newer, "{}"); - - var reader = new RunEventLogReader(); - var files = reader.ListEventFiles(root); - - Assert.True(files.Count >= 2); - Assert.Equal("newer.jsonl", files[0].Name); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/ScriptCommonTests.cs b/Journal.DevTool/tests/DevTool.Tests/ScriptCommonTests.cs deleted file mode 100644 index 727ab34..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/ScriptCommonTests.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.Diagnostics; -using Xunit; - -namespace DevTool.Tests; - -public sealed class ScriptCommonTests -{ - [Fact] - public async Task ResolveRepoRoot_UsesGlobRootHints() - { - var root = CreateTempDir("sdt-script-root-"); - var nested = Path.Combine(root, "src", "app"); - Directory.CreateDirectory(nested); - await File.WriteAllTextAsync(Path.Combine(root, "sample.sln"), ""); - await File.WriteAllTextAsync(Path.Combine(root, "devtool.json"), """ -{ - "name": "demo", - "version": "0.1.0", - "workflows": [], - "project": { - "rootHints": ["*.sln"] - } -} -"""); - - var output = await RunPythonAsync(nested, "import script_common; print(script_common.resolve_repo_root(r'" + Escape(nested) + "'))"); - Assert.Equal(Path.GetFullPath(root), output.Trim()); - } - - [Fact] - public async Task ResolveRepoRoot_UsesDirectoryMarkerHints() - { - var root = CreateTempDir("sdt-script-root-"); - var nested = Path.Combine(root, "child", "leaf"); - Directory.CreateDirectory(nested); - Directory.CreateDirectory(Path.Combine(root, ".git")); - await File.WriteAllTextAsync(Path.Combine(root, "devtool.json"), """ -{ - "name": "demo", - "version": "0.1.0", - "workflows": [], - "project": { - "rootHints": [".git", "package.json"] - } -} -"""); - - var output = await RunPythonAsync(nested, "import script_common; print(script_common.resolve_repo_root(r'" + Escape(nested) + "'))"); - Assert.Equal(Path.GetFullPath(root), output.Trim()); - } - - [Fact] - public async Task ResolveCommand_ExpandsWindowsPathTokens() - { - if (!OperatingSystem.IsWindows()) - return; - - var root = CreateTempDir("sdt-script-cmd-"); - var shimDir = Path.Combine(root, "nodejs"); - Directory.CreateDirectory(shimDir); - await File.WriteAllTextAsync(Path.Combine(shimDir, "npm.cmd"), "@echo off"); - - var output = await RunPythonAsync( - root, - "import script_common; print(script_common.resolve_command('npm'))", - new Dictionary - { - ["NVM_HOME"] = root, - ["NVM_SYMLINK"] = shimDir, - ["PATH"] = "%NVM_HOME%;%NVM_SYMLINK%", - }); - - Assert.EndsWith("npm.cmd", output.Trim(), StringComparison.OrdinalIgnoreCase); - } - - private static async Task RunPythonAsync( - string workingDir, - string script, - IReadOnlyDictionary? env = null) - { - var python = ResolvePython(); - var psi = new ProcessStartInfo - { - FileName = python, - WorkingDirectory = workingDir, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - psi.ArgumentList.Add("-c"); - psi.ArgumentList.Add($"import sys; sys.path.insert(0, r'{Escape(Path.Combine(ProjectRepoRoot(), "scripts"))}'); {script}"); - - if (env is not null) - { - foreach (var pair in env) - psi.Environment[pair.Key] = pair.Value ?? string.Empty; - } - - using var process = new Process { StartInfo = psi }; - process.Start(); - var stdout = await process.StandardOutput.ReadToEndAsync(); - var stderr = await process.StandardError.ReadToEndAsync(); - await process.WaitForExitAsync(); - if (process.ExitCode != 0) - throw new InvalidOperationException($"Python exited {process.ExitCode}: {stderr}"); - return stdout; - } - - private static string ResolvePython() - { - var candidates = OperatingSystem.IsWindows() ? new[] { "python", "py" } : new[] { "python3", "python" }; - foreach (var candidate in candidates) - { - try - { - using var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = candidate, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - } - }; - process.StartInfo.ArgumentList.Add("--version"); - process.Start(); - process.WaitForExit(2000); - if (process.ExitCode == 0) - return candidate; - } - catch - { - } - } - - throw new InvalidOperationException("Python executable not found."); - } - - private static string CreateTempDir(string prefix) - { - var path = Path.Combine(Path.GetTempPath(), prefix + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(path); - return path; - } - - private static string Escape(string value) => value.Replace("\\", "\\\\").Replace("'", "\\'"); - - private static string ProjectRepoRoot() - { - var dir = new DirectoryInfo(AppContext.BaseDirectory); - while (dir is not null) - { - if (File.Exists(Path.Combine(dir.FullName, "devtool.json")) && - File.Exists(Path.Combine(dir.FullName, "scripts", "script_common.py"))) - { - return dir.FullName; - } - dir = dir.Parent; - } - - throw new InvalidOperationException("Could not locate project repo root."); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/ScriptSmokeTests.cs b/Journal.DevTool/tests/DevTool.Tests/ScriptSmokeTests.cs deleted file mode 100644 index 17e4ed7..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/ScriptSmokeTests.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System.Diagnostics; -using System.Text.Json; -using Xunit; - -namespace DevTool.Tests; - -public sealed class ScriptSmokeTests -{ - [Fact] - public async Task DiagProbe_JsonContract_IsValid() - { - var python = ResolvePython(); - var result = await RunAsync( - python, - ["scripts/diag.py", "probe", "--tool", "python", "--json"]); - - Assert.Equal(0, result.ExitCode); - using var doc = JsonDocument.Parse(result.StdOut); - Assert.True(doc.RootElement.TryGetProperty("tool", out _)); - Assert.True(doc.RootElement.TryGetProperty("available", out _)); - } - - [Fact] - public async Task BuildAction_InvalidRequirements_PropagatesNonZeroAndJson() - { - var python = ResolvePython(); - var missingReq = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".txt"); - - var result = await RunAsync( - python, - [ - "scripts/build.py", - "python-pip-install", - "--project-root", - RepoRoot(), - "--requirements", - missingReq, - "--json" - ]); - - Assert.NotEqual(0, result.ExitCode); - var jsonText = ExtractLastJsonObject(result.StdOut); - using var doc = JsonDocument.Parse(jsonText); - Assert.True(doc.RootElement.TryGetProperty("exit_code", out var code)); - Assert.NotEqual(0, code.GetInt32()); - Assert.True(doc.RootElement.TryGetProperty("failure_reason", out _)); - } - - [Fact] - public async Task BuildAction_DotnetRestore_CommandNotFoundStillReturnsJson() - { - var python = ResolvePython(); - - var result = await RunAsync( - python, - [ - "scripts/build.py", - "dotnet-restore", - "--project-root", - RepoRoot(), - "--json" - ]); - - var jsonText = ExtractLastJsonObject(result.StdOut); - using var doc = JsonDocument.Parse(jsonText); - Assert.True(doc.RootElement.TryGetProperty("exit_code", out _)); - Assert.True(doc.RootElement.TryGetProperty("status", out _)); - } - - [Fact] - public async Task BuildAction_DotnetBuild_AutoSelectsSlnTarget_WhenSingleSlnFound() - { - var python = ResolvePython(); - var tempRoot = Path.Combine(Path.GetTempPath(), "sdt-dotnet-target-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(tempRoot); - var sln = Path.Combine(tempRoot, "sample.sln"); - await File.WriteAllTextAsync(sln, "Microsoft Visual Studio Solution File, Format Version 12.00"); - - var result = await RunAsync( - python, - [ - "scripts/build.py", - "dotnet-build", - "--project-root", - tempRoot, - "--working-dir", - ".", - "--json" - ]); - - var jsonText = ExtractLastJsonObject(result.StdOut); - using var doc = JsonDocument.Parse(jsonText); - Assert.True(doc.RootElement.TryGetProperty("args", out var args)); - Assert.Contains(args.EnumerateArray().Select(x => x.GetString()), x => string.Equals(x, sln, StringComparison.OrdinalIgnoreCase)); - } - - private static string ResolvePython() - { - var candidates = OperatingSystem.IsWindows() - ? new[] { "python", "py" } - : new[] { "python3", "python" }; - - foreach (var candidate in candidates) - { - try - { - using var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = candidate, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - } - }; - process.StartInfo.ArgumentList.Add("--version"); - process.Start(); - process.WaitForExit(2000); - if (process.ExitCode == 0) - return candidate; - } - catch - { - } - } - - throw new InvalidOperationException("Python executable not found for script smoke tests."); - } - - private static async Task<(int ExitCode, string StdOut, string StdErr)> RunAsync(string command, IReadOnlyList args) - { - var psi = new ProcessStartInfo - { - FileName = command, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = RepoRoot(), - }; - foreach (var arg in args) - psi.ArgumentList.Add(arg); - - using var process = new Process { StartInfo = psi }; - process.Start(); - var stdout = await process.StandardOutput.ReadToEndAsync(); - var stderr = await process.StandardError.ReadToEndAsync(); - await process.WaitForExitAsync(); - return (process.ExitCode, stdout, stderr); - } - - private static string RepoRoot() - { - var dir = new DirectoryInfo(AppContext.BaseDirectory); - while (dir is not null) - { - if (File.Exists(Path.Combine(dir.FullName, "devtool.json"))) - return dir.FullName; - dir = dir.Parent; - } - - throw new InvalidOperationException("Could not locate repo root (devtool.json not found)."); - } - - private static string ExtractLastJsonObject(string text) - { - var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - for (var i = lines.Length - 1; i >= 0; i--) - { - var line = lines[i]; - if (line.StartsWith("{", StringComparison.Ordinal) && line.EndsWith("}", StringComparison.Ordinal)) - return line; - } - - throw new InvalidOperationException("No JSON object line found in script output."); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/WorkflowExecutorTests.cs b/Journal.DevTool/tests/DevTool.Tests/WorkflowExecutorTests.cs deleted file mode 100644 index ee2c35f..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/WorkflowExecutorTests.cs +++ /dev/null @@ -1,311 +0,0 @@ -using Sdt.Config; -using Sdt.Core; -using Sdt.Runner; -using Xunit; - -namespace DevTool.Tests; - -public sealed class WorkflowExecutorTests -{ - [Fact] - public async Task MissingPrereq_UserDeclines_ReturnsUserDeclined() - { - var executor = new WorkflowExecutor( - new WorkflowPlanner(), - new FakeProbeService(isAvailable: false), - new FakeInstallerService(success: true), - new FakeActionRunner(success: true), - new RequirementResolver()); - - var cfg = new DevToolConfig(); - var wf = BuildSingleStepWorkflow("w", "dotnet"); - var map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf }; - - var result = await executor.ExecuteAsync( - wf, map, cfg, ".", (_, _) => Task.FromResult(false), (_, _) => { }); - - Assert.False(result.Success); - Assert.Equal(ExecutionStopReason.UserDeclined, result.StopReason); - } - - [Fact] - public async Task MissingPrereq_InstallFails_ReturnsInstallFailed() - { - var executor = new WorkflowExecutor( - new WorkflowPlanner(), - new FakeProbeService(isAvailable: false), - new FakeInstallerService(success: false), - new FakeActionRunner(success: true), - new RequirementResolver()); - - var cfg = new DevToolConfig(); - var wf = BuildSingleStepWorkflow("w", "dotnet"); - var map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf }; - - var result = await executor.ExecuteAsync( - wf, map, cfg, ".", (_, _) => Task.FromResult(true), (_, _) => { }); - - Assert.False(result.Success); - Assert.Equal(ExecutionStopReason.InstallFailed, result.StopReason); - } - - [Fact] - public async Task StepFailure_StopsImmediately() - { - var executor = new WorkflowExecutor( - new WorkflowPlanner(), - new FakeProbeService(isAvailable: true), - new FakeInstallerService(success: true), - new FakeActionRunner(success: false), - new RequirementResolver()); - - var wf = new WorkflowDefinition - { - Id = "w", - Label = "W", - Steps = - [ - new WorkflowStep { Id = "s1", Label = "S1", Command = "dotnet", Args = ["build"] }, - new WorkflowStep { Id = "s2", Label = "S2", Command = "dotnet", Args = ["build"] }, - ] - }; - var map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf }; - - var result = await executor.ExecuteAsync( - wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(true), (_, _) => { }); - - Assert.False(result.Success); - Assert.Equal(ExecutionStopReason.CommandFailed, result.StopReason); - Assert.Single(result.Steps); - } - - [Fact] - public async Task LegacyPwshScriptStep_MissingPrereq_PromptsBeforeRun() - { - var executor = new WorkflowExecutor( - new WorkflowPlanner(), - new FakeProbeService(isAvailable: false), - new FakeInstallerService(success: true), - new FakeActionRunner(success: true), - new RequirementResolver()); - - var wf = new WorkflowDefinition - { - Id = "w", - Label = "W", - Steps = - [ - new WorkflowStep - { - Id = "ps1", - Label = "Legacy PS1", - Command = "pwsh", - Args = ["-NoProfile", "-File", "scripts/publish-app.ps1", "-Target", "web"], - } - ] - }; - var map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf }; - - var result = await executor.ExecuteAsync( - wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(false), (_, _) => { }); - - Assert.False(result.Success); - Assert.Equal(ExecutionStopReason.UserDeclined, result.StopReason); - } - - [Fact] - public async Task TauriBuild_DoesNotRequireGlobalTauri_WhenNodeNpmCargoAvailable() - { - var executor = new WorkflowExecutor( - new WorkflowPlanner(), - new ConditionalProbeService(), - new FakeInstallerService(success: true), - new FakeActionRunner(success: true), - new RequirementResolver()); - - var wf = new WorkflowDefinition - { - Id = "w", - Label = "W", - Steps = - [ - new WorkflowStep - { - Id = "tauri", - Label = "Tauri Build", - Action = "tauri-build", - } - ] - }; - var map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf }; - - var result = await executor.ExecuteAsync( - wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(true), (_, _) => { }); - - Assert.True(result.Success); - Assert.Null(result.StopReason); - Assert.Single(result.Steps); - } - - [Fact] - public async Task MissingPrereq_EmitsProbeDiagnosticsToOutput() - { - var executor = new WorkflowExecutor( - new WorkflowPlanner(), - new DetailedProbeService(), - new FakeInstallerService(success: true), - new FakeActionRunner(success: true), - new RequirementResolver()); - - var wf = BuildSingleStepWorkflow("w", "dotnet"); - var map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf }; - var lines = new List(); - - var result = await executor.ExecuteAsync( - wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(false), (line, _) => lines.Add(line)); - - Assert.False(result.Success); - Assert.Contains(lines, l => l.Contains("Probe detail [dotnet]", StringComparison.OrdinalIgnoreCase)); - } - - [Fact] - public async Task ExecuteAsync_EmitsRunEvents_ForStepLifecycle() - { - var executor = new WorkflowExecutor( - new WorkflowPlanner(), - new FakeProbeService(isAvailable: true), - new FakeInstallerService(success: true), - new FakeActionRunner(success: true), - new RequirementResolver()); - - var wf = BuildSingleStepWorkflow("w", "dotnet"); - var map = new Dictionary(StringComparer.OrdinalIgnoreCase) { [wf.Id] = wf }; - var events = new List(); - - var result = await executor.ExecuteAsync( - wf, map, new DevToolConfig(), ".", (_, _) => Task.FromResult(true), (_, _) => { }, events.Add); - - Assert.True(result.Success); - Assert.Contains(events, e => e.Type == RunEventType.WorkflowStarted); - Assert.Contains(events, e => e.Type == RunEventType.WorkflowStepStarted); - Assert.Contains(events, e => e.Type == RunEventType.WorkflowStepCompleted); - Assert.Contains(events, e => e.Type == RunEventType.WorkflowCompleted && e.Success == true); - } - - [Fact] - public void AggregatorWorkflow_ExecutesDependenciesOnly() - { - var planner = new WorkflowPlanner(); - var dep = new WorkflowDefinition - { - Id = "dep", - Label = "Dependency", - Steps = [new WorkflowStep { Id = "s", Label = "S", Command = "dotnet", Args = ["build"] }] - }; - var agg = new WorkflowDefinition - { - Id = "agg", - Label = "Aggregator", - DependsOn = ["dep"], - Steps = [] - }; - - var map = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - [dep.Id] = dep, - [agg.Id] = agg - }; - - var plan = planner.ResolvePlan(agg, map); - - Assert.Single(plan); - Assert.Equal("dep", plan[0].Id); - } - - private static WorkflowDefinition BuildSingleStepWorkflow(string id, string tool) - { - return new WorkflowDefinition - { - Id = id, - Label = id, - Steps = - [ - new WorkflowStep - { - Id = "step", - Label = "step", - Command = tool, - Args = ["--version"], - Requires = [new ToolRequirement { Tool = tool, InstallPolicy = InstallPolicy.Prompt }], - } - ] - }; - } - - private sealed class FakeProbeService(bool isAvailable) : IToolProbe - { - public Task ProbeAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - => Task.FromResult(new ProbeResult(tool, isAvailable, Version: isAvailable ? "1.0.0" : null)); - } - - private sealed class FakeInstallerService(bool success) : IPrereqInstaller - { - public Task GetInstallPlanAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - => Task.FromResult(new InstallPlan(tool, Supported: true, "test", [new InstallCommand("echo", ["ok"])])); - - public Task RunInstallAsync( - InstallCommand command, - string projectRoot, - Action onOutput, - CancellationToken cancellationToken = default) - => Task.FromResult(new RunResult(success ? 0 : 1, TimeSpan.FromMilliseconds(5))); - } - - private sealed class FakeActionRunner(bool success) : IActionRunner - { - public Task RunStepAsync( - WorkflowStep step, - string projectRoot, - Action onOutput, - CancellationToken cancellationToken = default) - => Task.FromResult(new RunResult(success ? 0 : 2, TimeSpan.FromMilliseconds(10))); - } - - private sealed class ConditionalProbeService : IToolProbe - { - public Task ProbeAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - { - var available = tool.ToLowerInvariant() switch - { - "node" => true, - "npm" => true, - "cargo" => true, - "tauri" => false, - _ => true - }; - return Task.FromResult(new ProbeResult(tool, available, Version: available ? "1.0.0" : null)); - } - } - - private sealed class DetailedProbeService : IToolProbe - { - public Task ProbeAsync( - string tool, - string projectRoot, - DevToolConfig? config = null, - CancellationToken cancellationToken = default) - => Task.FromResult(new ProbeResult(tool, false, Details: "Fallback: unresolved command")); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/WorkflowModelBuilderTests.cs b/Journal.DevTool/tests/DevTool.Tests/WorkflowModelBuilderTests.cs deleted file mode 100644 index 96c4f4b..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/WorkflowModelBuilderTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -using Sdt.Config; -using Xunit; - -namespace DevTool.Tests; - -public sealed class WorkflowModelBuilderTests -{ - [Fact] - public void TargetsOnly_Strict_ThrowsMigrationError() - { - var cfg = new DevToolConfig - { - Targets = - [ - new BuildTarget - { - Id = "build", - Label = "Build", - Command = "dotnet", - Args = ["build"], - } - ] - }; - - var ex = Assert.Throws(() => WorkflowModelBuilder.Normalize(cfg, LegacyMode.Strict)); - Assert.Contains("Legacy 'targets' are not allowed in strict mode", ex.Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void TargetsOnly_Compat_ProducesWarningAndConvertedWorkflow() - { - var cfg = new DevToolConfig - { - Targets = - [ - new BuildTarget - { - Id = "build", - Label = "Build", - Command = "dotnet", - Args = ["build"], - } - ] - }; - - var result = WorkflowModelBuilder.Normalize(cfg, LegacyMode.Compat); - - Assert.Single(result.Workflows); - Assert.Contains(result.Warnings, w => w.Contains("legacy 'targets'", StringComparison.OrdinalIgnoreCase)); - } - - [Fact] - public void WorkflowsOnly_HasNoLegacyWarning() - { - var cfg = new DevToolConfig - { - Workflows = - [ - new WorkflowDefinition - { - Id = "build", - Label = "Build", - Steps = - [ - new WorkflowStep - { - Id = "run", - Label = "Run", - Command = "dotnet", - Args = ["build"], - } - ] - } - ] - }; - - var result = WorkflowModelBuilder.Normalize(cfg, LegacyMode.Strict); - - Assert.Single(result.Workflows); - Assert.DoesNotContain(result.Warnings, w => w.Contains("legacy 'targets'", StringComparison.OrdinalIgnoreCase)); - } - - [Fact] - public void Mixed_PrefersWorkflowsDeterministically() - { - var cfg = new DevToolConfig - { - Targets = - [ - new BuildTarget - { - Id = "legacy", - Label = "Legacy", - Command = "dotnet", - Args = ["build"], - } - ], - Workflows = - [ - new WorkflowDefinition - { - Id = "new", - Label = "New", - Steps = - [ - new WorkflowStep - { - Id = "step", - Label = "Step", - Command = "dotnet", - Args = ["build"], - } - ] - } - ] - }; - - var result = WorkflowModelBuilder.Normalize(cfg, LegacyMode.Strict); - - Assert.Single(result.Workflows); - Assert.Equal("new", result.Workflows[0].Id); - Assert.Contains(result.Warnings, w => w.Contains("Both 'workflows' and legacy 'targets'", StringComparison.OrdinalIgnoreCase)); - } - - [Fact] - public void LegacyPwshTarget_InfersToolRequirements_FromScript() - { - var cfg = new DevToolConfig - { - Targets = - [ - new BuildTarget - { - Id = "web", - Label = "Web", - Command = "pwsh", - Args = ["-NoProfile", "-File", "scripts/publish-app.ps1", "-Target", "web"], - } - ] - }; - - var result = WorkflowModelBuilder.Normalize(cfg, LegacyMode.Compat); - var step = Assert.Single(result.Workflows).Steps.Single(); - - Assert.Contains(step.Requires, r => r.Tool == "python"); - Assert.Contains(step.Requires, r => r.Tool == "node"); - Assert.Contains(step.Requires, r => r.Tool == "npm"); - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/WorkspaceDefaultsTests.cs b/Journal.DevTool/tests/DevTool.Tests/WorkspaceDefaultsTests.cs deleted file mode 100644 index 99d4880..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/WorkspaceDefaultsTests.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Sdt.Config; -using Xunit; - -namespace DevTool.Tests; - -public sealed class WorkspaceDefaultsTests -{ - [Fact] - public void ConfigLoader_AppliesWorkspaceDefaults_FromAncestorDirectory() - { - var workspaceRoot = CreateTempDir("sdt-ws-defaults-"); - var projectRoot = Path.Combine(workspaceRoot, "proj-a"); - Directory.CreateDirectory(projectRoot); - File.WriteAllText(Path.Combine(workspaceRoot, WorkspaceLoader.FileName), """ -{ - "name": "Test Workspace", - "projects": [] -} -"""); - - File.WriteAllText(Path.Combine(workspaceRoot, ConfigLoader.WorkspaceDefaultsFileName), """ -{ - "toolchains": { - "node": { - "packageManager": "pnpm", - "workingDir": "frontend" - } - }, - "env": [ - { "key": "DOTNET_ENVIRONMENT", "description": "default env", "default": "Development", "options": ["Development", "Production"] } - ] -} -"""); - - File.WriteAllText(Path.Combine(projectRoot, "devtool.json"), """ -{ - "name": "Project A", - "version": "1.0.0", - "workflows": [ - { - "id": "build", - "label": "Build", - "description": "Build app", - "group": "Build", - "dependsOn": [], - "steps": [ - { "id": "build-step", "label": "dotnet build", "command": "dotnet", "args": ["build"], "workingDir": "." } - ] - } - ] -} -"""); - - var loaded = ConfigLoader.FindAndLoad(projectRoot); - - Assert.NotNull(loaded); - Assert.Equal("pnpm", loaded!.Config.Toolchains?.Node?.PackageManager); - Assert.Equal("frontend", loaded.Config.Toolchains?.Node?.WorkingDir); - Assert.Single(loaded.Config.Env); - Assert.Contains(loaded.Warnings, w => w.Contains("Applied workspace defaults", StringComparison.OrdinalIgnoreCase)); - } - - [Fact] - public void ConfigLoader_ProjectValuesOverrideWorkspaceDefaults() - { - var workspaceRoot = CreateTempDir("sdt-ws-override-"); - var projectRoot = Path.Combine(workspaceRoot, "proj-b"); - Directory.CreateDirectory(projectRoot); - File.WriteAllText(Path.Combine(workspaceRoot, WorkspaceLoader.FileName), """ -{ - "name": "Test Workspace", - "projects": [] -} -"""); - - File.WriteAllText(Path.Combine(workspaceRoot, ConfigLoader.WorkspaceDefaultsFileName), """ -{ - "name": "Workspace Defaults", - "workflows": [ - { - "id": "from-defaults", - "label": "Defaults Workflow", - "description": "", - "group": "General", - "dependsOn": [], - "steps": [] - } - ], - "debug": { - "diagnostics": { - "enabled": true, - "outputDir": ".sdt/workspace-debug", - "includeAllEnv": true, - "bundleOnFailure": true - } - } -} -"""); - - File.WriteAllText(Path.Combine(projectRoot, "devtool.json"), """ -{ - "name": "Project B", - "version": "1.0.0", - "workflows": [ - { - "id": "project-workflow", - "label": "Project Workflow", - "description": "Only this one should remain", - "group": "Build", - "dependsOn": [], - "steps": [] - } - ], - "debug": { - "diagnostics": { - "enabled": false - } - } -} -"""); - - var loaded = ConfigLoader.FindAndLoad(projectRoot); - - Assert.NotNull(loaded); - Assert.Equal("Project B", loaded!.Config.Name); - Assert.Single(loaded.Config.Workflows); - Assert.Equal("project-workflow", loaded.Config.Workflows[0].Id); - Assert.NotNull(loaded.Config.Debug); - Assert.NotNull(loaded.Config.Debug!.Diagnostics); - Assert.False(loaded.Config.Debug.Diagnostics.Enabled); - Assert.Equal(".sdt/workspace-debug", loaded.Config.Debug.Diagnostics.OutputDir); - Assert.True(loaded.Config.Debug.Diagnostics.IncludeAllEnv); - } - - private static string CreateTempDir(string prefix) - { - var path = Path.Combine(Path.GetTempPath(), prefix + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(path); - return path; - } -} diff --git a/Journal.DevTool/tests/DevTool.Tests/WorkspaceLoaderTests.cs b/Journal.DevTool/tests/DevTool.Tests/WorkspaceLoaderTests.cs deleted file mode 100644 index 54be0fe..0000000 --- a/Journal.DevTool/tests/DevTool.Tests/WorkspaceLoaderTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Sdt.Config; -using Xunit; - -namespace DevTool.Tests; - -public sealed class WorkspaceLoaderTests -{ - [Fact] - public void FindAndLoad_DoesNotThrow_WhenAutoDiscoverHitsStrictLegacyProject() - { - var root = Path.Combine(Path.GetTempPath(), "sdt-workspace-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - File.WriteAllText(Path.Combine(root, "devtool.json"), """ -{ - "name": "legacy", - "version": "0.1.0", - "targets": [ - { - "id": "build", - "label": "Build", - "group": "Build", - "command": "dotnet", - "args": ["build"], - "workingDir": ".", - "dependsOn": [] - } - ], - "workflows": [] -} -"""); - - Environment.SetEnvironmentVariable("SDT_LEGACY_MODE", null); - var result = WorkspaceLoader.FindAndLoad(root); - Assert.Null(result); - } - - [Fact] - public void ResolveProjectRoot_AcceptsAbsolutePaths() - { - var root = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "ws-" + Guid.NewGuid().ToString("N"))); - var abs = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N"))); - var project = new WorkspaceProject { Path = abs }; - - var resolved = WorkspaceLoader.ResolveProjectRoot(root, project); - Assert.Equal(abs, resolved, ignoreCase: OperatingSystem.IsWindows()); - } - - [Fact] - public void WorkspaceProject_AdditionalFields_DefaultsAreSafe() - { - var project = new WorkspaceProject(); - Assert.Empty(project.Tags); - Assert.Empty(project.ToolFamilies); - Assert.False(project.Disabled); - } -} diff --git a/Journal.Sidecar/Journal.Sidecar.csproj b/Journal.Sidecar/Journal.Sidecar.csproj index e9b9245..6035b91 100644 --- a/Journal.Sidecar/Journal.Sidecar.csproj +++ b/Journal.Sidecar/Journal.Sidecar.csproj @@ -2,9 +2,6 @@ Exe - net10.0 - enable - enable true true @@ -15,10 +12,10 @@ - - - - + + + + diff --git a/Journal.SmokeTests/Journal.SmokeTests.csproj b/Journal.SmokeTests/Journal.SmokeTests.csproj index a81680c..0b5dedd 100644 --- a/Journal.SmokeTests/Journal.SmokeTests.csproj +++ b/Journal.SmokeTests/Journal.SmokeTests.csproj @@ -2,9 +2,6 @@ Exe - net10.0 - enable - enable diff --git a/Journal.WebGateway/Journal.WebGateway.csproj b/Journal.WebGateway/Journal.WebGateway.csproj index f4e284d..1d28321 100644 --- a/Journal.WebGateway/Journal.WebGateway.csproj +++ b/Journal.WebGateway/Journal.WebGateway.csproj @@ -1,10 +1,5 @@ - - net10.0 - enable - enable - diff --git a/Journal.slnx b/Journal.slnx index 10b89d0..7f1be05 100644 --- a/Journal.slnx +++ b/Journal.slnx @@ -3,6 +3,6 @@ - + diff --git a/package-lock.json b/package-lock.json index 1a303e4..8d53e23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,5 +2,1936 @@ "name": "journal", "lockfileVersion": 3, "requires": true, - "packages": {} + "packages": { + "": { + "name": "journal", + "workspaces": [ + "Journal.App", + "Journal.DevTool" + ] + }, + "Journal.App": { + "name": "journalapp", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.6.0", + "@tauri-apps/plugin-opener": "^2", + "tauri-plugin-mic-recorder-api": "^2.0.0" + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.6", + "@sveltejs/kit": "^2.9.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tauri-apps/cli": "^2", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "~5.6.2", + "vite": "^6.0.3" + } + }, + "Journal.DevTool": { + "dependencies": { + "tauri-plugin-mic-recorder-api": "^2.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.53.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.53.4.tgz", + "integrity": "sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.6.3", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", + "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", + "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.0.tgz", + "integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.0", + "@tauri-apps/cli-darwin-x64": "2.10.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", + "@tauri-apps/cli-linux-arm64-musl": "2.10.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", + "@tauri-apps/cli-linux-x64-gnu": "2.10.0", + "@tauri-apps/cli-linux-x64-musl": "2.10.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", + "@tauri-apps/cli-win32-x64-msvc": "2.10.0" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz", + "integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz", + "integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz", + "integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz", + "integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz", + "integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz", + "integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz", + "integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz", + "integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz", + "integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz", + "integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz", + "integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz", + "integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz", + "integrity": "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/devalue": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", + "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz", + "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/Journal.DevTool": { + "resolved": "Journal.DevTool", + "link": true + }, + "node_modules/journalapp": { + "resolved": "Journal.App", + "link": true + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.0.tgz", + "integrity": "sha512-2lLO/7EupnjO/95t+XZesXs8Bf3nYLIDfCo270h5QWbj/vjLqmrQ1LiRk9LPggxSDsnVYfehamZNf+rgQYApZg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", + "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.53.6", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.6.tgz", + "integrity": "sha512-lP5DGF3oDDI9fhHcSpaBiJEkFLuS16h92DhM1L5K1lFm0WjOmUh1i2sNkBBk8rkxJRpob0dBE75jRfUzGZUOGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.6.3", + "esm-env": "^1.2.1", + "esrap": "^2.2.2", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.4.tgz", + "integrity": "sha512-F1pGqXc710Oi/wTI4d/x7d6lgPwwfx1U6w3Q35n4xsC2e8C/yN2sM1+mWxjlMcpAfWucjlq4vPi+P4FZ8a14sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/tauri-plugin-mic-recorder-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tauri-plugin-mic-recorder-api/-/tauri-plugin-mic-recorder-api-2.0.0.tgz", + "integrity": "sha512-04wqYCX4WIlYd6KUY7aS3+W4B5RtnSoVczaQCBSXKpQkEx9XdaaBN05XCee2unxGva0btSXBItFqQSdosnS4jQ==", + "license": "MIT", + "dependencies": { + "@tauri-apps/api": ">=2.0.0-beta.6" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", + "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..470bab3 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "journal", + "private": true, + "workspaces": [ + "Journal.App" + ] +} \ No newline at end of file