refactor(parser): move parser into src/services, remove docker setup for dev environment, add dev startup script

This commit is contained in:
Sam Chau
2026-01-15 17:04:24 +10:30
parent bcf0a3ba55
commit 728d0f8aef
20 changed files with 138 additions and 123 deletions

7
.gitignore vendored
View File

@@ -28,3 +28,10 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# .NET
bin/
obj/
*.user
*.suo
.vs/

View File

@@ -27,7 +27,9 @@
"@std/yaml": "jsr:@std/yaml@^1.0.10"
},
"tasks": {
"dev": "DENO_ENV=development PORT=6969 HOST=0.0.0.0 APP_BASE_PATH=./dist/dev PARSER_HOST=localhost PARSER_PORT=5000 deno run -A npm:vite dev",
"dev": "deno run -A scripts/dev.ts",
"dev:vite": "DENO_ENV=development PORT=6969 HOST=0.0.0.0 APP_BASE_PATH=./dist/dev PARSER_HOST=localhost PARSER_PORT=5000 deno run -A npm:vite dev",
"dev:parser": "cd src/services/parser && dotnet watch run --urls http://localhost:5000",
"build": "APP_BASE_PATH=./dist/build deno run -A npm:vite build && deno compile --no-check --allow-net --allow-read --allow-write --allow-env --allow-ffi --allow-run --target x86_64-unknown-linux-gnu --output dist/build/profilarr dist/build/mod.ts",
"build:windows": "APP_BASE_PATH=./dist/build deno run -A npm:vite build && deno compile --no-check --allow-net --allow-read --allow-write --allow-env --allow-ffi --allow-run --target x86_64-pc-windows-msvc --output dist/windows/profilarr.exe dist/build/mod.ts",
"preview": "PORT=6868 HOST=0.0.0.0 APP_BASE_PATH=./dist/dev PARSER_HOST=localhost PARSER_PORT=5000 ./dist/build/profilarr",

3
deno.lock generated
View File

@@ -2112,7 +2112,8 @@
"npm:tailwindcss@^4.1.13",
"npm:typescript-eslint@^8.44.1",
"npm:typescript@^5.9.2",
"npm:vite@^7.1.7"
"npm:vite@^7.1.7",
"npm:yaml@^2.8.2"
]
}
}

77
scripts/dev.ts Normal file
View File

@@ -0,0 +1,77 @@
/**
* Dev script that runs parser and server concurrently with labeled output
*/
const colors = {
parser: '\x1b[33m', // yellow
server: '\x1b[34m', // blue
reset: '\x1b[0m'
};
async function streamOutput(
reader: ReadableStreamDefaultReader<Uint8Array>,
label: string,
color: string
) {
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
for (const line of text.split('\n')) {
if (line.trim()) {
console.log(`${color}[${label}]${colors.reset} ${line}`);
}
}
}
}
async function runParser() {
const cmd = new Deno.Command('dotnet', {
args: ['watch', 'run', '--urls', 'http://localhost:5000'],
cwd: 'src/services/parser',
stdout: 'piped',
stderr: 'piped'
});
const process = cmd.spawn();
await Promise.all([
streamOutput(process.stdout.getReader(), 'parser', colors.parser),
streamOutput(process.stderr.getReader(), 'parser', colors.parser)
]);
return process.status;
}
async function runVite() {
const cmd = new Deno.Command('deno', {
args: ['run', '-A', 'npm:vite', 'dev'],
env: {
...Deno.env.toObject(),
DENO_ENV: 'development',
PORT: '6969',
HOST: '0.0.0.0',
APP_BASE_PATH: './dist/dev',
PARSER_HOST: 'localhost',
PARSER_PORT: '5000'
},
stdout: 'piped',
stderr: 'piped'
});
const process = cmd.spawn();
await Promise.all([
streamOutput(process.stdout.getReader(), 'server', colors.server),
streamOutput(process.stderr.getReader(), 'server', colors.server)
]);
return process.status;
}
console.log(`${colors.parser}[parser]${colors.reset} Starting .NET parser service...`);
console.log(`${colors.server}[server]${colors.reset} Starting Vite dev server...`);
console.log('');
await Promise.all([runParser(), runVite()]);

View File

@@ -1,17 +0,0 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000
ENTRYPOINT ["dotnet", "Parser.dll"]

View File

@@ -1,14 +0,0 @@
services:
parser:
container_name: profilarr_parser
build: .
ports:
- "5000:5000"
environment:
- ASPNETCORE_ENVIRONMENT=Development
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s

View File

@@ -1,11 +1,24 @@
<script lang="ts">
import type { ComponentType } from 'svelte';
import type { Component } from 'svelte';
import type { MouseEventHandler } from 'svelte/elements';
export let checked: boolean = false;
export let icon: ComponentType;
export let color: string = 'accent'; // accent, blue, green, red, or hex color like #FFC230
export let shape: 'square' | 'circle' | 'rounded' = 'rounded';
export let disabled: boolean = false;
interface Props {
checked?: boolean;
icon: Component<{ size?: number; class?: string }>;
color?: string; // accent, blue, green, red, or hex color like #FFC230
shape?: 'square' | 'circle' | 'rounded';
disabled?: boolean;
onclick?: MouseEventHandler<HTMLButtonElement>;
}
let {
checked = false,
icon,
color = 'accent',
shape = 'rounded',
disabled = false,
onclick
}: Props = $props();
// Shape classes
const shapeClasses: Record<string, string> = {
@@ -14,9 +27,23 @@
rounded: 'rounded'
};
$: shapeClass = shapeClasses[shape] || shapeClasses.rounded;
$: isCustomColor = color.startsWith('#');
$: isAccent = color === 'accent';
const shapeClass = $derived(shapeClasses[shape] || shapeClasses.rounded);
const isCustomColor = $derived(color.startsWith('#'));
const isAccent = $derived(color === 'accent');
const colorClasses: Record<string, string> = {
accent:
'bg-accent-600 border-accent-600 dark:bg-accent-500 dark:border-accent-500 hover:brightness-110',
green:
'bg-green-600 border-green-600 dark:bg-green-500 dark:border-green-500 hover:brightness-110',
red: 'bg-red-600 border-red-600 dark:bg-red-500 dark:border-red-500 hover:brightness-110',
blue: 'bg-blue-600 border-blue-600 dark:bg-blue-500 dark:border-blue-500 hover:brightness-110'
};
const uncheckedClass =
'bg-neutral-50 border-neutral-300 hover:bg-neutral-100 hover:border-neutral-400 dark:bg-neutral-800 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:hover:border-neutral-500';
const checkedClass = $derived(colorClasses[color] || colorClasses.accent);
</script>
{#if isCustomColor}
@@ -25,104 +52,35 @@
role="checkbox"
aria-checked={checked}
{disabled}
on:click
{onclick}
class="flex h-5 w-5 items-center justify-center border-2 transition-all {shapeClass} {disabled
? 'cursor-not-allowed opacity-50'
: 'cursor-pointer focus:outline-none'} {checked
? 'hover:brightness-110'
: 'bg-neutral-50 hover:bg-neutral-100 hover:border-neutral-400 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:hover:border-neutral-500'}"
: uncheckedClass}"
style="background-color: {checked ? color : ''}; border-color: {checked
? color
: 'rgb(229, 231, 235)'};"
>
{#if checked}
<svelte:component this={icon} size={14} class="text-white" />
{/if}
</button>
{:else if isAccent}
<button
type="button"
role="checkbox"
aria-checked={checked}
{disabled}
on:click
class="flex h-5 w-5 items-center justify-center border-2 transition-all {shapeClass} {checked
? 'bg-accent-600 border-accent-600 dark:bg-accent-500 dark:border-accent-500 hover:brightness-110'
: 'bg-neutral-50 border-neutral-300 hover:bg-neutral-100 hover:border-neutral-400 dark:bg-neutral-800 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:hover:border-neutral-500'} {disabled
? 'cursor-not-allowed opacity-50'
: 'cursor-pointer focus:outline-none'}"
>
{#if checked}
<svelte:component this={icon} size={14} class="text-white" />
{/if}
</button>
{:else if color === 'green'}
<button
type="button"
role="checkbox"
aria-checked={checked}
{disabled}
on:click
class="flex h-5 w-5 items-center justify-center border-2 transition-all {shapeClass} {checked
? 'bg-green-600 border-green-600 dark:bg-green-500 dark:border-green-500 hover:brightness-110'
: 'bg-neutral-50 border-neutral-300 hover:bg-neutral-100 hover:border-neutral-400 dark:bg-neutral-800 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:hover:border-neutral-500'} {disabled
? 'cursor-not-allowed opacity-50'
: 'cursor-pointer focus:outline-none'}"
>
{#if checked}
<svelte:component this={icon} size={14} class="text-white" />
{/if}
</button>
{:else if color === 'red'}
<button
type="button"
role="checkbox"
aria-checked={checked}
{disabled}
on:click
class="flex h-5 w-5 items-center justify-center border-2 transition-all {shapeClass} {checked
? 'bg-red-600 border-red-600 dark:bg-red-500 dark:border-red-500 hover:brightness-110'
: 'bg-neutral-50 border-neutral-300 hover:bg-neutral-100 hover:border-neutral-400 dark:bg-neutral-800 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:hover:border-neutral-500'} {disabled
? 'cursor-not-allowed opacity-50'
: 'cursor-pointer focus:outline-none'}"
>
{#if checked}
<svelte:component this={icon} size={14} class="text-white" />
{/if}
</button>
{:else if color === 'blue'}
<button
type="button"
role="checkbox"
aria-checked={checked}
{disabled}
on:click
class="flex h-5 w-5 items-center justify-center border-2 transition-all {shapeClass} {checked
? 'bg-blue-600 border-blue-600 dark:bg-blue-500 dark:border-blue-500 hover:brightness-110'
: 'bg-neutral-50 border-neutral-300 hover:bg-neutral-100 hover:border-neutral-400 dark:bg-neutral-800 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:hover:border-neutral-500'} {disabled
? 'cursor-not-allowed opacity-50'
: 'cursor-pointer focus:outline-none'}"
>
{#if checked}
<svelte:component this={icon} size={14} class="text-white" />
{@const Icon = icon}
<Icon size={14} class="text-white" />
{/if}
</button>
{:else}
<!-- Fallback to accent for unknown colors -->
<button
type="button"
role="checkbox"
aria-checked={checked}
{disabled}
on:click
{onclick}
class="flex h-5 w-5 items-center justify-center border-2 transition-all {shapeClass} {checked
? 'bg-accent-600 border-accent-600 dark:bg-accent-500 dark:border-accent-500 hover:brightness-110'
: 'bg-neutral-50 border-neutral-300 hover:bg-neutral-100 hover:border-neutral-400 dark:bg-neutral-800 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:hover:border-neutral-500'} {disabled
? 'cursor-not-allowed opacity-50'
: 'cursor-pointer focus:outline-none'}"
? checkedClass
: uncheckedClass} {disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer focus:outline-none'}"
>
{#if checked}
<svelte:component this={icon} size={14} class="text-white" />
{@const Icon = icon}
<Icon size={14} class="text-white" />
{/if}
</button>
{/if}

View File

@@ -28,7 +28,8 @@
: ''}"
>
{#if icon}
<svelte:component this={icon} class="h-4 w-4" />
{@const Icon = icon}
<Icon class="h-4 w-4" />
{/if}
{label}
</a>

View File

@@ -32,12 +32,12 @@
<IconCheckbox
icon={Check}
checked={useEmojis}
on:click={toggle}
onclick={toggle}
/>
<button
type="button"
class="flex-1 text-left"
on:click={toggle}
onclick={toggle}
>
<span class="text-sm font-medium text-neutral-900 dark:text-neutral-50">
Use Emojis