chore: add zero-dependency static launcher

This commit is contained in:
Codex
2026-08-28 08:49:24 +09:00
parent bfc37e76c7
commit 4c5756a5a9
8 changed files with 539 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
param(
[ValidateRange(1024, 65535)]
[int]$Port = 4173,
[switch]$NoBrowser
)
$ErrorActionPreference = 'Stop'
$projectRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$rootWithSeparator = $projectRoot.TrimEnd([System.IO.Path]::DirectorySeparatorChar) + [System.IO.Path]::DirectorySeparatorChar
$serverUrl = "http://127.0.0.1:$Port/"
$listener = [System.Net.HttpListener]::new()
$listener.Prefixes.Add($serverUrl)
$mimeTypes = @{
'.css' = 'text/css; charset=utf-8'
'.flac' = 'audio/flac'
'.html' = 'text/html; charset=utf-8'
'.ico' = 'image/x-icon'
'.jpeg' = 'image/jpeg'
'.jpg' = 'image/jpeg'
'.js' = 'text/javascript; charset=utf-8'
'.json' = 'application/json; charset=utf-8'
'.md' = 'text/markdown; charset=utf-8'
'.png' = 'image/png'
'.svg' = 'image/svg+xml'
'.wav' = 'audio/wav'
'.webp' = 'image/webp'
}
function Send-TextResponse {
param(
[System.Net.HttpListenerResponse]$Response,
[int]$StatusCode,
[string]$Message
)
$body = [System.Text.Encoding]::UTF8.GetBytes($Message)
$Response.StatusCode = $StatusCode
$Response.ContentType = 'text/plain; charset=utf-8'
$Response.ContentLength64 = $body.Length
$Response.OutputStream.Write($body, 0, $body.Length)
}
try {
$listener.Start()
Write-Host "micarrsim is running at $serverUrl" -ForegroundColor Green
Write-Host 'Press Ctrl+C to stop.' -ForegroundColor DarkGray
if (-not $NoBrowser) {
Start-Process $serverUrl
}
while ($listener.IsListening) {
$context = $listener.GetContext()
try {
$relativePath = [System.Uri]::UnescapeDataString($context.Request.Url.AbsolutePath.TrimStart('/'))
$relativePath = $relativePath.Replace('/', [System.IO.Path]::DirectorySeparatorChar)
if ([string]::IsNullOrWhiteSpace($relativePath)) {
$relativePath = 'index.html'
}
$candidatePath = [System.IO.Path]::GetFullPath((Join-Path $projectRoot $relativePath))
$isWithinRoot = $candidatePath.Equals($projectRoot, [System.StringComparison]::OrdinalIgnoreCase) -or
$candidatePath.StartsWith($rootWithSeparator, [System.StringComparison]::OrdinalIgnoreCase)
if (-not $isWithinRoot) {
Send-TextResponse -Response $context.Response -StatusCode 403 -Message 'Forbidden'
continue
}
if ([System.IO.Directory]::Exists($candidatePath)) {
$candidatePath = Join-Path $candidatePath 'index.html'
}
if (-not [System.IO.File]::Exists($candidatePath)) {
Send-TextResponse -Response $context.Response -StatusCode 404 -Message 'Not found'
continue
}
$content = [System.IO.File]::ReadAllBytes($candidatePath)
$extension = [System.IO.Path]::GetExtension($candidatePath).ToLowerInvariant()
$contentType = $mimeTypes[$extension]
if ([string]::IsNullOrWhiteSpace($contentType)) {
$contentType = 'application/octet-stream'
}
$context.Response.StatusCode = 200
$context.Response.ContentType = $contentType
$context.Response.ContentLength64 = $content.Length
$context.Response.Headers['Cache-Control'] = 'no-store'
if ($context.Request.HttpMethod -ne 'HEAD') {
$context.Response.OutputStream.Write($content, 0, $content.Length)
}
}
catch {
if ($context.Response.OutputStream.CanWrite) {
Send-TextResponse -Response $context.Response -StatusCode 500 -Message 'Internal server error'
}
Write-Warning $_
}
finally {
$context.Response.OutputStream.Close()
}
}
}
finally {
if ($listener.IsListening) {
$listener.Stop()
}
$listener.Close()
}