diff --git a/.github/actions/setup-runtimes-caching/action.yml b/.github/actions/setup-runtimes-caching/action.yml
index ac273c089..107ce5311 100644
--- a/.github/actions/setup-runtimes-caching/action.yml
+++ b/.github/actions/setup-runtimes-caching/action.yml
@@ -24,6 +24,11 @@ runs:
9.0.x
10.0.x
+ - name: Install Aspire CLI
+ uses: timheuer/setup-aspire@v0.1.0
+ with:
+ quality: release # temp workaround until url is fixed
+
- name: Set up Python
uses: actions/setup-python@v5
with:
@@ -129,4 +134,3 @@ runs:
if: ${{ inputs.name == 'Full' || contains(inputs.name, 'Hosting.Ollama') }}
with:
version: 0.11.8
-
diff --git a/.gitignore b/.gitignore
index 959f23f3f..ccf1022b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,5 @@ nuget
target
node_modules
+**/.modules/
+**/*.AppHost.TypeScript/nuget.config
diff --git a/eng/testing/validate-typescript-apphost.ps1 b/eng/testing/validate-typescript-apphost.ps1
new file mode 100644
index 000000000..c38f57a2f
--- /dev/null
+++ b/eng/testing/validate-typescript-apphost.ps1
@@ -0,0 +1,218 @@
+#!/usr/bin/env pwsh
+
+param(
+ [Parameter(Mandatory = $true)]
+ [string]$AppHostPath,
+
+ [Parameter(Mandatory = $true)]
+ [string]$PackageProjectPath,
+
+ [Parameter(Mandatory = $true)]
+ [string]$PackageName,
+
+ [Parameter(Mandatory = $true)]
+ [string[]]$WaitForResources,
+
+ [string[]]$RequiredCommands = @(),
+
+ [string]$PackageVersion = "",
+
+ [ValidateSet("healthy", "up", "down")]
+ [string]$WaitStatus = "healthy",
+
+ [int]$WaitTimeoutSeconds = 180
+)
+
+$ErrorActionPreference = "Stop"
+
+function Invoke-ExternalCommand {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$FilePath,
+
+ [Parameter(Mandatory = $true)]
+ [string[]]$Arguments
+ )
+
+ & $FilePath @Arguments
+ if ($LASTEXITCODE -ne 0) {
+ $joinedArguments = [string]::Join(" ", $Arguments)
+ throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $joinedArguments"
+ }
+}
+
+function Invoke-CleanupStep {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Description,
+
+ [Parameter(Mandatory = $true)]
+ [scriptblock]$Action,
+
+ [System.Collections.Generic.List[string]]$Failures = $null
+ )
+
+ try {
+ & $Action
+ }
+ catch {
+ $message = "Cleanup step '$Description' failed: $($_.Exception.Message)"
+ if ($null -ne $Failures) {
+ $Failures.Add($message)
+ return
+ }
+
+ throw $message
+ }
+}
+
+$resolvedAppHostPath = (Resolve-Path $AppHostPath).Path
+$resolvedPackageProjectPath = (Resolve-Path $PackageProjectPath).Path
+$appHostDirectory = Split-Path -Parent $resolvedAppHostPath
+$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\\..")).Path
+$configPath = Join-Path $appHostDirectory "aspire.config.json"
+$nugetConfigPath = Join-Path $appHostDirectory "nuget.config"
+$localSource = Join-Path ([System.IO.Path]::GetTempPath()) ("ct-polyglot-" + [Guid]::NewGuid().ToString("N"))
+$originalConfig = $null
+$appStarted = $false
+$primaryError = $null
+$cleanupFailures = [System.Collections.Generic.List[string]]::new()
+
+if ([string]::IsNullOrWhiteSpace($PackageVersion)) {
+ $versionPrefix = (& dotnet msbuild $resolvedPackageProjectPath -nologo -v:q -getProperty:VersionPrefix).Trim()
+ if ([string]::IsNullOrWhiteSpace($versionPrefix)) {
+ throw "Could not determine the evaluated VersionPrefix for $resolvedPackageProjectPath."
+ }
+
+ $PackageVersion = "$versionPrefix-polyglot.local"
+}
+
+if ($WaitForResources.Count -eq 1) {
+ $splitOptions = [System.StringSplitOptions]::RemoveEmptyEntries -bor [System.StringSplitOptions]::TrimEntries
+ $WaitForResources = $WaitForResources[0].Split(",", $splitOptions)
+}
+
+if ($RequiredCommands.Count -eq 1) {
+ $splitOptions = [System.StringSplitOptions]::RemoveEmptyEntries -bor [System.StringSplitOptions]::TrimEntries
+ $RequiredCommands = $RequiredCommands[0].Split(",", $splitOptions)
+}
+
+foreach ($commandName in $RequiredCommands) {
+ if ($null -eq (Get-Command $commandName -ErrorAction SilentlyContinue)) {
+ throw "Required command '$commandName' was not found on PATH."
+ }
+}
+
+try {
+ $originalConfig = Get-Content -Path $configPath -Raw
+ New-Item -ItemType Directory -Path $localSource -Force | Out-Null
+
+ Invoke-ExternalCommand "dotnet" @(
+ "pack",
+ $resolvedPackageProjectPath,
+ "-c", "Debug",
+ "-p:PackageVersion=$PackageVersion",
+ "-o", $localSource
+ )
+
+ $config = $originalConfig | ConvertFrom-Json -AsHashtable
+ if ($null -eq $config["packages"]) {
+ $config["packages"] = [ordered]@{}
+ }
+
+ $config["packages"][$PackageName] = $PackageVersion
+ $config | ConvertTo-Json -Depth 10 | Set-Content -Path $configPath -NoNewline
+
+ @"
+
+
+
+
+
+
+"@ | Set-Content -Path $nugetConfigPath -NoNewline
+
+ Push-Location $appHostDirectory
+ try {
+ Invoke-ExternalCommand "npm" @("ci")
+ Invoke-ExternalCommand "aspire" @(
+ "restore",
+ "--apphost", $resolvedAppHostPath,
+ "--non-interactive"
+ )
+ Invoke-ExternalCommand "npx" @("tsc", "--noEmit")
+ }
+ finally {
+ Pop-Location
+ }
+
+ Invoke-ExternalCommand "aspire" @(
+ "start",
+ "--apphost", $resolvedAppHostPath,
+ "--isolated",
+ "--format", "Json",
+ "--non-interactive"
+ )
+ $appStarted = $true
+
+ foreach ($resource in $WaitForResources) {
+ Invoke-ExternalCommand "aspire" @(
+ "wait",
+ $resource,
+ "--status", $WaitStatus,
+ "--apphost", $resolvedAppHostPath,
+ "--timeout", $WaitTimeoutSeconds
+ )
+ }
+
+ Invoke-ExternalCommand "aspire" @(
+ "describe",
+ "--apphost", $resolvedAppHostPath,
+ "--format", "Json"
+ )
+}
+catch {
+ $primaryError = $_
+}
+finally {
+ Invoke-CleanupStep -Description "restore Aspire config" -Action {
+ if ($null -ne $originalConfig) {
+ Set-Content -Path $configPath -Value $originalConfig -NoNewline
+ }
+ } -Failures $cleanupFailures
+
+ Invoke-CleanupStep -Description "remove generated nuget.config" -Action {
+ if (Test-Path $nugetConfigPath) {
+ Remove-Item $nugetConfigPath -Force
+ }
+ } -Failures $cleanupFailures
+
+ Invoke-CleanupStep -Description "remove local package source" -Action {
+ if (Test-Path $localSource) {
+ Remove-Item $localSource -Recurse -Force
+ }
+ } -Failures $cleanupFailures
+
+ Invoke-CleanupStep -Description "stop Aspire app" -Action {
+ if ($appStarted) {
+ Invoke-ExternalCommand "aspire" @(
+ "stop",
+ "--apphost", $resolvedAppHostPath
+ )
+ }
+ } -Failures $cleanupFailures
+}
+
+if ($cleanupFailures.Count -gt 0) {
+ $cleanupFailureMessage = "Cleanup failures:${Environment.NewLine}$($cleanupFailures -join [Environment]::NewLine)"
+ if ($null -ne $primaryError) {
+ Write-Error -Message $cleanupFailureMessage -ErrorAction Continue
+ }
+ else {
+ throw $cleanupFailureMessage
+ }
+}
+
+if ($null -ne $primaryError) {
+ throw $primaryError
+}
\ No newline at end of file
diff --git a/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/apphost.ts b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/apphost.ts
new file mode 100644
index 000000000..334110e13
--- /dev/null
+++ b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/apphost.ts
@@ -0,0 +1,90 @@
+import { mkdirSync, mkdtempSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { createBuilder } from './.modules/aspire.js';
+
+const builder = await createBuilder();
+const bindMountRoot = mkdtempSync(join(tmpdir(), 'activemq-polyglot-'));
+const artemisDataPath = join(bindMountRoot, 'artemis-data');
+const artemisConfPath = join(bindMountRoot, 'artemis-conf');
+
+mkdirSync(artemisDataPath, { recursive: true });
+mkdirSync(artemisConfPath, { recursive: true });
+
+const mqPassword = await builder.addParameterWithValue("mq-password", "admin", {
+ secret: true
+});
+const mqUser = await builder.addParameterWithValue("mq-user", "admin", {
+ publishValueAsDefault: true
+});
+
+// addActiveMQ — ActiveMQ Classic with all parameters
+const classic = await builder.addActiveMQ("classic", {
+ userName: mqUser,
+ password: mqPassword,
+ port: 36161,
+ scheme: "activemq",
+ webPort: 38161
+});
+
+// addActiveMQ — minimal overloads with explicit credentials for repeatable startup
+const classic2 = await builder.addActiveMQ("classic2", {
+ userName: mqUser,
+ password: mqPassword
+});
+
+// addActiveMQArtemis — Artemis with all parameters
+const artemis = await builder.addActiveMQArtemis("artemis", {
+ userName: mqUser,
+ password: mqPassword,
+ port: 36162,
+ scheme: "tcp",
+ webPort: 38162
+});
+
+// addActiveMQArtemis — minimal overloads with explicit credentials for repeatable startup
+const artemis2 = await builder.addActiveMQArtemis("artemis2", {
+ userName: mqUser,
+ password: mqPassword
+});
+
+// withDataVolume — fluent chaining on Classic
+await classic.withDataVolume({ name: "classic-data" });
+
+// withConfVolume — fluent chaining on Classic
+await classic.withConfVolume({ name: "classic-conf" });
+
+// withDataBindMount — bind mount on Artemis
+await artemis.withDataBindMount(artemisDataPath);
+
+// withConfBindMount — bind mount on Artemis
+await artemis.withConfBindMount(artemisConfPath);
+
+// withDataVolume + withConfVolume — chaining on Artemis
+await artemis2.withDataVolume();
+await artemis2.withConfVolume();
+
+// ---- Property access on ActiveMQServerResourceBase (ExposeProperties = true) ----
+const classicResource = await classic;
+const _classicEndpoint = await classicResource.primaryEndpoint.get();
+const _classicHost = await classicResource.host.get();
+const _classicPort = await classicResource.port.get();
+const _classicUri = await classicResource.uriExpression.get();
+const _classicCstr = await classicResource.connectionStringExpression.get();
+
+const classic2Resource = await classic2;
+const _classic2Host = await classic2Resource.host.get();
+const _classic2Port = await classic2Resource.port.get();
+
+const artemisResource = await artemis;
+const _artemisEndpoint = await artemisResource.primaryEndpoint.get();
+const _artemisHost = await artemisResource.host.get();
+const _artemisPort = await artemisResource.port.get();
+const _artemisUri = await artemisResource.uriExpression.get();
+const _artemisCstr = await artemisResource.connectionStringExpression.get();
+
+const artemis2Resource = await artemis2;
+const _artemis2Host = await artemis2Resource.host.get();
+const _artemis2Port = await artemis2Resource.port.get();
+
+await builder.build().run();
diff --git a/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/aspire.config.json b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/aspire.config.json
new file mode 100644
index 000000000..612bd8bf9
--- /dev/null
+++ b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/aspire.config.json
@@ -0,0 +1,18 @@
+{
+ "appHost": {
+ "path": "apphost.ts",
+ "language": "typescript/nodejs"
+ },
+ "profiles": {
+ "https": {
+ "applicationUrl": "https://localhost:29750;http://localhost:28931",
+ "environmentVariables": {
+ "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:10975",
+ "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:13319"
+ }
+ }
+ },
+ "packages": {
+ "CommunityToolkit.Aspire.Hosting.ActiveMQ": ""
+ }
+}
\ No newline at end of file
diff --git a/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/package-lock.json b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/package-lock.json
new file mode 100644
index 000000000..046aa2f76
--- /dev/null
+++ b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/package-lock.json
@@ -0,0 +1,962 @@
+{
+ "name": "communitytoolkit-aspire-hosting-activemq-apphost-typescript",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "communitytoolkit-aspire-hosting-activemq-apphost-typescript",
+ "version": "1.0.0",
+ "dependencies": {
+ "vscode-jsonrpc": "^8.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "nodemon": "^3.1.11",
+ "tsx": "^4.19.0",
+ "typescript": "^5.3.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
+ "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
+ "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
+ "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
+ "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
+ "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
+ "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
+ "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
+ "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
+ "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
+ "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
+ "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
+ "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
+ "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
+ "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
+ "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
+ "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
+ "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
+ "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
+ "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
+ "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
+ "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.37",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
+ "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "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/esbuild": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
+ "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.4",
+ "@esbuild/android-arm": "0.27.4",
+ "@esbuild/android-arm64": "0.27.4",
+ "@esbuild/android-x64": "0.27.4",
+ "@esbuild/darwin-arm64": "0.27.4",
+ "@esbuild/darwin-x64": "0.27.4",
+ "@esbuild/freebsd-arm64": "0.27.4",
+ "@esbuild/freebsd-x64": "0.27.4",
+ "@esbuild/linux-arm": "0.27.4",
+ "@esbuild/linux-arm64": "0.27.4",
+ "@esbuild/linux-ia32": "0.27.4",
+ "@esbuild/linux-loong64": "0.27.4",
+ "@esbuild/linux-mips64el": "0.27.4",
+ "@esbuild/linux-ppc64": "0.27.4",
+ "@esbuild/linux-riscv64": "0.27.4",
+ "@esbuild/linux-s390x": "0.27.4",
+ "@esbuild/linux-x64": "0.27.4",
+ "@esbuild/netbsd-arm64": "0.27.4",
+ "@esbuild/netbsd-x64": "0.27.4",
+ "@esbuild/openbsd-arm64": "0.27.4",
+ "@esbuild/openbsd-x64": "0.27.4",
+ "@esbuild/openharmony-arm64": "0.27.4",
+ "@esbuild/sunos-x64": "0.27.4",
+ "@esbuild/win32-arm64": "0.27.4",
+ "@esbuild/win32-ia32": "0.27.4",
+ "@esbuild/win32-x64": "0.27.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/get-tsconfig": {
+ "version": "4.13.6",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
+ "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/nodemon": {
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vscode-jsonrpc": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz",
+ "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ }
+ }
+}
diff --git a/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/package.json b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/package.json
new file mode 100644
index 000000000..354efc32b
--- /dev/null
+++ b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "communitytoolkit-aspire-hosting-activemq-apphost-typescript",
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "build": "tsc",
+ "start": "aspire start --apphost apphost.ts",
+ "stop": "aspire stop --apphost apphost.ts",
+ "validate": "npm ci && npx tsc --noEmit",
+ "dev": "tsc --watch"
+ },
+ "dependencies": {
+ "vscode-jsonrpc": "^8.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "nodemon": "^3.1.11",
+ "tsx": "^4.19.0",
+ "typescript": "^5.3.0"
+ }
+}
diff --git a/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/tsconfig.json b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/tsconfig.json
new file mode 100644
index 000000000..9cf99bb13
--- /dev/null
+++ b/examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "include": ["apphost.ts", ".modules/**/*.ts", ".modules/**/*.d.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQBuilderExtensions.cs
index 3483045bc..8d0bf8308 100644
--- a/src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQBuilderExtensions.cs
+++ b/src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQBuilderExtensions.cs
@@ -7,6 +7,8 @@
using Microsoft.Extensions.Logging;
using System.Text;
+#pragma warning disable ASPIREATS001 // AspireExport is experimental
+
namespace Aspire.Hosting;
///
@@ -28,6 +30,7 @@ public static class ActiveMQBuilderExtensions
/// The scheme of the endpoint, e.g. tcp or activemq (for masstransit). Defaults to tcp.
/// The host port that the underlying webconsole is bound to when running locally.
/// A reference to the .
+ [AspireExport("addActiveMQ", Description = "Adds an ActiveMQ Classic container resource")]
public static IResourceBuilder AddActiveMQ(this IDistributedApplicationBuilder builder,
[ResourceName] string name,
IResourceBuilder? userName = null,
@@ -62,6 +65,7 @@ public static IResourceBuilder AddActiveMQ(this IDistrib
/// The scheme of the endpoint, e.g. tcp or activemq (for masstransit). Defaults to tcp.
/// The host port that the underlying webconsole is bound to when running locally.
/// A reference to the .
+ [AspireExport("addActiveMQArtemis", Description = "Adds an ActiveMQ Artemis container resource")]
public static IResourceBuilder AddActiveMQArtemis(this IDistributedApplicationBuilder builder,
[ResourceName] string name,
IResourceBuilder? userName = null,
@@ -70,6 +74,7 @@ public static IResourceBuilder AddActiveMQArtemis
string scheme = "tcp",
int? webPort = null)
{
+ ArgumentNullException.ThrowIfNull(builder, nameof(builder));
ArgumentNullException.ThrowIfNull(name, nameof(name));
ArgumentNullException.ThrowIfNull(scheme, nameof(scheme));
@@ -104,6 +109,7 @@ private static IResourceBuilder Build(this IDistributedApplicationBuilder
/// The name of the volume. Defaults to an auto-generated name based on the application and resource names.
/// A flag that indicates if this is a read-only volume.
/// The .
+ [AspireExport("withDataVolume", Description = "Adds a named volume for the data folder to an ActiveMQ container resource")]
public static IResourceBuilder WithDataVolume(this IResourceBuilder builder, string? name = null, bool isReadOnly = false)
where T : ActiveMQServerResourceBase =>
builder
@@ -118,6 +124,7 @@ public static IResourceBuilder WithDataVolume(this IResourceBuilder bui
/// The name of the volume. Defaults to an auto-generated name based on the application and resource names.
/// A flag that indicates if this is a read-only volume.
/// The .
+ [AspireExport("withConfVolume", Description = "Adds a named volume for the config folder to an ActiveMQ container resource")]
public static IResourceBuilder WithConfVolume(this IResourceBuilder builder, string? name = null, bool isReadOnly = false)
where T : ActiveMQServerResourceBase =>
builder
@@ -132,6 +139,7 @@ public static IResourceBuilder WithConfVolume(this IResourceBuilder bui
/// The source directory on the host to mount into the container.
/// A flag that indicates if this is a read-only mount.
/// The .
+ [AspireExport("withDataBindMount", Description = "Adds a bind mount for the data folder to an ActiveMQ container resource")]
public static IResourceBuilder WithDataBindMount(this IResourceBuilder builder, string source, bool isReadOnly = false)
where T : ActiveMQServerResourceBase =>
builder.WithBindMount(source, builder.Resource.ActiveMqSettings.DataPath, isReadOnly);
@@ -143,6 +151,7 @@ public static IResourceBuilder WithDataBindMount(this IResourceBuilder
/// The source directory on the host to mount into the container.
/// A flag that indicates if this is a read-only mount.
/// The .
+ [AspireExport("withConfBindMount", Description = "Adds a bind mount for the conf folder to an ActiveMQ container resource")]
public static IResourceBuilder WithConfBindMount(this IResourceBuilder builder, string source, bool isReadOnly = false)
where T : ActiveMQServerResourceBase =>
builder.WithBindMount(source, builder.Resource.ActiveMqSettings.ConfPath, isReadOnly);
@@ -214,3 +223,5 @@ private static IResourceBuilder WithJolokiaHealthCheck(
return builder;
}
}
+
+#pragma warning restore ASPIREATS001
diff --git a/src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQServerResourceBase.cs b/src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQServerResourceBase.cs
index bd4872cf4..ec4c7d567 100644
--- a/src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQServerResourceBase.cs
+++ b/src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQServerResourceBase.cs
@@ -2,6 +2,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
+#pragma warning disable ASPIREATS001 // AspireExport is experimental
+
namespace Aspire.Hosting.ApplicationModel;
///
@@ -12,6 +14,7 @@ namespace Aspire.Hosting.ApplicationModel;
/// A parameter that contains the ActiveMQ server password.
/// Scheme used in the connectionString (e.g. tcp or activemq, see MassTransit)
/// Settings being used for ActiveMQ Classic or Artemis
+[AspireExport(ExposeProperties = true)]
public abstract class ActiveMQServerResourceBase(string name, ParameterResource? userName, ParameterResource password, string scheme, IActiveMQSettings settings) : ContainerResource(name), IResourceWithConnectionString, IResourceWithEnvironment
{
internal const string PrimaryEndpointName = "tcp";
@@ -53,6 +56,8 @@ public abstract class ActiveMQServerResourceBase(string name, ParameterResource?
///
/// Gets the ActiveMQ settings.
///
+ /// This property is not available in polyglot app hosts.
+ [AspireExportIgnore(Reason = "IActiveMQSettings is an internal configuration type not compatible with ATS.")]
public IActiveMQSettings ActiveMqSettings { get; } = settings;
internal ReferenceExpression UserNameReference =>
@@ -79,4 +84,6 @@ IEnumerable> IResourceWithConnectionSt
private static T ThrowIfNull([NotNull] T? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
=> argument ?? throw new ArgumentNullException(paramName);
-}
\ No newline at end of file
+}
+
+#pragma warning restore ASPIREATS001
diff --git a/tests/CommunityToolkit.Aspire.Hosting.ActiveMQ.Tests/TypeScriptAppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.ActiveMQ.Tests/TypeScriptAppHostTests.cs
new file mode 100644
index 000000000..a1fc7ea45
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.ActiveMQ.Tests/TypeScriptAppHostTests.cs
@@ -0,0 +1,18 @@
+using CommunityToolkit.Aspire.Testing;
+
+namespace CommunityToolkit.Aspire.Hosting.ActiveMQ.Tests;
+
+[RequiresDocker]
+public class TypeScriptAppHostTests
+{
+ [Fact]
+ public async Task TypeScriptAppHostCompilesAndStarts()
+ {
+ await TypeScriptAppHostTest.Run(
+ appHostProject: "CommunityToolkit.Aspire.Hosting.ActiveMQ.AppHost.TypeScript",
+ packageName: "CommunityToolkit.Aspire.Hosting.ActiveMQ",
+ exampleName: "activemq",
+ waitForResources: ["classic", "classic2", "artemis", "artemis2"],
+ cancellationToken: TestContext.Current.CancellationToken);
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.Testing/ProcessTestUtilities.cs b/tests/CommunityToolkit.Aspire.Testing/ProcessTestUtilities.cs
new file mode 100644
index 000000000..473b545ec
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Testing/ProcessTestUtilities.cs
@@ -0,0 +1,68 @@
+using System.Diagnostics;
+using System.Text;
+using Xunit.Sdk;
+
+namespace CommunityToolkit.Aspire.Testing;
+
+public static class ProcessTestUtilities
+{
+ public static async Task RunProcessAsync(string fileName, IEnumerable arguments, string workingDirectory, CancellationToken cancellationToken = default)
+ {
+ ProcessStartInfo startInfo = new(fileName)
+ {
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ UseShellExecute = false,
+ WorkingDirectory = workingDirectory
+ };
+
+ foreach (string argument in arguments) {
+ startInfo.ArgumentList.Add(argument);
+ }
+
+ using Process process = new()
+ {
+ StartInfo = startInfo
+ };
+
+ StringBuilder standardOutput = new();
+ StringBuilder standardError = new();
+ process.OutputDataReceived += (_, args) =>
+ {
+ if (args.Data is not null)
+ {
+ standardOutput.AppendLine(args.Data);
+ }
+ };
+ process.ErrorDataReceived += (_, args) =>
+ {
+ if (args.Data is not null)
+ {
+ standardError.AppendLine(args.Data);
+ }
+ };
+
+ if (!process.Start())
+ {
+ throw new XunitException($"Failed to start process '{fileName}'.");
+ }
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ await process.WaitForExitAsync(cancellationToken);
+ process.WaitForExit();
+
+ if (process.ExitCode != 0)
+ {
+ string joinedArguments = string.Join(" ", arguments.Select(QuoteArgument));
+ throw new XunitException(
+ $"Command '{fileName} {joinedArguments}' failed with exit code {process.ExitCode}.{Environment.NewLine}" +
+ $"Standard output:{Environment.NewLine}{standardOutput}{Environment.NewLine}" +
+ $"Standard error:{Environment.NewLine}{standardError}");
+ }
+ }
+
+ private static string QuoteArgument(string argument) =>
+ argument.Contains(' ', StringComparison.Ordinal) ? $"\"{argument}\"" : argument;
+}
diff --git a/tests/CommunityToolkit.Aspire.Testing/TypeScriptAppHostTest.cs b/tests/CommunityToolkit.Aspire.Testing/TypeScriptAppHostTest.cs
new file mode 100644
index 000000000..927eec71b
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Testing/TypeScriptAppHostTest.cs
@@ -0,0 +1,76 @@
+namespace CommunityToolkit.Aspire.Testing;
+
+///
+/// Runs the shared TypeScript AppHost validation flow for example app hosts.
+///
+public static class TypeScriptAppHostTest
+{
+ ///
+ /// Runs the TypeScript AppHost validation script for an example app host.
+ ///
+ /// The app host project directory name under the example folder.
+ /// The integration package name and project directory name.
+ /// The example folder name.
+ /// The resources that must reach the expected Aspire state.
+ /// Optional commands that must exist on PATH before validation runs.
+ /// Optional Aspire resource status to wait for.
+ /// The cancellation token.
+ public static async Task Run(
+ string appHostProject,
+ string packageName,
+ string exampleName,
+ IEnumerable waitForResources,
+ IEnumerable? requiredCommands = null,
+ string? waitStatus = null,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(appHostProject);
+ ArgumentException.ThrowIfNullOrWhiteSpace(packageName);
+ ArgumentException.ThrowIfNullOrWhiteSpace(exampleName);
+ ArgumentNullException.ThrowIfNull(waitForResources);
+
+ List resources = waitForResources
+ .Where(static resource => !string.IsNullOrWhiteSpace(resource))
+ .ToList();
+
+ if (resources.Count == 0)
+ {
+ throw new ArgumentException("At least one resource name is required.", nameof(waitForResources));
+ }
+
+ List commands = requiredCommands?
+ .Where(static command => !string.IsNullOrWhiteSpace(command))
+ .ToList() ?? [];
+
+ string repoRoot = Path.GetFullPath(Path.Combine("..", "..", "..", "..", ".."));
+ string scriptPath = Path.Combine(repoRoot, "eng", "testing", "validate-typescript-apphost.ps1");
+ string appHostPath = Path.Combine(repoRoot, "examples", exampleName, appHostProject, "apphost.ts");
+ string packageProjectPath = Path.Combine(repoRoot, "src", packageName, $"{packageName}.csproj");
+ string shell = OperatingSystem.IsWindows() ? "pwsh.exe" : "pwsh";
+
+ List arguments =
+ [
+ "-NoLogo",
+ "-NoProfile",
+ "-File", scriptPath,
+ "-AppHostPath", appHostPath,
+ "-PackageProjectPath", packageProjectPath,
+ "-PackageName", packageName,
+ "-WaitForResources", string.Join(',', resources)
+ ];
+
+ if (!string.IsNullOrWhiteSpace(waitStatus))
+ {
+ arguments.Add("-WaitStatus");
+ arguments.Add(waitStatus);
+ }
+
+ if (commands.Count > 0)
+ {
+ arguments.Add("-RequiredCommands");
+ arguments.Add(string.Join(',', commands));
+ }
+
+ await ProcessTestUtilities.RunProcessAsync(shell, arguments, repoRoot, cancellationToken);
+ }
+}