From ceaa3892a762021400e97cb50c9613cfbeac57a3 Mon Sep 17 00:00:00 2001 From: Lajos Date: Fri, 27 Mar 2026 22:38:06 +0100 Subject: [PATCH 01/20] feat: MVP 1. (#7) * feat: MVP 1. * code review fixes * code review fixes pt.2 * regenerated schemas * code reviews pt. 2. * repo add fix * CORS and gitignore fix * code review fixes pt.1. * user settings refactor * cr fixes * token list fix * page layout optimalizations * progress * progress * progress * repository layer fixes * progress * code review fixes * code review fixes * added createElevatedContext * FS updates, system identity context * fs update * cr fixes * visual hierarchy update * persist log entries, bulk removes * progress on entity refactors * service details to data grid * review fixes, bulk remove fixes * navigation fixes * allow to clear logs * code review fixes * code review fixes * service creation and GH pull improvements * Form refactors * missing sync fixes * install fix * Service status history updates * log fix * process updates * detached process kill * export button * form handling improvements * updates * selection fix * service details page refactor * services refactor * prerequisites refactor * progress on prerequisites, cr fixes * component consolidation and cleanup * service form fixes * prerequisites and build fixes * progress on env.variables * prerequisite check in-memory * MCP server improvements * required files setup * github repo in memory search fix * fs update and fixes * theme support * furystack upgrade * eslint integration * prettier fix * test fix * gitignore fix * filled changelogs * removed e2e tests from main build pipeline * progress on fs upgrades * routes refactor * added stackCraftNavigate * type safe routing * config test fix * creation and smoke fix * shades upgrades, dog fooding progress * fixed dog fooding test --- service/data-backup-mp/service-logs.sqlite | Bin 0 -> 1671168 bytes service/data-backup-mp/stack-craft.sqlite | Bin 0 -> 241664 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 service/data-backup-mp/service-logs.sqlite create mode 100644 service/data-backup-mp/stack-craft.sqlite diff --git a/service/data-backup-mp/service-logs.sqlite b/service/data-backup-mp/service-logs.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..8aef413053e6d015af17997b3cb8f0018e42d9d8 GIT binary patch literal 1671168 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 z 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�J`cO-%ip`ZqrDYt(D_ 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 OdWOEUH~fWcoc{*}f#F{O literal 0 HcmV?d00001 diff --git a/service/data-backup-mp/stack-craft.sqlite b/service/data-backup-mp/stack-craft.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..82e800a697048ee7deb19b8ef7f029afa052c88f GIT binary patch literal 241664 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 yS69{mA^6X?yIU)Swz|u-t literal 0 HcmV?d00001 From f9b4b24efc833e28b37c6fb6c07c946cfcd4213c Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Fri, 27 Mar 2026 23:08:45 +0100 Subject: [PATCH 02/20] routing refactors --- e2e/dogfooding.spec.ts | 4 +- e2e/smoke.spec.ts | 23 +- frontend/src/components/app-routes.tsx | 94 ++++-- frontend/src/components/body.tsx | 10 +- frontend/src/components/breadcrumbs.tsx | 188 +++++++++++ frontend/src/components/repository-table.tsx | 5 +- frontend/src/components/service-table.tsx | 14 +- frontend/src/components/sidebar.tsx | 40 ++- frontend/src/pages/dashboard/index.tsx | 308 ++++++++---------- .../src/pages/import-export/import-stack.tsx | 4 +- .../prerequisites/prerequisites-list.tsx | 22 ++ .../pages/repositories/create-repository.tsx | 6 +- .../pages/repositories/edit-repository.tsx | 15 +- .../pages/repositories/repositories-list.tsx | 34 ++ .../src/pages/services/create-service.tsx | 7 +- .../src/pages/services/service-detail.tsx | 20 +- frontend/src/pages/services/service-logs.tsx | 1 + frontend/src/pages/services/services-list.tsx | 165 ++++++++++ frontend/src/pages/stacks/create-stack.tsx | 2 +- frontend/src/pages/stacks/edit-stack.tsx | 13 +- frontend/src/pages/stacks/stack-setup.tsx | 12 +- .../pages/wizards/create-service-wizard.tsx | 20 +- 22 files changed, 763 insertions(+), 244 deletions(-) create mode 100644 frontend/src/components/breadcrumbs.tsx create mode 100644 frontend/src/pages/prerequisites/prerequisites-list.tsx create mode 100644 frontend/src/pages/repositories/repositories-list.tsx create mode 100644 frontend/src/pages/services/services-list.tsx diff --git a/e2e/dogfooding.spec.ts b/e2e/dogfooding.spec.ts index d5b878c..889d820 100644 --- a/e2e/dogfooding.spec.ts +++ b/e2e/dogfooding.spec.ts @@ -31,7 +31,9 @@ test('DOG FOODING TIME - Create a service that uses the StackCraft GitHub reposi await expect(page.getByTestId('page-header-title')).toContainText(displayName) - // Create the service with the StackCraft GitHub repository + // Navigate to services list via the dashboard card + await page.locator('shade-dashboard a', { hasText: 'Services' }).click() + await expect(page.locator('shade-services-list')).toBeVisible() await page.locator('button', { hasText: 'Create Service' }).first().click() await expect(page.locator('shade-create-service-wizard')).toBeVisible() diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index c457f4b..a88ec1c 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -34,7 +34,9 @@ test.describe.serial('App Flow', () => { await expect(page.getByTestId('page-header-title')).toContainText(displayName) - // Create an example "Hello World" service + // Navigate to services list via the dashboard card + await page.locator('shade-dashboard a', { hasText: 'Services' }).click() + await expect(page.locator('shade-services-list')).toBeVisible() await page.locator('button', { hasText: 'Create Service' }).first().click() await expect(page.locator('shade-create-service-wizard')).toBeVisible() @@ -43,10 +45,13 @@ test.describe.serial('App Flow', () => { await page.locator('input[name="runCommand"]').fill('echo hello') await page.locator('button', { hasText: 'Create' }).click() - await expect(page.locator('shade-dashboard')).toBeVisible() - await expect(page.getByTestId('page-header-title')).toContainText(displayName) + await expect(page.locator('shade-services-list')).toBeVisible() - // Create a repository - FuryStack (https://github.com/furystack/furystack) + // Navigate back to dashboard then to repositories list via dashboard card + await page.locator('shade-sidebar-stack-link a', { hasText: 'Overview' }).first().click() + await expect(page.locator('shade-dashboard')).toBeVisible() + await page.locator('shade-dashboard a', { hasText: 'Repositories' }).click() + await expect(page.locator('shade-repositories-list')).toBeVisible() await page.locator('button', { hasText: 'Add Repository' }).first().click() await expect(page.locator('shade-create-repository')).toBeVisible() @@ -54,9 +59,13 @@ test.describe.serial('App Flow', () => { await page.locator('input[name="url"]').fill('https://github.com/furystack/furystack') await page.locator('button', { hasText: 'Add' }).click() - await expect(page.locator('shade-dashboard')).toBeVisible() - await expect(page.getByTestId('page-header-title')).toContainText(displayName) + await expect(page.locator('shade-repositories-list')).toBeVisible() + // Navigate back to dashboard then to services list via dashboard card + await page.locator('shade-sidebar-stack-link a', { hasText: 'Overview' }).first().click() + await expect(page.locator('shade-dashboard')).toBeVisible() + await page.locator('shade-dashboard a', { hasText: 'Services' }).click() + await expect(page.locator('shade-services-list')).toBeVisible() await page.locator('button', { hasText: 'Create Service' }).first().click() await expect(page.locator('shade-create-service-wizard')).toBeVisible() @@ -65,6 +74,6 @@ test.describe.serial('App Flow', () => { await page.locator('input[name="runCommand"]').fill('echo hello') await page.locator('button', { hasText: 'Create' }).click() - await expect(page.locator('shade-dashboard')).toBeVisible() + await expect(page.locator('shade-services-list')).toBeVisible() }) }) diff --git a/frontend/src/components/app-routes.tsx b/frontend/src/components/app-routes.tsx index 4bd90c0..8e568d7 100644 --- a/frontend/src/components/app-routes.tsx +++ b/frontend/src/components/app-routes.tsx @@ -3,11 +3,14 @@ import type { MatchResult } from 'path-to-regexp' import { Dashboard } from '../pages/dashboard/index.js' import { ExportStack } from '../pages/import-export/export-stack.js' import { ImportStack } from '../pages/import-export/import-stack.js' +import { PrerequisitesList } from '../pages/prerequisites/prerequisites-list.js' import { CreateRepository } from '../pages/repositories/create-repository.js' import { EditRepository } from '../pages/repositories/edit-repository.js' +import { RepositoriesList } from '../pages/repositories/repositories-list.js' import { CreateService } from '../pages/services/create-service.js' import { ServiceDetail } from '../pages/services/service-detail.js' import { ServiceLogs } from '../pages/services/service-logs.js' +import { ServicesList } from '../pages/services/services-list.js' import { UserSettings } from '../pages/settings/user-settings.js' import { CreateStack } from '../pages/stacks/create-stack.js' import { EditStack } from '../pages/stacks/edit-stack.js' @@ -18,55 +21,88 @@ export const appRoutes = { '/': { component: () => , }, - '/services/create/:stackName': { + '/settings': { + component: () => , + }, + '/stacks/create': { + component: () => , + }, + '/stacks/import': { + component: () => , + }, + '/stacks/:stackName': { component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( - + ), }, - '/services/wizard/:stackName': { + '/stacks/:stackName/edit': { component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( - + ), }, - '/services/:id/logs/:processUid': { - component: ({ match }: { match: MatchResult<{ id: string; processUid: string }> }) => ( - + '/stacks/:stackName/export': { + component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( + ), }, - '/services/:id/logs': { - component: ({ match }: { match: MatchResult<{ id: string }> }) => , + '/stacks/:stackName/setup': { + component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( + + ), }, - '/services/:id': { - component: ({ match }: { match: MatchResult<{ id: string }> }) => , + '/stacks/:stackName/services': { + component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( + + ), }, - '/repositories/create/:stackName': { + '/stacks/:stackName/services/create': { component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( - + ), }, - '/repositories/:id': { - component: ({ match }: { match: MatchResult<{ id: string }> }) => , + '/stacks/:stackName/services/wizard': { + component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( + + ), }, - '/settings': { - component: () => , + '/stacks/:stackName/services/:serviceId/logs/:processUid': { + component: ({ match }: { match: MatchResult<{ stackName: string; serviceId: string; processUid: string }> }) => ( + + ), }, - '/stacks/create': { - component: () => , + '/stacks/:stackName/services/:serviceId/logs': { + component: ({ match }: { match: MatchResult<{ stackName: string; serviceId: string }> }) => ( + + ), }, - '/stacks/import': { - component: () => , + '/stacks/:stackName/services/:serviceId': { + component: ({ match }: { match: MatchResult<{ stackName: string; serviceId: string }> }) => ( + + ), }, - '/stacks/:name/setup': { - component: ({ match }: { match: MatchResult<{ name: string }> }) => , + '/stacks/:stackName/repositories': { + component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( + + ), }, - '/stacks/:name/edit': { - component: ({ match }: { match: MatchResult<{ name: string }> }) => , + '/stacks/:stackName/repositories/create': { + component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( + + ), }, - '/stacks/:name': { - component: ({ match }: { match: MatchResult<{ name: string }> }) => , + '/stacks/:stackName/repositories/:repositoryId': { + component: ({ match }: { match: MatchResult<{ stackName: string; repositoryId: string }> }) => ( + + ), }, - '/stacks/:name/export': { - component: ({ match }: { match: MatchResult<{ name: string }> }) => , + '/stacks/:stackName/prerequisites': { + component: ({ match }: { match: MatchResult<{ stackName: string }> }) => ( + + ), }, } as const satisfies Record> diff --git a/frontend/src/components/body.tsx b/frontend/src/components/body.tsx index 21de290..6d267e3 100644 --- a/frontend/src/components/body.tsx +++ b/frontend/src/components/body.tsx @@ -4,6 +4,7 @@ import { Dashboard } from '../pages/dashboard/index.js' import { Init, Offline } from '../pages/index.js' import { SessionService } from '../services/session.js' import { appRoutes } from './app-routes.js' +import { Breadcrumbs } from './breadcrumbs.js' export const Body = Shade<{ style?: Partial; injector?: Injector }>({ customElementName: 'shade-app-body', @@ -15,7 +16,14 @@ export const Body = Shade<{ style?: Partial; injector?: Inj {(() => { switch (sessionState) { case 'authenticated': - return } /> + return ( +
+
+ +
+ } /> +
+ ) case 'offline': return default: diff --git a/frontend/src/components/breadcrumbs.tsx b/frontend/src/components/breadcrumbs.tsx new file mode 100644 index 0000000..a08d138 --- /dev/null +++ b/frontend/src/components/breadcrumbs.tsx @@ -0,0 +1,188 @@ +import { useEntitySync } from '@furystack/entity-sync-client' +import { createComponent, LocationService, Shade } from '@furystack/shades' +import { cssVariableTheme } from '@furystack/shades-common-components' +import { GitHubRepository as GitHubRepositoryModel, ServiceDefinition, StackDefinition } from 'common' + +import { StackCraftNestedRouteLink } from './app-routes.js' + +type BreadcrumbSegment = { + label: string + href?: Parameters[0]['href'] + params?: Record +} + +const parseBreadcrumbs = ( + pathname: string, +): { segments: BreadcrumbSegment[]; stackName?: string; serviceId?: string; repositoryId?: string } => { + const segments: BreadcrumbSegment[] = [] + const parts = pathname.split('/').filter(Boolean) + if (parts[0] !== 'stacks' || parts.length < 2) return { segments } + + let serviceId: string | undefined + let repositoryId: string | undefined + + const stackName = parts[1] + if (stackName === 'create' || stackName === 'import') return { segments } + + segments.push({ + label: stackName, + href: '/stacks/:stackName', + params: { stackName }, + }) + + const subSection = parts[2] + if (!subSection) return { segments, stackName } + + if (subSection === 'services') { + segments.push({ + label: 'Services', + href: '/stacks/:stackName/services', + params: { stackName }, + }) + + serviceId = parts[3] + if (serviceId && serviceId !== 'create' && serviceId !== 'wizard') { + segments.push({ + label: serviceId, + href: '/stacks/:stackName/services/:serviceId', + params: { stackName, serviceId }, + }) + + const logSection = parts[4] + if (logSection === 'logs') { + const processUid = parts[5] + if (processUid) { + segments.push({ + label: 'Logs', + href: '/stacks/:stackName/services/:serviceId/logs', + params: { stackName, serviceId }, + }) + segments.push({ label: `${processUid.slice(0, 8)}…` }) + } else { + segments.push({ label: 'Logs' }) + } + } + } else if (serviceId === 'create') { + segments.push({ label: 'Create' }) + } else if (serviceId === 'wizard') { + segments.push({ label: 'Create' }) + } + } else if (subSection === 'repositories') { + segments.push({ + label: 'Repositories', + href: '/stacks/:stackName/repositories', + params: { stackName }, + }) + + repositoryId = parts[3] + if (repositoryId && repositoryId !== 'create') { + segments.push({ label: repositoryId }) + } else if (repositoryId === 'create') { + segments.push({ label: 'Add' }) + } + } else if (subSection === 'prerequisites') { + segments.push({ label: 'Prerequisites' }) + } else if (subSection === 'edit') { + segments.push({ label: 'Edit' }) + } else if (subSection === 'export') { + segments.push({ label: 'Export' }) + } else if (subSection === 'setup') { + segments.push({ label: 'Setup' }) + } + + return { segments, stackName, serviceId, repositoryId } +} + +type EntityNameResolverProps = { + stackName?: string + serviceId?: string + repositoryId?: string + segments: BreadcrumbSegment[] +} + +const EntityNameResolver = Shade({ + customElementName: 'shade-entity-name-resolver', + css: { + display: 'flex', + alignItems: 'center', + gap: '6px', + fontSize: '13px', + color: cssVariableTheme.text.secondary, + padding: '8px 0', + flexWrap: 'wrap', + '& a': { + color: cssVariableTheme.text.secondary, + textDecoration: 'none', + transition: `color ${cssVariableTheme.transitions.duration.fast} ease`, + }, + '& a:hover': { + color: cssVariableTheme.text.primary, + }, + '& .breadcrumb-current': { + color: cssVariableTheme.text.primary, + fontWeight: cssVariableTheme.typography.fontWeight.semibold, + }, + '& .breadcrumb-separator': { + opacity: '0.4', + fontSize: '11px', + }, + }, + render: (options) => { + const { props } = options + const resolved = [...props.segments] + + const stackState = props.stackName ? useEntitySync(options, StackDefinition, props.stackName) : undefined + const serviceState = props.serviceId ? useEntitySync(options, ServiceDefinition, props.serviceId) : undefined + const repoState = props.repositoryId ? useEntitySync(options, GitHubRepositoryModel, props.repositoryId) : undefined + + if (stackState?.status === 'synced' && stackState.data) { + const stackSeg = resolved.find((s) => s.label === props.stackName) + if (stackSeg) stackSeg.label = stackState.data.displayName + } + + if (serviceState?.status === 'synced' && serviceState.data) { + const svcSeg = resolved.find((s) => s.label === props.serviceId) + if (svcSeg) svcSeg.label = serviceState.data.displayName + } + + if (repoState?.status === 'synced' && repoState.data) { + const repoSeg = resolved.find((s) => s.label === props.repositoryId) + if (repoSeg) repoSeg.label = repoState.data.displayName + } + + return ( + + ) + }, +}) + +export const Breadcrumbs = Shade({ + customElementName: 'shade-breadcrumbs', + render: ({ injector, useObservable }) => { + const [currentUrl] = useObservable('locationChange', injector.getInstance(LocationService).onLocationPathChanged) + + const { segments, stackName, serviceId, repositoryId } = parseBreadcrumbs(currentUrl) + + if (segments.length === 0) return + + return ( + + ) + }, +}) diff --git a/frontend/src/components/repository-table.tsx b/frontend/src/components/repository-table.tsx index 73b56b9..154618d 100644 --- a/frontend/src/components/repository-table.tsx +++ b/frontend/src/components/repository-table.tsx @@ -74,7 +74,10 @@ export const RepositoryTable = Shade({ ), url: (entry) => {entry.url}, actions: (entry) => ( - + diff --git a/frontend/src/components/service-table.tsx b/frontend/src/components/service-table.tsx index 42a23f1..78d86ca 100644 --- a/frontend/src/components/service-table.tsx +++ b/frontend/src/components/service-table.tsx @@ -182,7 +182,10 @@ export const ServiceTable = Shade({ startIcon={} /> )} - + - + @@ -162,142 +153,131 @@ export const Dashboard = Shade({ ) : null} - -
-

Services ({services.length})

- {hasSelection ? ( - {selectedServices.length} selected - ) : null} - {hasSelection ? ( - - {hasStopped ? ( - + + +
+
+ +

Services

+ + {services.length} + +
+ +
+ {services.length > 0 ? ( +
+ {runningCount > 0 ? ( + + {runningCount} running + ) : null} - {hasRunning ? ( - + + {svc.displayName} +
+ ))} + {services.length > 5 ? ( + + +{services.length - 5} more + ) : null} - - - - - -
- ) : null} -
- - - -
- {services.length === 0 ? ( -
-

No services in this stack yet.

-
- ) : ( - { - const newIds = selected.map((s) => s.id) - const changed = - newIds.length !== selectedServiceIds.length || newIds.some((id) => !selectedServiceIds.includes(id)) - if (changed) { - setSelectedServiceIds(newIds) - } - }} - /> - )} - +
+ ) : ( +

No services yet.

+ )} +
+
- -
-

Repositories

- - - -
- -
+ + +
+
+ +

Repositories

+ + {repos.length} + +
+ +
+ {repos.length > 0 ? ( +
+ {repos.slice(0, 5).map((repo) => ( + + {repo.displayName} + + ))} + {repos.length > 5 ? ( + + +{repos.length - 5} more + + ) : null} +
+ ) : ( +

No repositories yet.

+ )} +
+
- -
-

Prerequisites

-
- -
+ + +
+
+ +

Prerequisites

+ + {prereqs.length} + +
+ +
+ {prereqs.length > 0 ? ( +
+ {prereqs.slice(0, 5).map((prereq) => ( + + {prereq.name} + + ))} + {prereqs.length > 5 ? ( + + +{prereqs.length - 5} more + + ) : null} +
+ ) : ( +

No prerequisites yet.

+ )} +
+
) }, diff --git a/frontend/src/pages/import-export/import-stack.tsx b/frontend/src/pages/import-export/import-stack.tsx index 8b9487b..2de97d9 100644 --- a/frontend/src/pages/import-export/import-stack.tsx +++ b/frontend/src/pages/import-export/import-stack.tsx @@ -134,9 +134,9 @@ export const ImportStack = Shade({ }) const hasAutoSetup = formData.autoSetup === 'on' if (hasAutoSetup && (parsed.services?.length ?? 0) > 0) { - stackCraftNavigate(injector, '/stacks/:name/setup', { name: parsed.stack.name }) + stackCraftNavigate(injector, '/stacks/:stackName/setup', { stackName: parsed.stack.name }) } else { - stackCraftNavigate(injector, '/stacks/:name', { name: parsed.stack.name }) + stackCraftNavigate(injector, '/stacks/:stackName', { stackName: parsed.stack.name }) } } catch (error) { injector.getInstance(NotyService).emit('onNotyAdded', { diff --git a/frontend/src/pages/prerequisites/prerequisites-list.tsx b/frontend/src/pages/prerequisites/prerequisites-list.tsx new file mode 100644 index 0000000..6ceab21 --- /dev/null +++ b/frontend/src/pages/prerequisites/prerequisites-list.tsx @@ -0,0 +1,22 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Icon, icons, PageContainer, PageHeader, Paper } from '@furystack/shades-common-components' + +import { PrerequisiteTable } from '../../components/prerequisite-table.js' + +type PrerequisitesListProps = { + stackName: string +} + +export const PrerequisitesList = Shade({ + customElementName: 'shade-prerequisites-list', + render: ({ props }) => { + return ( + + } title="Prerequisites" /> + + + + + ) + }, +}) diff --git a/frontend/src/pages/repositories/create-repository.tsx b/frontend/src/pages/repositories/create-repository.tsx index 8c4d036..36131be 100644 --- a/frontend/src/pages/repositories/create-repository.tsx +++ b/frontend/src/pages/repositories/create-repository.tsx @@ -32,7 +32,7 @@ export const CreateRepository = Shade({ body: `"${data.displayName}" was added successfully.`, type: 'success', }) - stackCraftNavigate(injector, '/stacks/:name', { name: props.stackName }) + stackCraftNavigate(injector, '/stacks/:stackName/repositories', { stackName: props.stackName }) } catch (error) { injector.getInstance(NotyService).emit('onNotyAdded', { title: 'Error', @@ -50,7 +50,9 @@ export const CreateRepository = Shade({ mode="create" stackName={props.stackName} onSubmit={(data) => void handleSubmit(data)} - cancelHref="/" + onCancel={() => + stackCraftNavigate(injector, '/stacks/:stackName/repositories', { stackName: props.stackName }) + } /> diff --git a/frontend/src/pages/repositories/edit-repository.tsx b/frontend/src/pages/repositories/edit-repository.tsx index 4c4ca72..fe4ddd4 100644 --- a/frontend/src/pages/repositories/edit-repository.tsx +++ b/frontend/src/pages/repositories/edit-repository.tsx @@ -19,6 +19,7 @@ import { GitHubRepoForm } from '../../components/entity-forms/github-repo-form.j import { GitHubReposApiClient } from '../../services/api-clients/github-repos-api-client.js' type EditRepositoryProps = { + stackName: string repositoryId: string } @@ -47,7 +48,7 @@ export const EditRepository = Shade({ title="Error loading repository" description={repoState.error} actions={ - + @@ -65,7 +66,7 @@ export const EditRepository = Shade({ + @@ -96,7 +97,7 @@ export const EditRepository = Shade({ body: `"${data.displayName ?? repo.displayName}" was updated successfully.`, type: 'success', }) - stackCraftNavigate(injector, '/stacks/:name', { name: repo.stackName }) + stackCraftNavigate(injector, '/stacks/:stackName/repositories', { stackName: repo.stackName }) } catch (error) { injector.getInstance(NotyService).emit('onNotyAdded', { title: 'Error', @@ -119,7 +120,7 @@ export const EditRepository = Shade({ body: `"${repo.displayName}" was deleted.`, type: 'success', }) - stackCraftNavigate(injector, '/stacks/:name', { name: repo.stackName }) + stackCraftNavigate(injector, '/stacks/:stackName/repositories', { stackName: repo.stackName }) } catch (error) { injector.getInstance(NotyService).emit('onNotyAdded', { title: 'Error', @@ -136,7 +137,7 @@ export const EditRepository = Shade({ title={`Edit: ${repo.displayName}`} actions={
- + @@ -159,7 +160,9 @@ export const EditRepository = Shade({ stackName={repo.stackName} initial={repo} onSubmit={(data) => void handleSave(data)} - cancelHref="/" + onCancel={() => + stackCraftNavigate(injector, '/stacks/:stackName/repositories', { stackName: repo.stackName }) + } /> {ConfirmDialog(isConfirmingDelete, { diff --git a/frontend/src/pages/repositories/repositories-list.tsx b/frontend/src/pages/repositories/repositories-list.tsx new file mode 100644 index 0000000..3b399a4 --- /dev/null +++ b/frontend/src/pages/repositories/repositories-list.tsx @@ -0,0 +1,34 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Button, Icon, icons, PageContainer, PageHeader } from '@furystack/shades-common-components' + +import { StackCraftNestedRouteLink } from '../../components/app-routes.js' +import { RepositoryTable } from '../../components/repository-table.js' + +type RepositoriesListProps = { + stackName: string +} + +export const RepositoriesList = Shade({ + customElementName: 'shade-repositories-list', + render: ({ props }) => { + return ( + + } + title="Repositories" + actions={ + + + + } + /> + + + ) + }, +}) diff --git a/frontend/src/pages/services/create-service.tsx b/frontend/src/pages/services/create-service.tsx index a0a6904..8d43084 100644 --- a/frontend/src/pages/services/create-service.tsx +++ b/frontend/src/pages/services/create-service.tsx @@ -70,7 +70,10 @@ export const CreateService = Shade({ body: `Service "${data.displayName}" was created successfully.`, type: 'success', }) - stackCraftNavigate(injector, '/services/:id', { id: createdService.result.id }) + stackCraftNavigate(injector, '/stacks/:stackName/services/:serviceId', { + stackName: props.stackName, + serviceId: createdService.result.id, + }) } catch (error) { noty.emit('onNotyAdded', { title: 'Error', @@ -136,7 +139,7 @@ export const CreateService = Shade({ onSubmit={(data: Partial) => void handleSubmit(data)} onCreatePrerequisite={(data: Partial) => handleCreatePrerequisite(data)} onCreateRepository={(data: Partial) => handleCreateRepository(data)} - cancelHref="/" + onCancel={() => stackCraftNavigate(injector, '/stacks/:stackName/services', { stackName: props.stackName })} /> diff --git a/frontend/src/pages/services/service-detail.tsx b/frontend/src/pages/services/service-detail.tsx index 675516e..38834b0 100644 --- a/frontend/src/pages/services/service-detail.tsx +++ b/frontend/src/pages/services/service-detail.tsx @@ -65,6 +65,7 @@ const eventLabels: Record = { } type ServiceDetailProps = { + stackName: string serviceId: string } @@ -267,7 +268,7 @@ export const ServiceDetail = Shade({ body: `"${service.displayName}" was deleted.`, type: 'success', }) - stackCraftNavigate(injector, '/stacks/:name', { name: service.stackName }) + stackCraftNavigate(injector, '/stacks/:stackName/services', { stackName: service.stackName }) } catch (error) { noty.emit('onNotyAdded', { title: 'Error', @@ -373,7 +374,10 @@ export const ServiceDetail = Shade({ : `${prereqSatisfiedCount}/${servicePrereqs.length} prereqs`} ) : null} - + @@ -418,8 +422,8 @@ export const ServiceDetail = Shade({ Repository {linkedRepo.displayName} @@ -665,7 +669,7 @@ export const ServiceDetail = Shade({ stackEnvVars={stackConfig?.environmentVariables ?? {}} /> ) : null} - + {ConfirmDialog(isConfirmingDelete, { title: 'Delete Service', message: `Are you sure you want to delete "${service.displayName}"? This action cannot be undone.`, @@ -680,6 +684,7 @@ export const ServiceDetail = Shade({ type ServiceHistoryProps = { serviceId: string + stackName: string } type HistoryColumn = 'createdAt' | 'event' | 'triggeredBy' | 'triggerSource' | 'metadata' | 'processUid' @@ -777,8 +782,9 @@ const ServiceHistory = Shade({ + ) : null} + {hasRunning ? ( + + ) : null} + + + ) : null} + + + +
+ } + /> + {services.length === 0 ? ( +
No services in this stack yet.
+ ) : ( + { + const newIds = selected.map((s) => s.id) + const changed = + newIds.length !== selectedServiceIds.length || newIds.some((id) => !selectedServiceIds.includes(id)) + if (changed) { + setSelectedServiceIds(newIds) + } + }} + /> + )} + + ) + }, +}) diff --git a/frontend/src/pages/stacks/create-stack.tsx b/frontend/src/pages/stacks/create-stack.tsx index 92a0138..103fc9b 100644 --- a/frontend/src/pages/stacks/create-stack.tsx +++ b/frontend/src/pages/stacks/create-stack.tsx @@ -29,7 +29,7 @@ export const CreateStack = Shade({ type: 'success', }) - stackCraftNavigate(injector, '/stacks/:name', { name: createdStack.result.name }) + stackCraftNavigate(injector, '/stacks/:stackName', { stackName: createdStack.result.name }) } catch (error) { injector.getInstance(NotyService).emit('onNotyAdded', { title: 'Error', diff --git a/frontend/src/pages/stacks/edit-stack.tsx b/frontend/src/pages/stacks/edit-stack.tsx index 96618c5..2d28176 100644 --- a/frontend/src/pages/stacks/edit-stack.tsx +++ b/frontend/src/pages/stacks/edit-stack.tsx @@ -49,7 +49,7 @@ export const EditStack = Shade({ title="Error loading stack" description={stackState.error} actions={ - + @@ -102,7 +102,7 @@ export const EditStack = Shade({ body: `"${data.displayName ?? stack.displayName}" was updated successfully.`, type: 'success', }) - stackCraftNavigate(injector, '/stacks/:name', { name: stack.name }) + stackCraftNavigate(injector, '/stacks/:stackName', { stackName: stack.name }) } catch (error) { injector.getInstance(NotyService).emit('onNotyAdded', { title: 'Error', @@ -142,7 +142,7 @@ export const EditStack = Shade({ title={`Edit: ${stack.displayName}`} actions={
- + @@ -160,7 +160,12 @@ export const EditStack = Shade({ } /> - void handleSave(data)} cancelHref="/" /> + void handleSave(data)} + onCancel={() => stackCraftNavigate(injector, '/stacks/:stackName', { stackName: stack.name })} + /> ({ Set Up All Services )} - + @@ -236,7 +236,10 @@ export const StackSetup = Shade({
{ready ? ( - + )} - + {state.createdServiceId ? ( - + @@ -288,7 +291,10 @@ export const CreateServiceWizard = Shade({ Retry {state.createdServiceId ? ( - + @@ -296,7 +302,7 @@ export const CreateServiceWizard = Shade({ ) : null} + + {isOpen ? ( +
+
+ setFilter((ev.target as HTMLInputElement).value)} + style={{ + width: '100%', + padding: '6px 10px', + border: `1px solid ${theme.divider}`, + borderRadius: '4px', + backgroundColor: theme.background.default, + color: theme.text.primary, + fontSize: '13px', + outline: 'none', + boxSizing: 'border-box', + fontFamily: 'inherit', + }} + /> +
+ + {isLoading ? ( +
Loading...
+ ) : filteredBranches.length === 0 ? ( +
+ {filter ? 'No matching branches' : 'No branches found'} +
+ ) : ( +
+ {filteredBranches.map((branch) => { + const isCurrent = branch.name === currentBranch + return ( + + ) + })} +
+ )} +
+ ) : null} + + {isOpen ? ( +
setIsOpen(false)} + /> + ) : null} +
+ ) + }, +}) diff --git a/frontend/src/components/mini-pipeline-dots.tsx b/frontend/src/components/mini-pipeline-dots.tsx new file mode 100644 index 0000000..010928b --- /dev/null +++ b/frontend/src/components/mini-pipeline-dots.tsx @@ -0,0 +1,57 @@ +import { createComponent, Shade } from '@furystack/shades' +import { ThemeProviderService } from '@furystack/shades-common-components' +import type { ServiceView } from 'common' + +import { getPipelineStages, getStageColor } from '../utils/service-pipeline.js' + +type MiniPipelineDotsProps = { + service: ServiceView +} + +export const MiniPipelineDots = Shade({ + customElementName: 'shade-mini-pipeline-dots', + render: ({ props, injector }) => { + const stages = getPipelineStages(props.service) + const { theme } = injector.getInstance(ThemeProviderService) + + return ( +
`${s.label}: ${s.status}`).join(' | ')} + > + {stages.map((stage) => { + const color = getStageColor(stage.status) + const paletteColor = theme.palette[color] + const isSkipped = stage.status === 'skipped' + const isFilled = stage.status === 'done' || stage.status === 'failed' || stage.status === 'in-progress' + + return ( +
+ ) + })} + +
+ ) + }, +}) diff --git a/frontend/src/components/service-pipeline-stepper.tsx b/frontend/src/components/service-pipeline-stepper.tsx new file mode 100644 index 0000000..249f95b --- /dev/null +++ b/frontend/src/components/service-pipeline-stepper.tsx @@ -0,0 +1,101 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Button, Timeline, TimelineItem } from '@furystack/shades-common-components' +import type { ServiceView } from 'common' + +import type { PipelineStageStatus } from '../utils/service-pipeline.js' +import { getPipelineStages, getStageColor } from '../utils/service-pipeline.js' + +type ServicePipelineStepperProps = { + service: ServiceView + onAction?: (apiAction: string) => void + onViewLogs?: (stageId: string) => void +} + +const stageApiActions: Record = { + clone: '/services/:id/pull', + install: '/services/:id/install', + build: '/services/:id/build', + run: '/services/:id/start', +} + +const stageActionLabels: Record> = { + clone: { skipped: null, pending: 'Clone', 'in-progress': null, done: 'Pull', failed: 'Retry' }, + install: { skipped: null, pending: 'Install', 'in-progress': null, done: 'Reinstall', failed: 'Retry' }, + build: { skipped: null, pending: 'Build', 'in-progress': null, done: 'Rebuild', failed: 'Retry' }, + run: { skipped: null, pending: 'Start', 'in-progress': null, done: 'Restart', failed: 'Start' }, +} + +const statusDots: Record = { + skipped: '—', + pending: '○', + 'in-progress': '⏳', + done: '✓', + failed: '✗', +} + +export const ServicePipelineStepper = Shade({ + customElementName: 'shade-service-pipeline-stepper', + render: ({ props }) => { + const stages = getPipelineStages(props.service) + const visibleStages = stages.filter((s) => s.status !== 'skipped') + + const hasInProgress = visibleStages.some((s) => s.status === 'in-progress') + + return ( + + {visibleStages.map((stage) => { + const color = getStageColor(stage.status) + const actionLabel = stageActionLabels[stage.id]?.[stage.status] ?? null + const dot = {statusDots[stage.status]} + + const getActionTarget = () => { + if (stage.id === 'run' && stage.status === 'done') return '/services/:id/stop' + return stageApiActions[stage.id] + } + + return ( + +
+ {stage.command ? ( + + {stage.command} + + ) : null} +
+ {actionLabel && props.onAction ? ( + + ) : null} + {(stage.status === 'failed' || stage.status === 'done') && + stage.id !== 'clone' && + props.onViewLogs ? ( + + ) : null} +
+
+
+ ) + })} +
+ ) + }, +}) diff --git a/frontend/src/components/service-table.tsx b/frontend/src/components/service-table.tsx index 78d86ca..26b87ca 100644 --- a/frontend/src/components/service-table.tsx +++ b/frontend/src/components/service-table.tsx @@ -17,28 +17,19 @@ import { PrerequisiteCheckResult } from 'common' import { ServicesApiClient } from '../services/api-clients/services-api-client.js' import { applyClientFindOptions } from '../utils/apply-client-find-options.js' +import { getPrimaryAction } from '../utils/service-pipeline.js' import { StackCraftNestedRouteLink } from './app-routes.js' -import { RunStatusChip } from './status-chips.js' +import { MiniPipelineDots } from './mini-pipeline-dots.js' type ServiceTableProps = { services: ServiceView[] onSelectionChange?: (selected: ServiceView[]) => void } -type ServiceColumn = 'selection' | 'displayName' | 'runStatus' | 'actions' +type ServiceColumn = 'selection' | 'displayName' | 'pipeline' | 'branch' | 'actions' const columnFilters: { [K in ServiceColumn]?: ColumnFilterConfig } = { displayName: { type: 'string' }, - runStatus: { - type: 'enum', - values: [ - { label: 'Running', value: 'running' }, - { label: 'Stopped', value: 'stopped' }, - { label: 'Starting', value: 'starting' }, - { label: 'Stopping', value: 'stopping' }, - { label: 'Error', value: 'error' }, - ], - }, } export const ServiceTable = Shade({ @@ -75,7 +66,6 @@ export const ServiceTable = Shade({ return { satisfiedCount, failedCount, total: prereqIds.length } } - // Reconcile selection: remap stale object references to current entries by id const currentSelection = collectionService.selection.getValue() if (currentSelection.length > 0) { const entryById = new Map(entries.map((e) => [e.id, e])) @@ -92,7 +82,7 @@ export const ServiceTable = Shade({ return ( - columns={['selection', 'displayName', 'runStatus', 'actions']} + columns={['selection', 'displayName', 'pipeline', 'branch', 'actions']} findOptions={findOptions} onFindOptionsChange={setFindOptions} styles={undefined} @@ -100,88 +90,101 @@ export const ServiceTable = Shade({ columnFilters={columnFilters} headerComponents={{ selection: () => , + displayName: () => Service, + pipeline: () => Status, + branch: () => Branch, actions: () => Actions, }} rowComponents={{ selection: (entry) => , - displayName: (entry) => ( - - {entry.displayName} - {entry.description ? ( -
{entry.description}
- ) : null} -
- ), - runStatus: (entry) => { + displayName: (entry) => { const summary = getPrereqSummary(entry.prerequisiteIds) return ( -
- + + {entry.displayName} + {entry.description ? ( +
{entry.description}
+ ) : null} {summary ? ( - 0 - ? 'error' - : summary.satisfiedCount === summary.total - ? 'success' - : 'secondary' - } - title={`${summary.satisfiedCount}/${summary.total} prerequisite(s) satisfied`} - > - {summary.satisfiedCount === summary.total - ? `✓ ${summary.total} prereq` - : `${summary.satisfiedCount}/${summary.total} prereq`} - +
+ 0 + ? 'error' + : summary.satisfiedCount === summary.total + ? 'success' + : 'secondary' + } + title={`${summary.satisfiedCount}/${summary.total} prerequisite(s) satisfied`} + > + {summary.satisfiedCount === summary.total + ? `✓ ${summary.total} prereq` + : `${summary.satisfiedCount}/${summary.total} prereq`} + +
) : null} -
+
) }, + pipeline: (entry) => , + branch: (entry) => ( + + {entry.currentBranch ?? '—'} + + ), actions: (entry) => { - const needsSetup = - (entry.repositoryId && entry.cloneStatus !== 'cloned') || - (entry.installCommand && entry.installStatus !== 'installed') || - (entry.buildCommand && entry.buildStatus !== 'built') + const primary = getPrimaryAction(entry) return (
e.stopPropagation()} > - {needsSetup ? ( + {/* Context-aware primary action */} + {primary.apiAction ? ( - } - /> - - ) => void handleSave(data)} - onCreatePrerequisite={(data: Partial) => handleCreatePrerequisite(data)} - onCreateRepository={(data: Partial) => handleCreateRepository(data)} - onCancel={() => setIsEditing(false)} - /> - - - ) - } + const primary = getPrimaryAction(service) + const secondaryActions = getSecondaryActions(service) + + const tabs: Array<{ id: TabId; label: string }> = [ + { id: 'overview', label: 'Overview' }, + { id: 'logs', label: 'Logs' }, + { id: 'history', label: 'History' }, + { id: 'configuration', label: 'Configuration' }, + ] return ( + {/* Header */} + {service.repositoryId ? ( + + ) : null} {servicePrereqs.length > 0 ? ( ({ : `${prereqSatisfiedCount}/${servicePrereqs.length} prereqs`} ) : null} - - - - + ) : null} + {secondaryActions.map((action) => ( + + ))}
} /> + + {/* Tab bar */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab content */} + {activeTab === 'overview' ? ( + void runAction(apiAction, apiAction)} + onViewLogs={() => + stackCraftNavigate(injector, '/stacks/:stackName/services/:serviceId/logs', { + stackName: service.stackName, + serviceId: service.id, + }) + } + /> + ) : null} + + {activeTab === 'logs' ? ( + + ) : null} + + {activeTab === 'history' ? ( + + ) : null} + + {activeTab === 'configuration' ? ( + void handleSave(data)} + onCancel={() => setActiveTab('overview')} + onCreatePrerequisite={handleCreatePrerequisite} + onCreateRepository={handleCreateRepository} + onApplyFiles={async (relativePath?: string) => { + const key = relativePath ? `apply-file-${relativePath}` : 'apply-files-all' + setActionInProgress(key) + try { + await api.call({ + method: 'POST', + action: '/services/:id/apply-files', + url: { id: service.id }, + body: relativePath ? { relativePath } : {}, + }) + noty.emit('onNotyAdded', { + title: 'Files applied', + body: relativePath + ? `${relativePath} was written to disk.` + : 'All shared files were written to disk.', + type: 'success', + }) + } catch (error) { + noty.emit('onNotyAdded', { + title: 'Error', + body: error instanceof Error ? error.message : 'Failed to apply files', + type: 'error', + }) + } finally { + setActionInProgress(null) + } + }} + /> + ) : null} + + {ConfirmDialog(isConfirmingDelete, { + title: 'Delete Service', + message: `Are you sure you want to delete "${service.displayName}"? This action cannot be undone.`, + confirmText: 'Delete', + onConfirm: () => void handleDelete(), + onCancel: () => setIsConfirmingDelete(false), + })} + + ) + }, +}) + +/* ============================================ + * Overview Tab + * ============================================ */ + +type OverviewTabProps = { + service: ServiceView + linkedRepo: GitHubRepository | undefined + fullCwd: string | null + servicePrereqs: Prerequisite[] + prereqSatisfiedCount: number + prereqFailedCount: number + actionInProgress: string | null + onAction: (apiAction: string) => void + onViewLogs: (stageId: string) => void +} + +const OverviewTab = Shade({ + customElementName: 'shade-service-overview-tab', + render: ({ props }) => { + const { service, linkedRepo, fullCwd, servicePrereqs, prereqSatisfiedCount, prereqFailedCount } = props + + return ( +
{service.description ? ( ) : null} + + {/* Pipeline stepper */} + +

Pipeline

+ +
+ + {/* Service info */} +

Service Info

({
) : null} + {service.repositoryId ? ( +
+ Branch + + + + +
+ ) : null} Working Directory - {fullCwd ?? '(loading…)'} + {fullCwd ?? '(loading…)'} {fullCwd ? (
@@ -462,185 +628,10 @@ export const ServiceDetail = Shade({ ) : ( )} - {service.repositoryId ? ( -
- Clone - - -
- ) : null} - {service.installCommand ? ( -
- Install -
- {service.installCommand} - -
- -
- ) : null} - {service.buildCommand ? ( -
- Build -
- {service.buildCommand} - -
- -
- ) : null} - Run -
- {service.runCommand} - -
- {service.runStatus !== 'running' ? ( - - ) : ( - - )}
- {service.files && service.files.length > 0 ? ( - -
-

Shared Files

- - {service.files.length} file(s) - - -
-
- {service.files.map((file) => ( -
- {file.relativePath} - -
- ))} -
-
- ) : null} + + {/* Prerequisites */} {servicePrereqs.length > 0 ? (
@@ -662,26 +653,87 @@ export const ServiceDetail = Shade({ ) : null} - {servicePrereqs.some((p) => p.type === 'env-variable') ? ( - p.type === 'env-variable')} - stackEnvVars={stackConfig?.environmentVariables ?? {}} - /> - ) : null} - - {ConfirmDialog(isConfirmingDelete, { - title: 'Delete Service', - message: `Are you sure you want to delete "${service.displayName}"? This action cannot be undone.`, - confirmText: 'Delete', - onConfirm: () => void handleDelete(), - onCancel: () => setIsConfirmingDelete(false), - })} - +
) }, }) +/* ============================================ + * Logs Tab + * ============================================ */ + +type LogsTabProps = { + serviceId: string + stackName: string +} + +const LogsTab = Shade({ + customElementName: 'shade-service-logs-tab', + render: ({ props, injector }) => { + const api = injector.getInstance(ServicesApiClient) + const noty = injector.getInstance(NotyService) + + const handleClearLogs = async () => { + try { + await api.call({ + method: 'DELETE', + action: '/services/:id/logs', + url: { id: props.serviceId }, + }) + noty.emit('onNotyAdded', { + title: 'Logs cleared', + body: 'Service logs have been cleared.', + type: 'success', + }) + } catch (error) { + noty.emit('onNotyAdded', { + title: 'Error', + body: error instanceof Error ? error.message : 'Failed to clear logs', + type: 'error', + }) + } + } + + return ( +
+
+

Service Logs

+
+ + + + +
+
+ + + +
+ ) + }, +}) + +/* ============================================ + * History Tab + * ============================================ */ + type ServiceHistoryProps = { serviceId: string stackName: string @@ -719,7 +771,6 @@ const ServiceHistory = Shade({ const [findOptions, setFindOptions] = useState>>( 'findOptionsObservable', - { top: 25, order: { id: 'DESC' }, @@ -800,3 +851,117 @@ const ServiceHistory = Shade({ ) }, }) + +/* ============================================ + * Configuration Tab + * ============================================ */ + +type ConfigurationTabProps = { + service: ServiceView + repos: GitHubRepository[] + allPrereqs: Prerequisite[] + otherServices: ServiceDefinition[] + servicePrereqs: Prerequisite[] + stackConfig: StackConfig | undefined + actionInProgress: string | null + onSave: (data: Partial) => void + onCancel: () => void + onCreatePrerequisite: (data: Partial) => Promise + onCreateRepository: (data: Partial) => Promise + onApplyFiles: (relativePath?: string) => Promise +} + +const ConfigurationTab = Shade({ + customElementName: 'shade-service-config-tab', + render: ({ props }) => { + const { + service, + repos, + allPrereqs, + otherServices, + servicePrereqs, + stackConfig, + actionInProgress, + } = props + + return ( +
+ {/* Edit form */} + +

Edit Service

+ +
+ + {/* Shared files */} + {service.files && service.files.length > 0 ? ( + +
+

Shared Files

+ + {service.files.length} file(s) + + +
+
+ {service.files.map((file) => ( +
+ {file.relativePath} + +
+ ))} +
+
+ ) : null} + + {/* Environment variable overrides */} + {servicePrereqs.some((p) => p.type === 'env-variable') ? ( + p.type === 'env-variable')} + stackEnvVars={stackConfig?.environmentVariables ?? {}} + /> + ) : null} +
+ ) + }, +}) diff --git a/frontend/src/pages/services/services-list.tsx b/frontend/src/pages/services/services-list.tsx index 1a841e2..f67d9f2 100644 --- a/frontend/src/pages/services/services-list.tsx +++ b/frontend/src/pages/services/services-list.tsx @@ -131,6 +131,25 @@ export const ServicesList = Shade({ > Set Up + {hasRunning ? ( + + ) : null} + ) : null} = { + skipped: 'secondary', + pending: 'secondary', + 'in-progress': 'warning', + done: 'success', + failed: 'error', +} + +export const getStageColor = (status: PipelineStageStatus): keyof Palette => stageStatusColor[status] + +export const getPipelineStages = (service: ServiceView): PipelineStage[] => { + const stages: PipelineStage[] = [] + + if (service.repositoryId) { + const cloneStatus: PipelineStageStatus = + service.cloneStatus === 'cloned' + ? 'done' + : service.cloneStatus === 'cloning' + ? 'in-progress' + : service.cloneStatus === 'failed' + ? 'failed' + : 'pending' + stages.push({ id: 'clone', label: 'Clone', status: cloneStatus }) + } else { + stages.push({ id: 'clone', label: 'Clone', status: 'skipped' }) + } + + if (service.installCommand) { + const installStatus: PipelineStageStatus = + service.installStatus === 'installed' + ? 'done' + : service.installStatus === 'installing' + ? 'in-progress' + : service.installStatus === 'failed' + ? 'failed' + : 'pending' + stages.push({ id: 'install', label: 'Install', status: installStatus, command: service.installCommand }) + } else { + stages.push({ id: 'install', label: 'Install', status: 'skipped' }) + } + + if (service.buildCommand) { + const buildStatus: PipelineStageStatus = + service.buildStatus === 'built' + ? 'done' + : service.buildStatus === 'building' + ? 'in-progress' + : service.buildStatus === 'failed' + ? 'failed' + : 'pending' + stages.push({ id: 'build', label: 'Build', status: buildStatus, command: service.buildCommand }) + } else { + stages.push({ id: 'build', label: 'Build', status: 'skipped' }) + } + + const runStatus: PipelineStageStatus = + service.runStatus === 'running' + ? 'done' + : service.runStatus === 'starting' || service.runStatus === 'stopping' + ? 'in-progress' + : service.runStatus === 'error' + ? 'failed' + : 'pending' + stages.push({ id: 'run', label: 'Run', status: runStatus, command: service.runCommand }) + + return stages +} + +const stageActionMap: Record = { + clone: { label: 'Clone', retryLabel: 'Retry Clone', apiAction: '/services/:id/pull' }, + install: { label: 'Install', retryLabel: 'Retry Install', apiAction: '/services/:id/install' }, + build: { label: 'Build', retryLabel: 'Retry Build', apiAction: '/services/:id/build' }, + run: { label: 'Start', retryLabel: 'Start', apiAction: '/services/:id/start' }, +} + +export const getPrimaryAction = (service: ServiceView): ServiceAction => { + if (service.runStatus === 'running') { + return { label: 'Stop', apiAction: '/services/:id/stop', color: 'error', icon: 'stopCircle' } + } + if (service.runStatus === 'starting' || service.runStatus === 'stopping') { + return { label: 'Stop', apiAction: '/services/:id/stop', color: 'warning', icon: 'stopCircle' } + } + + const stages = getPipelineStages(service) + const failedStage = stages.find((s) => s.status === 'failed') + if (failedStage) { + const map = stageActionMap[failedStage.id] + return { label: map.retryLabel, apiAction: map.apiAction, color: 'error', icon: 'refresh' } + } + + const inProgressStage = stages.find((s) => s.status === 'in-progress') + if (inProgressStage) { + return { label: `${inProgressStage.label}ing...`, apiAction: '', color: 'warning', icon: 'loader' } + } + + const pendingStage = stages.find((s) => s.status === 'pending') + if (pendingStage) { + if (pendingStage.id === 'run') { + return { label: 'Start', apiAction: '/services/:id/start', color: 'success', icon: 'play' } + } + const map = stageActionMap[pendingStage.id] + return { label: map.label, apiAction: map.apiAction, color: 'primary', icon: 'settings' } + } + + return { label: 'Start', apiAction: '/services/:id/start', color: 'success', icon: 'play' } +} + +export const getSecondaryActions = (service: ServiceView): ServiceAction[] => { + const actions: ServiceAction[] = [] + + if (service.runStatus === 'running') { + actions.push({ label: 'Restart', apiAction: '/services/:id/restart', color: 'warning', icon: 'refresh' }) + actions.push({ label: 'Update', apiAction: '/services/:id/update', color: 'primary', icon: 'download' }) + } + + const stages = getPipelineStages(service) + const hasPendingStages = stages.some( + (s) => s.status === 'pending' && s.id !== 'run', + ) + if (hasPendingStages) { + actions.push({ label: 'Set Up All', apiAction: '/services/:id/setup', color: 'primary', icon: 'settings' }) + } + + if (service.runStatus === 'stopped' || service.runStatus === 'error') { + if (service.repositoryId && service.cloneStatus === 'cloned') { + actions.push({ label: 'Update', apiAction: '/services/:id/update', color: 'primary', icon: 'download' }) + } + } + + return actions +} + +export const needsSetup = (service: ServiceView): boolean => { + if (service.repositoryId && service.cloneStatus !== 'cloned') return true + if (service.installCommand && service.installStatus !== 'installed') return true + if (service.buildCommand && service.buildStatus !== 'built') return true + return false +} diff --git a/service/src/app-models/services/actions/service-branches-action.ts b/service/src/app-models/services/actions/service-branches-action.ts new file mode 100644 index 0000000..85bab99 --- /dev/null +++ b/service/src/app-models/services/actions/service-branches-action.ts @@ -0,0 +1,43 @@ +import { getRepository } from '@furystack/repository' +import { RequestError } from '@furystack/rest' +import { JsonResult, type RequestAction } from '@furystack/rest-service' +import type { ServiceBranchesEndpoint } from 'common' +import { ServiceDefinition, ServiceStatus } from 'common' +import { GitService } from '../../../services/git-service.js' +import { resolveServiceCwd } from '../../../utils/resolve-service-cwd.js' + +export const ServiceBranchesAction: RequestAction = async ({ + injector, + getUrlParams, +}) => { + const { id: serviceId } = getUrlParams() + const repo = getRepository(injector) + + const services = await repo + .getDataSetFor(ServiceDefinition, 'id') + .find(injector, { filter: { id: { $eq: serviceId } }, top: 1 }) + const svc = services[0] + if (!svc) throw new RequestError('Service not found', 404) + + const statuses = await repo + .getDataSetFor(ServiceStatus, 'serviceId') + .find(injector, { filter: { serviceId: { $eq: serviceId } }, top: 1 }) + const status = statuses[0] + if (!status || status.cloneStatus !== 'cloned') { + throw new RequestError('Repository is not cloned yet', 400) + } + + const cwd = await resolveServiceCwd(injector, svc) + const git = injector.getInstance(GitService) + + const [currentBranch, branches] = await Promise.all([ + git.getCurrentBranch(cwd), + git.getBranches(cwd), + ]) + + return JsonResult({ + currentBranch, + local: branches.local, + remote: branches.remote, + }) +} diff --git a/service/src/app-models/services/actions/service-checkout-action.ts b/service/src/app-models/services/actions/service-checkout-action.ts new file mode 100644 index 0000000..41a7f8e --- /dev/null +++ b/service/src/app-models/services/actions/service-checkout-action.ts @@ -0,0 +1,56 @@ +import { getCurrentUser } from '@furystack/core' +import { getRepository } from '@furystack/repository' +import { RequestError } from '@furystack/rest' +import { JsonResult, type RequestAction } from '@furystack/rest-service' +import type { ServiceCheckoutEndpoint } from 'common' +import { ServiceDefinition, ServiceStatus } from 'common' +import { GitService } from '../../../services/git-service.js' +import { ProcessManager } from '../../../services/process-manager.js' +import { resolveServiceCwd } from '../../../utils/resolve-service-cwd.js' + +export const ServiceCheckoutAction: RequestAction = async ({ + injector, + getUrlParams, + getBody, +}) => { + const { id: serviceId } = getUrlParams() + const { branch } = await getBody() + const repo = getRepository(injector) + + const services = await repo + .getDataSetFor(ServiceDefinition, 'id') + .find(injector, { filter: { id: { $eq: serviceId } }, top: 1 }) + const svc = services[0] + if (!svc) throw new RequestError('Service not found', 404) + + const statuses = await repo + .getDataSetFor(ServiceStatus, 'serviceId') + .find(injector, { filter: { serviceId: { $eq: serviceId } }, top: 1 }) + const status = statuses[0] + if (!status || status.cloneStatus !== 'cloned') { + throw new RequestError('Repository is not cloned yet', 400) + } + + const cwd = await resolveServiceCwd(injector, svc) + const git = injector.getInstance(GitService) + const pm = injector.getInstance(ProcessManager) + + await git.checkout(cwd, branch) + + const currentBranch = await git.getCurrentBranch(cwd) + + let username = 'unknown' + try { + const { username: resolvedUsername } = (await getCurrentUser(injector)) ?? {} + if (resolvedUsername) username = resolvedUsername + } catch { + // Identity context may not be available + } + + await pm.updateBranch(serviceId, currentBranch, { + triggeredBy: username, + triggerSource: 'api', + }) + + return JsonResult({ success: true, serviceId }) +} diff --git a/service/src/app-models/services/setup-services-rest-api.ts b/service/src/app-models/services/setup-services-rest-api.ts index 4c0f4bb..8011d9a 100644 --- a/service/src/app-models/services/setup-services-rest-api.ts +++ b/service/src/app-models/services/setup-services-rest-api.ts @@ -12,6 +12,8 @@ import { getCorsOptions } from '../../get-cors-options.js' import { getPort } from '../../get-port.js' import { ProcessManager } from '../../services/process-manager.js' import { ClearServiceLogsAction } from './actions/clear-service-logs-action.js' +import { ServiceBranchesAction } from './actions/service-branches-action.js' +import { ServiceCheckoutAction } from './actions/service-checkout-action.js' import { ServiceHistoryAction } from './actions/service-history-action.js' import { ServiceLifecycleAction } from './actions/service-lifecycle-action.js' import { ServiceLogsAction } from './actions/service-logs-action.js' @@ -89,6 +91,9 @@ export const setupServicesRestApi = async (injector: Injector) => { '/services/:id/history': Validate({ schema: servicesApiSchema, schemaName: 'ServiceHistoryEndpoint' })( ServiceHistoryAction, ), + '/services/:id/branches': Validate({ schema: servicesApiSchema, schemaName: 'ServiceBranchesEndpoint' })( + ServiceBranchesAction, + ), }, POST: { '/services': Validate({ schema: servicesApiSchema, schemaName: 'PostServiceEndpoint' })( @@ -165,6 +170,9 @@ export const setupServicesRestApi = async (injector: Injector) => { '/services/:id/update': Validate({ schema: servicesApiSchema, schemaName: 'ServiceActionEndpoint' })( ServiceLifecycleAction('update'), ), + '/services/:id/checkout': Validate({ schema: servicesApiSchema, schemaName: 'ServiceCheckoutEndpoint' })( + ServiceCheckoutAction, + ), '/services/:id/apply-files': Validate({ schema: servicesApiSchema, schemaName: 'ApplyServiceFilesEndpoint', diff --git a/service/src/services/process-manager.ts b/service/src/services/process-manager.ts index 9ddf099..00ae777 100644 --- a/service/src/services/process-manager.ts +++ b/service/src/services/process-manager.ts @@ -157,6 +157,7 @@ export class ProcessManager { installStatus: update.installStatus ?? current.installStatus, buildStatus: update.buildStatus ?? current.buildStatus, runStatus: update.runStatus ?? current.runStatus, + currentBranch: current.currentBranch, }) } catch { // May fire after disposal during shutdown — safe to ignore @@ -376,12 +377,14 @@ export class ProcessManager { mkdirSync(dirname(cwd), { recursive: true }) await git.clone(repo.url, cwd) await this.updateServiceStatus(serviceId, { cloneStatus: 'cloned' }, 'clone-completed', trigger) + await this.refreshCurrentBranch(serviceId, cwd) this.applySharedFiles(svc, cwd) return { cloned: true, pulled: false, updated: true } } else if (isGitRepo) { await this.logger.information({ message: `Pulling in ${cwd}` }) const { updated } = await git.pull(cwd) await this.updateServiceStatus(serviceId, { cloneStatus: 'cloned' }, 'clone-completed', trigger) + await this.refreshCurrentBranch(serviceId, cwd) this.applySharedFiles(svc, cwd) return { cloned: false, pulled: true, updated } } else { @@ -395,6 +398,7 @@ export class ProcessManager { mkdirSync(dirname(cwd), { recursive: true }) await git.clone(repo.url, cwd) await this.updateServiceStatus(serviceId, { cloneStatus: 'cloned' }, 'clone-completed', trigger) + await this.refreshCurrentBranch(serviceId, cwd) this.applySharedFiles(svc, cwd) return { cloned: true, pulled: false, updated: true } } @@ -406,6 +410,28 @@ export class ProcessManager { } } + /** + * Updates the currentBranch field on ServiceStatus. + */ + public async updateBranch(serviceId: string, branch: string, _trigger: TriggerContext): Promise { + const elevated = this.getElevatedInjector() + const statusDs = getRepository(elevated).getDataSetFor(ServiceStatus, 'serviceId') + await statusDs.update(elevated, serviceId, { currentBranch: branch, updatedAt: new Date().toISOString() }) + } + + private async refreshCurrentBranch(serviceId: string, cwd: string): Promise { + try { + const git = getInjectorReference(this).getInstance(GitService) + const branch = await git.getCurrentBranch(cwd) + const elevated = this.getElevatedInjector() + await getRepository(elevated) + .getDataSetFor(ServiceStatus, 'serviceId') + .update(elevated, serviceId, { currentBranch: branch }) + } catch { + // Best-effort; branch info is non-critical + } + } + private applySharedFiles(svc: ServiceDefinition, cwd: string): void { const files = svc.files ?? [] if (files.length === 0) return From e2b50a625ee4c5c6783f4f7632bfca5ce7c7ce30 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Sat, 28 Mar 2026 10:09:51 +0100 Subject: [PATCH 05/20] cr fixes --- common/schemas/entities.json | 212 ++------- common/schemas/github-repositories-api.json | 184 ++------ common/schemas/identity-api.json | 91 +--- common/schemas/install-api.json | 70 +-- common/schemas/prerequisites-api.json | 223 ++-------- common/schemas/services-api.json | 406 ++++-------------- common/schemas/stacks-api.json | 286 +++--------- common/schemas/system-api.json | 51 +-- common/schemas/tokens-api.json | 117 ++--- frontend/src/components/branch-selector.tsx | 56 ++- .../components/entity-forms/service-form.tsx | 40 +- .../environment-variables-manager.tsx | 54 +-- frontend/src/components/log-line.tsx | 3 +- frontend/src/components/log-viewer.tsx | 16 +- .../src/components/service-env-overrides.tsx | 58 +-- .../components/service-pipeline-stepper.tsx | 8 +- frontend/src/components/service-table.tsx | 13 +- frontend/src/pages/dashboard/index.tsx | 35 +- .../installer/check-prerequisites-step.tsx | 9 +- frontend/src/pages/login.tsx | 2 +- frontend/src/pages/offline.tsx | 5 +- .../src/pages/services/service-detail.tsx | 140 +++--- .../pages/wizards/create-service-wizard.tsx | 4 +- frontend/src/utils/service-pipeline.spec.ts | 231 ++++++++++ frontend/src/utils/service-pipeline.ts | 4 +- .../actions/service-branches-action.spec.ts | 124 ++++++ .../actions/service-branches-action.ts | 10 +- .../actions/service-checkout-action.spec.ts | 136 ++++++ 28 files changed, 1054 insertions(+), 1534 deletions(-) create mode 100644 frontend/src/utils/service-pipeline.spec.ts create mode 100644 service/src/app-models/services/actions/service-branches-action.spec.ts create mode 100644 service/src/app-models/services/actions/service-checkout-action.spec.ts diff --git a/common/schemas/entities.json b/common/schemas/entities.json index 1881db3..edc88b8 100644 --- a/common/schemas/entities.json +++ b/common/schemas/entities.json @@ -6,10 +6,7 @@ "properties": { "source": { "type": "string", - "enum": [ - "inherit", - "custom" - ], + "enum": ["inherit", "custom"], "description": "Whether to inherit the value from the host system or use a custom value" }, "customValue": { @@ -17,9 +14,7 @@ "description": "The custom value to use when source is 'custom'" } }, - "required": [ - "source" - ], + "required": ["source"], "additionalProperties": false, "description": "Describes how an environment variable's value is resolved. Used in {@link StackConfig } for stack-level defaults and in {@link ServiceConfig } for per-service overrides." }, @@ -48,13 +43,7 @@ "type": "string" } }, - "required": [ - "stackName", - "mainDirectory", - "environmentVariables", - "createdAt", - "updatedAt" - ], + "required": ["stackName", "mainDirectory", "environmentVariables", "createdAt", "updatedAt"], "additionalProperties": false, "description": "User-specific stack configuration. Contains settings unique to this installation/machine. Not included in exports - set by the user during import/installation." }, @@ -80,13 +69,7 @@ "type": "string" } }, - "required": [ - "name", - "displayName", - "description", - "createdAt", - "updatedAt" - ], + "required": ["name", "displayName", "description", "createdAt", "updatedAt"], "additionalProperties": false, "description": "Shareable stack definition. Contains the immutable identity and description of a stack. Included in stack exports and shared between installations." }, @@ -147,10 +130,7 @@ "description": "File content (usually plain text)" } }, - "required": [ - "relativePath", - "content" - ], + "required": ["relativePath", "content"], "additionalProperties": false, "description": "A file to be placed relative to the service root (e.g. .env, appConfig.local.json)" }, @@ -238,40 +218,19 @@ }, "CloneStatus": { "type": "string", - "enum": [ - "not-cloned", - "cloning", - "cloned", - "failed" - ] + "enum": ["not-cloned", "cloning", "cloned", "failed"] }, "InstallStatus": { "type": "string", - "enum": [ - "not-installed", - "installing", - "installed", - "failed" - ] + "enum": ["not-installed", "installing", "installed", "failed"] }, "BuildStatus": { "type": "string", - "enum": [ - "not-built", - "building", - "built", - "failed" - ] + "enum": ["not-built", "building", "built", "failed"] }, "RunStatus": { "type": "string", - "enum": [ - "stopped", - "starting", - "running", - "stopping", - "error" - ] + "enum": ["stopped", "starting", "running", "stopping", "error"] }, "ServiceStatus": { "type": "object", @@ -314,14 +273,7 @@ "type": "string" } }, - "required": [ - "serviceId", - "cloneStatus", - "installStatus", - "buildStatus", - "runStatus", - "updatedAt" - ], + "required": ["serviceId", "cloneStatus", "installStatus", "buildStatus", "runStatus", "updatedAt"], "additionalProperties": false, "description": "Runtime status of a service. Managed by the system (ProcessManager, GitWatcher). Never exported. Reset to defaults on import." }, @@ -532,10 +484,7 @@ } } }, - "required": [ - "username", - "roles" - ], + "required": ["username", "roles"], "additionalProperties": false }, "ServiceStateEvent": { @@ -568,13 +517,7 @@ }, "TriggerSource": { "type": "string", - "enum": [ - "api", - "mcp", - "auto-fetch", - "auto-restart", - "system" - ], + "enum": ["api", "mcp", "auto-fetch", "auto-restart", "system"], "description": "How a state change was triggered. Used to distinguish user actions from automated system behavior." }, "ServiceStateHistory": { @@ -620,14 +563,7 @@ "type": "string" } }, - "required": [ - "id", - "serviceId", - "event", - "triggeredBy", - "triggerSource", - "createdAt" - ], + "required": ["id", "serviceId", "event", "triggeredBy", "triggerSource", "createdAt"], "additionalProperties": false, "description": "Audit log entry for service state transitions. Records every lifecycle event (start, stop, crash, install, build, pull) with full context: who triggered it, how, and any relevant metadata. Entries are never deleted and not included in exports." }, @@ -645,10 +581,7 @@ }, "stream": { "type": "string", - "enum": [ - "stdout", - "stderr" - ] + "enum": ["stdout", "stderr"] }, "line": { "type": "string" @@ -657,14 +590,7 @@ "type": "string" } }, - "required": [ - "id", - "serviceId", - "processUid", - "stream", - "line", - "createdAt" - ], + "required": ["id", "serviceId", "processUid", "stream", "line", "createdAt"], "additionalProperties": false }, "PublicApiToken": { @@ -686,12 +612,7 @@ "type": "string" } }, - "required": [ - "id", - "username", - "name", - "createdAt" - ], + "required": ["id", "username", "name", "createdAt"], "additionalProperties": false }, "PrerequisiteType": { @@ -719,9 +640,7 @@ "type": "string" } }, - "required": [ - "minimumVersion" - ], + "required": ["minimumVersion"], "additionalProperties": false }, "yarn": { @@ -731,9 +650,7 @@ "type": "string" } }, - "required": [ - "minimumVersion" - ], + "required": ["minimumVersion"], "additionalProperties": false }, "dotnet-sdk": { @@ -743,9 +660,7 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false }, "dotnet-runtime": { @@ -755,9 +670,7 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false }, "nuget-feed": { @@ -770,9 +683,7 @@ "type": "string" } }, - "required": [ - "feedUrl" - ], + "required": ["feedUrl"], "additionalProperties": false }, "git": { @@ -794,9 +705,7 @@ "type": "string" } }, - "required": [ - "variableName" - ], + "required": ["variableName"], "additionalProperties": false }, "custom-script": { @@ -806,9 +715,7 @@ "type": "string" } }, - "required": [ - "script" - ], + "required": ["script"], "additionalProperties": false } }, @@ -835,9 +742,7 @@ "type": "string" } }, - "required": [ - "minimumVersion" - ], + "required": ["minimumVersion"], "additionalProperties": false }, { @@ -847,9 +752,7 @@ "type": "string" } }, - "required": [ - "minimumVersion" - ], + "required": ["minimumVersion"], "additionalProperties": false }, { @@ -859,9 +762,7 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false }, { @@ -871,9 +772,7 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false }, { @@ -886,9 +785,7 @@ "type": "string" } }, - "required": [ - "feedUrl" - ], + "required": ["feedUrl"], "additionalProperties": false }, { @@ -904,9 +801,7 @@ "type": "string" } }, - "required": [ - "variableName" - ], + "required": ["variableName"], "additionalProperties": false }, { @@ -916,9 +811,7 @@ "type": "string" } }, - "required": [ - "script" - ], + "required": ["script"], "additionalProperties": false } ], @@ -958,27 +851,13 @@ "type": "string" } }, - "required": [ - "id", - "stackName", - "name", - "type", - "config", - "installationHelp", - "createdAt", - "updatedAt" - ], + "required": ["id", "stackName", "name", "type", "config", "installationHelp", "createdAt", "updatedAt"], "additionalProperties": false, "description": "Shareable prerequisite definition. Describes an external requirement (e.g. Node.js, Git, an env variable) that a stack or service may need to be satisfied before it can run. Included in stack exports and shared between installations." }, "PrerequisiteCheckStatus": { "type": "string", - "enum": [ - "unchecked", - "checking", - "satisfied", - "failed" - ], + "enum": ["unchecked", "checking", "satisfied", "failed"], "description": "Possible statuses for a prerequisite check." }, "PrerequisiteCheckResult": { @@ -1001,12 +880,7 @@ "description": "ISO-8601 timestamp of the last check" } }, - "required": [ - "prerequisiteId", - "status", - "output", - "checkedAt" - ], + "required": ["prerequisiteId", "status", "output", "checkedAt"], "additionalProperties": false, "description": "Represents the result of evaluating a {@link Prerequisite } . Stored in an in-memory store on the service and synced to the frontend via entity sync so that every connected client sees real-time status." }, @@ -1040,15 +914,7 @@ "type": "string" } }, - "required": [ - "id", - "stackName", - "url", - "displayName", - "description", - "createdAt", - "updatedAt" - ], + "required": ["id", "stackName", "url", "displayName", "description", "createdAt", "updatedAt"], "additionalProperties": false, "description": "Shareable GitHub repository definition. Links a git repository to a stack for cloning and pulling. Included in stack exports and shared between installations." }, @@ -1074,14 +940,8 @@ "type": "string" } }, - "required": [ - "id", - "username", - "name", - "tokenHash", - "createdAt" - ], + "required": ["id", "username", "name", "tokenHash", "createdAt"], "additionalProperties": false } } -} \ No newline at end of file +} diff --git a/common/schemas/github-repositories-api.json b/common/schemas/github-repositories-api.json index b0749a5..0814bcb 100644 --- a/common/schemas/github-repositories-api.json +++ b/common/schemas/github-repositories-api.json @@ -25,13 +25,7 @@ "description": "Optional description" } }, - "required": [ - "id", - "stackName", - "url", - "displayName", - "description" - ], + "required": ["id", "stackName", "url", "displayName", "description"], "additionalProperties": false }, "PostGitHubRepoEndpoint": { @@ -44,10 +38,7 @@ "$ref": "#/definitions/WithOptionalId%3CGitHubRepoWritableFields%2C%22id%22%3E" } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "GitHubRepository": { @@ -80,15 +71,7 @@ "type": "string" } }, - "required": [ - "id", - "stackName", - "url", - "displayName", - "description", - "createdAt", - "updatedAt" - ], + "required": ["id", "stackName", "url", "displayName", "description", "createdAt", "updatedAt"], "additionalProperties": false, "description": "Shareable GitHub repository definition. Links a git repository to a stack for cloning and pulling. Included in stack exports and shared between installations." }, @@ -117,12 +100,7 @@ "description": "Optional description" } }, - "required": [ - "description", - "displayName", - "stackName", - "url" - ] + "required": ["description", "displayName", "stackName", "url"] }, "PatchGitHubRepoEndpoint": { "$ref": "#/definitions/PatchEndpoint%3CGitHubRepoWritableFields%2C%22id%22%3E" @@ -164,20 +142,14 @@ "description": "UUID primary key" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "body", - "url", - "result" - ], + "required": ["body", "url", "result"], "additionalProperties": false, "description": "Endpoint model for updating entities" }, @@ -191,9 +163,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { @@ -206,16 +176,11 @@ "type": "string" } }, - "required": [ - "accessible" - ], + "required": ["accessible"], "additionalProperties": false } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false }, "GitHubRepositoriesApi": { @@ -231,10 +196,7 @@ "$ref": "#/definitions/GetEntityEndpoint%3CGitHubRepository%2C%22id%22%3E" } }, - "required": [ - "/github-repositories", - "/github-repositories/:id" - ], + "required": ["/github-repositories", "/github-repositories/:id"], "additionalProperties": false }, "POST": { @@ -247,10 +209,7 @@ "$ref": "#/definitions/ValidateRepoEndpoint" } }, - "required": [ - "/github-repositories", - "/github-repositories/:id/validate" - ], + "required": ["/github-repositories", "/github-repositories/:id/validate"], "additionalProperties": false }, "PATCH": { @@ -260,9 +219,7 @@ "$ref": "#/definitions/PatchGitHubRepoEndpoint" } }, - "required": [ - "/github-repositories/:id" - ], + "required": ["/github-repositories/:id"], "additionalProperties": false }, "PUT": { @@ -291,9 +248,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -304,9 +259,7 @@ "$ref": "#/definitions/DeleteEndpoint%3CGitHubRepository%2C%22id%22%3E" } }, - "required": [ - "/github-repositories/:id" - ], + "required": ["/github-repositories/:id"], "additionalProperties": false }, "HEAD": { @@ -335,9 +288,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -367,9 +318,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -399,9 +348,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -431,19 +378,12 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET", - "POST", - "PATCH", - "DELETE" - ], + "required": ["GET", "POST", "PATCH", "DELETE"], "additionalProperties": false }, "GetCollectionEndpoint": { @@ -462,10 +402,7 @@ "$ref": "#/definitions/GetCollectionResult%3CGitHubRepository%3E" } }, - "required": [ - "query", - "result" - ], + "required": ["query", "result"], "additionalProperties": false, "description": "Rest endpoint model for getting / querying collections" }, @@ -485,52 +422,31 @@ "properties": { "id": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "stackName": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "url": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "displayName": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "description": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "createdAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "updatedAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] } }, "additionalProperties": false, @@ -540,15 +456,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "id", - "stackName", - "url", - "displayName", - "description", - "createdAt", - "updatedAt" - ] + "enum": ["id", "stackName", "url", "displayName", "description", "createdAt", "updatedAt"] }, "description": "The result set will be limited to these fields" }, @@ -1009,10 +917,7 @@ "description": "List of the selected entities" } }, - "required": [ - "count", - "entries" - ], + "required": ["count", "entries"], "additionalProperties": false, "description": "Response Model for GetCollection" }, @@ -1026,15 +931,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "id", - "stackName", - "url", - "displayName", - "description", - "createdAt", - "updatedAt" - ] + "enum": ["id", "stackName", "url", "displayName", "description", "createdAt", "updatedAt"] }, "description": "The list of fields to select" } @@ -1049,20 +946,14 @@ "description": "The entity's unique identifier" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "$ref": "#/definitions/GitHubRepository" } }, - "required": [ - "query", - "url", - "result" - ], + "required": ["query", "url", "result"], "additionalProperties": false, "description": "Endpoint model for getting a single entity" }, @@ -1077,21 +968,16 @@ "description": "UUID primary key" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false, "description": "Endpoint model for deleting entities" } } -} \ No newline at end of file +} diff --git a/common/schemas/identity-api.json b/common/schemas/identity-api.json index 2319f9b..03ed913 100644 --- a/common/schemas/identity-api.json +++ b/common/schemas/identity-api.json @@ -11,15 +11,11 @@ "type": "boolean" } }, - "required": [ - "isAuthenticated" - ], + "required": ["isAuthenticated"], "additionalProperties": false } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "GetCurrentUserAction": { @@ -29,9 +25,7 @@ "$ref": "#/definitions/User" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "User": { @@ -47,10 +41,7 @@ } } }, - "required": [ - "username", - "roles" - ], + "required": ["username", "roles"], "additionalProperties": false }, "LoginAction": { @@ -69,17 +60,11 @@ "type": "string" } }, - "required": [ - "username", - "password" - ], + "required": ["username", "password"], "additionalProperties": false } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "LogoutAction": { @@ -87,9 +72,7 @@ "properties": { "result": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "PasswordResetAction": { @@ -102,9 +85,7 @@ "type": "boolean" } }, - "required": [ - "success" - ], + "required": ["success"], "additionalProperties": false }, "body": { @@ -117,17 +98,11 @@ "type": "string" } }, - "required": [ - "currentPassword", - "newPassword" - ], + "required": ["currentPassword", "newPassword"], "additionalProperties": false } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "IdentityApi": { @@ -143,10 +118,7 @@ "$ref": "#/definitions/GetCurrentUserAction" } }, - "required": [ - "/isAuthenticated", - "/currentUser" - ], + "required": ["/isAuthenticated", "/currentUser"], "additionalProperties": false }, "POST": { @@ -162,11 +134,7 @@ "$ref": "#/definitions/PasswordResetAction" } }, - "required": [ - "/login", - "/logout", - "/password-reset" - ], + "required": ["/login", "/logout", "/password-reset"], "additionalProperties": false }, "PATCH": { @@ -195,9 +163,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -227,9 +193,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -259,9 +223,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -291,9 +253,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -323,9 +283,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -355,9 +313,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -387,18 +343,13 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET", - "POST" - ], + "required": ["GET", "POST"], "additionalProperties": false } } -} \ No newline at end of file +} diff --git a/common/schemas/install-api.json b/common/schemas/install-api.json index 340b73a..c46e57d 100644 --- a/common/schemas/install-api.json +++ b/common/schemas/install-api.json @@ -3,10 +3,7 @@ "definitions": { "InstallState": { "type": "string", - "enum": [ - "needsInstall", - "installed" - ] + "enum": ["needsInstall", "installed"] }, "InstallStateResponse": { "type": "object", @@ -15,9 +12,7 @@ "$ref": "#/definitions/InstallState" } }, - "required": [ - "state" - ], + "required": ["state"], "additionalProperties": false }, "GetServiceStatusAction": { @@ -27,9 +22,7 @@ "$ref": "#/definitions/InstallStateResponse" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "InstallAction": { @@ -42,9 +35,7 @@ "type": "boolean" } }, - "required": [ - "success" - ], + "required": ["success"], "additionalProperties": false }, "body": { @@ -57,17 +48,11 @@ "type": "string" } }, - "required": [ - "username", - "password" - ], + "required": ["username", "password"], "additionalProperties": false } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "InstallApi": { @@ -80,9 +65,7 @@ "$ref": "#/definitions/GetServiceStatusAction" } }, - "required": [ - "/serviceStatus" - ], + "required": ["/serviceStatus"], "additionalProperties": false }, "POST": { @@ -92,9 +75,7 @@ "$ref": "#/definitions/InstallAction" } }, - "required": [ - "/install" - ], + "required": ["/install"], "additionalProperties": false }, "PATCH": { @@ -123,9 +104,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -155,9 +134,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -187,9 +164,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -219,9 +194,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -251,9 +224,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -283,9 +254,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -315,18 +284,13 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET", - "POST" - ], + "required": ["GET", "POST"], "additionalProperties": false } } -} \ No newline at end of file +} diff --git a/common/schemas/prerequisites-api.json b/common/schemas/prerequisites-api.json index 49c14cd..1ade5fc 100644 --- a/common/schemas/prerequisites-api.json +++ b/common/schemas/prerequisites-api.json @@ -29,14 +29,7 @@ "description": "Help text shown when the prerequisite check fails" } }, - "required": [ - "id", - "stackName", - "name", - "type", - "config", - "installationHelp" - ], + "required": ["id", "stackName", "name", "type", "config", "installationHelp"], "additionalProperties": false }, "PrerequisiteType": { @@ -63,9 +56,7 @@ "type": "string" } }, - "required": [ - "minimumVersion" - ], + "required": ["minimumVersion"], "additionalProperties": false }, { @@ -75,9 +66,7 @@ "type": "string" } }, - "required": [ - "minimumVersion" - ], + "required": ["minimumVersion"], "additionalProperties": false }, { @@ -87,9 +76,7 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false }, { @@ -99,9 +86,7 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false }, { @@ -114,9 +99,7 @@ "type": "string" } }, - "required": [ - "feedUrl" - ], + "required": ["feedUrl"], "additionalProperties": false }, { @@ -132,9 +115,7 @@ "type": "string" } }, - "required": [ - "variableName" - ], + "required": ["variableName"], "additionalProperties": false }, { @@ -144,9 +125,7 @@ "type": "string" } }, - "required": [ - "script" - ], + "required": ["script"], "additionalProperties": false } ], @@ -162,10 +141,7 @@ "$ref": "#/definitions/WithOptionalId%3CPrerequisiteWritableFields%2C%22id%22%3E" } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "Prerequisite": { @@ -202,16 +178,7 @@ "type": "string" } }, - "required": [ - "id", - "stackName", - "name", - "type", - "config", - "installationHelp", - "createdAt", - "updatedAt" - ], + "required": ["id", "stackName", "name", "type", "config", "installationHelp", "createdAt", "updatedAt"], "additionalProperties": false, "description": "Shareable prerequisite definition. Describes an external requirement (e.g. Node.js, Git, an env variable) that a stack or service may need to be satisfied before it can run. Included in stack exports and shared between installations." }, @@ -244,13 +211,7 @@ "description": "Help text shown when the prerequisite check fails" } }, - "required": [ - "config", - "installationHelp", - "name", - "stackName", - "type" - ] + "required": ["config", "installationHelp", "name", "stackName", "type"] }, "PatchPrerequisiteEndpoint": { "$ref": "#/definitions/PatchEndpoint%3CPrerequisiteWritableFields%2C%22id%22%3E" @@ -296,20 +257,14 @@ "description": "UUID primary key" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "body", - "url", - "result" - ], + "required": ["body", "url", "result"], "additionalProperties": false, "description": "Endpoint model for updating entities" }, @@ -323,9 +278,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { @@ -338,17 +291,11 @@ "type": "string" } }, - "required": [ - "satisfied", - "output" - ], + "required": ["satisfied", "output"], "additionalProperties": false } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false }, "PrerequisitesApi": { @@ -364,10 +311,7 @@ "$ref": "#/definitions/GetEntityEndpoint%3CPrerequisite%2C%22id%22%3E" } }, - "required": [ - "/prerequisites", - "/prerequisites/:id" - ], + "required": ["/prerequisites", "/prerequisites/:id"], "additionalProperties": false }, "POST": { @@ -380,10 +324,7 @@ "$ref": "#/definitions/CheckPrerequisiteEndpoint" } }, - "required": [ - "/prerequisites", - "/prerequisites/:id/check" - ], + "required": ["/prerequisites", "/prerequisites/:id/check"], "additionalProperties": false }, "PATCH": { @@ -393,9 +334,7 @@ "$ref": "#/definitions/PatchPrerequisiteEndpoint" } }, - "required": [ - "/prerequisites/:id" - ], + "required": ["/prerequisites/:id"], "additionalProperties": false }, "PUT": { @@ -424,9 +363,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -437,9 +374,7 @@ "$ref": "#/definitions/DeleteEndpoint%3CPrerequisite%2C%22id%22%3E" } }, - "required": [ - "/prerequisites/:id" - ], + "required": ["/prerequisites/:id"], "additionalProperties": false }, "HEAD": { @@ -468,9 +403,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -500,9 +433,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -532,9 +463,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -564,19 +493,12 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET", - "POST", - "PATCH", - "DELETE" - ], + "required": ["GET", "POST", "PATCH", "DELETE"], "additionalProperties": false }, "GetCollectionEndpoint": { @@ -595,10 +517,7 @@ "$ref": "#/definitions/GetCollectionResult%3CPrerequisite%3E" } }, - "required": [ - "query", - "result" - ], + "required": ["query", "result"], "additionalProperties": false, "description": "Rest endpoint model for getting / querying collections" }, @@ -618,59 +537,35 @@ "properties": { "id": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "stackName": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "name": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "type": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "config": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "installationHelp": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "createdAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "updatedAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] } }, "additionalProperties": false, @@ -680,16 +575,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "id", - "stackName", - "name", - "type", - "config", - "installationHelp", - "createdAt", - "updatedAt" - ] + "enum": ["id", "stackName", "name", "type", "config", "installationHelp", "createdAt", "updatedAt"] }, "description": "The result set will be limited to these fields" }, @@ -1188,10 +1074,7 @@ "description": "List of the selected entities" } }, - "required": [ - "count", - "entries" - ], + "required": ["count", "entries"], "additionalProperties": false, "description": "Response Model for GetCollection" }, @@ -1205,16 +1088,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "id", - "stackName", - "name", - "type", - "config", - "installationHelp", - "createdAt", - "updatedAt" - ] + "enum": ["id", "stackName", "name", "type", "config", "installationHelp", "createdAt", "updatedAt"] }, "description": "The list of fields to select" } @@ -1229,20 +1103,14 @@ "description": "The entity's unique identifier" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "$ref": "#/definitions/Prerequisite" } }, - "required": [ - "query", - "url", - "result" - ], + "required": ["query", "url", "result"], "additionalProperties": false, "description": "Endpoint model for getting a single entity" }, @@ -1257,21 +1125,16 @@ "description": "UUID primary key" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false, "description": "Endpoint model for deleting entities" } } -} \ No newline at end of file +} diff --git a/common/schemas/services-api.json b/common/schemas/services-api.json index 06e931a..eb685e2 100644 --- a/common/schemas/services-api.json +++ b/common/schemas/services-api.json @@ -86,10 +86,7 @@ "description": "File content (usually plain text)" } }, - "required": [ - "relativePath", - "content" - ], + "required": ["relativePath", "content"], "additionalProperties": false, "description": "A file to be placed relative to the service root (e.g. .env, appConfig.local.json)" }, @@ -134,10 +131,7 @@ "properties": { "source": { "type": "string", - "enum": [ - "inherit", - "custom" - ], + "enum": ["inherit", "custom"], "description": "Whether to inherit the value from the host system or use a custom value" }, "customValue": { @@ -145,9 +139,7 @@ "description": "The custom value to use when source is 'custom'" } }, - "required": [ - "source" - ], + "required": ["source"], "additionalProperties": false, "description": "Describes how an environment variable's value is resolved. Used in {@link StackConfig } for stack-level defaults and in {@link ServiceConfig } for per-service overrides." }, @@ -257,10 +249,7 @@ "$ref": "#/definitions/WithOptionalId%3CServiceWritableFields%2C%22id%22%3E" } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "ServiceView": { @@ -409,40 +398,19 @@ }, "CloneStatus": { "type": "string", - "enum": [ - "not-cloned", - "cloning", - "cloned", - "failed" - ] + "enum": ["not-cloned", "cloning", "cloned", "failed"] }, "InstallStatus": { "type": "string", - "enum": [ - "not-installed", - "installing", - "installed", - "failed" - ] + "enum": ["not-installed", "installing", "installed", "failed"] }, "BuildStatus": { "type": "string", - "enum": [ - "not-built", - "building", - "built", - "failed" - ] + "enum": ["not-built", "building", "built", "failed"] }, "RunStatus": { "type": "string", - "enum": [ - "stopped", - "starting", - "running", - "stopping", - "error" - ] + "enum": ["stopped", "starting", "running", "stopping", "error"] }, "WithOptionalId": { "type": "object", @@ -635,20 +603,14 @@ "description": "UUID primary key" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "body", - "url", - "result" - ], + "required": ["body", "url", "result"], "additionalProperties": false, "description": "Endpoint model for updating entities" }, @@ -662,9 +624,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { @@ -677,17 +637,11 @@ "type": "string" } }, - "required": [ - "success", - "serviceId" - ], + "required": ["success", "serviceId"], "additionalProperties": false } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false }, "ApplyServiceFilesEndpoint": { @@ -700,9 +654,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "body": { @@ -730,19 +682,11 @@ } } }, - "required": [ - "success", - "serviceId", - "applied" - ], + "required": ["success", "serviceId", "applied"], "additionalProperties": false } }, - "required": [ - "url", - "body", - "result" - ], + "required": ["url", "body", "result"], "additionalProperties": false }, "ServiceLogsEndpoint": { @@ -755,9 +699,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "query": { @@ -785,17 +727,11 @@ } } }, - "required": [ - "entries" - ], + "required": ["entries"], "additionalProperties": false } }, - "required": [ - "url", - "query", - "result" - ], + "required": ["url", "query", "result"], "additionalProperties": false }, "ServiceLogEntry": { @@ -812,10 +748,7 @@ }, "stream": { "type": "string", - "enum": [ - "stdout", - "stderr" - ] + "enum": ["stdout", "stderr"] }, "line": { "type": "string" @@ -824,14 +757,7 @@ "type": "string" } }, - "required": [ - "id", - "serviceId", - "processUid", - "stream", - "line", - "createdAt" - ], + "required": ["id", "serviceId", "processUid", "stream", "line", "createdAt"], "additionalProperties": false }, "ClearServiceLogsEndpoint": { @@ -844,9 +770,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { @@ -856,16 +780,11 @@ "type": "boolean" } }, - "required": [ - "success" - ], + "required": ["success"], "additionalProperties": false } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false }, "ServiceHistoryEndpoint": { @@ -878,9 +797,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "query": { @@ -902,17 +819,11 @@ } } }, - "required": [ - "entries" - ], + "required": ["entries"], "additionalProperties": false } }, - "required": [ - "url", - "query", - "result" - ], + "required": ["url", "query", "result"], "additionalProperties": false }, "ServiceStateHistory": { @@ -958,14 +869,7 @@ "type": "string" } }, - "required": [ - "id", - "serviceId", - "event", - "triggeredBy", - "triggerSource", - "createdAt" - ], + "required": ["id", "serviceId", "event", "triggeredBy", "triggerSource", "createdAt"], "additionalProperties": false, "description": "Audit log entry for service state transitions. Records every lifecycle event (start, stop, crash, install, build, pull) with full context: who triggered it, how, and any relevant metadata. Entries are never deleted and not included in exports." }, @@ -999,13 +903,7 @@ }, "TriggerSource": { "type": "string", - "enum": [ - "api", - "mcp", - "auto-fetch", - "auto-restart", - "system" - ], + "enum": ["api", "mcp", "auto-fetch", "auto-restart", "system"], "description": "How a state change was triggered. Used to distinguish user actions from automated system behavior." }, "ServiceBranchesEndpoint": { @@ -1018,9 +916,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { @@ -1042,18 +938,11 @@ } } }, - "required": [ - "currentBranch", - "local", - "remote" - ], + "required": ["currentBranch", "local", "remote"], "additionalProperties": false } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false }, "ServiceCheckoutEndpoint": { @@ -1066,9 +955,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "body": { @@ -1078,9 +965,7 @@ "type": "string" } }, - "required": [ - "branch" - ], + "required": ["branch"], "additionalProperties": false }, "result": { @@ -1093,18 +978,11 @@ "type": "string" } }, - "required": [ - "success", - "serviceId" - ], + "required": ["success", "serviceId"], "additionalProperties": false } }, - "required": [ - "url", - "body", - "result" - ], + "required": ["url", "body", "result"], "additionalProperties": false }, "ServicesApi": { @@ -1197,9 +1075,7 @@ "$ref": "#/definitions/PatchServiceEndpoint" } }, - "required": [ - "/services/:id" - ], + "required": ["/services/:id"], "additionalProperties": false }, "PUT": { @@ -1228,9 +1104,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -1244,10 +1118,7 @@ "$ref": "#/definitions/ClearServiceLogsEndpoint" } }, - "required": [ - "/services/:id", - "/services/:id/logs" - ], + "required": ["/services/:id", "/services/:id/logs"], "additionalProperties": false }, "HEAD": { @@ -1276,9 +1147,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -1308,9 +1177,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -1340,9 +1207,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -1372,19 +1237,12 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET", - "POST", - "PATCH", - "DELETE" - ], + "required": ["GET", "POST", "PATCH", "DELETE"], "additionalProperties": false }, "GetCollectionEndpoint": { @@ -1403,10 +1261,7 @@ "$ref": "#/definitions/GetCollectionResult%3CServiceView%3E" } }, - "required": [ - "query", - "result" - ], + "required": ["query", "result"], "additionalProperties": false, "description": "Rest endpoint model for getting / querying collections" }, @@ -1426,206 +1281,119 @@ "properties": { "id": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "stackName": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "displayName": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "description": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "workingDirectory": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "repositoryId": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "prerequisiteIds": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "prerequisiteServiceIds": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "installCommand": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "buildCommand": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "runCommand": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "files": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "createdAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "updatedAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "serviceId": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "autoFetchEnabled": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "autoFetchIntervalMinutes": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "autoRestartOnFetch": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "environmentVariableOverrides": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "cloneStatus": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "installStatus": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "buildStatus": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "runStatus": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "currentBranch": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "lastClonedAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "lastInstalledAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "lastBuiltAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "lastStartedAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "lastFetchedAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] } }, "additionalProperties": false, @@ -3226,10 +2994,7 @@ "description": "List of the selected entities" } }, - "required": [ - "count", - "entries" - ], + "required": ["count", "entries"], "additionalProperties": false, "description": "Response Model for GetCollection" }, @@ -3288,20 +3053,14 @@ "description": "The entity's unique identifier" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "$ref": "#/definitions/ServiceView" } }, - "required": [ - "query", - "url", - "result" - ], + "required": ["query", "url", "result"], "additionalProperties": false, "description": "Endpoint model for getting a single entity" }, @@ -3316,21 +3075,16 @@ "description": "UUID primary key" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false, "description": "Endpoint model for deleting entities" } } -} \ No newline at end of file +} diff --git a/common/schemas/stacks-api.json b/common/schemas/stacks-api.json index b98f01d..9f6dc90 100644 --- a/common/schemas/stacks-api.json +++ b/common/schemas/stacks-api.json @@ -29,23 +29,14 @@ "description": "Optional description of what this stack does" } }, - "required": [ - "description", - "displayName", - "environmentVariables", - "mainDirectory", - "name" - ] + "required": ["description", "displayName", "environmentVariables", "mainDirectory", "name"] }, "EnvironmentVariableValue": { "type": "object", "properties": { "source": { "type": "string", - "enum": [ - "inherit", - "custom" - ], + "enum": ["inherit", "custom"], "description": "Whether to inherit the value from the host system or use a custom value" }, "customValue": { @@ -53,9 +44,7 @@ "description": "The custom value to use when source is 'custom'" } }, - "required": [ - "source" - ], + "required": ["source"], "additionalProperties": false, "description": "Describes how an environment variable's value is resolved. Used in {@link StackConfig } for stack-level defaults and in {@link ServiceConfig } for per-service overrides." }, @@ -69,10 +58,7 @@ "$ref": "#/definitions/WithOptionalId%3CStackWritableFields%2C%22name%22%3E" } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "StackView": { @@ -153,12 +139,7 @@ "description": "Stack-level environment variable values, keyed by variable name" } }, - "required": [ - "description", - "displayName", - "environmentVariables", - "mainDirectory" - ] + "required": ["description", "displayName", "environmentVariables", "mainDirectory"] }, "PatchStackEndpoint": { "$ref": "#/definitions/PatchEndpoint%3CStackWritableFields%2C%22name%22%3E" @@ -203,20 +184,14 @@ "description": "Unique kebab-case identifier for the stack" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "body", - "url", - "result" - ], + "required": ["body", "url", "result"], "additionalProperties": false, "description": "Endpoint model for updating entities" }, @@ -230,9 +205,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { @@ -254,11 +227,7 @@ "description": "Optional description of what this stack does" } }, - "required": [ - "name", - "displayName", - "description" - ], + "required": ["name", "displayName", "description"], "additionalProperties": false }, "services": { @@ -363,13 +332,7 @@ "description": "Optional description" } }, - "required": [ - "id", - "stackName", - "url", - "displayName", - "description" - ], + "required": ["id", "stackName", "url", "displayName", "description"], "additionalProperties": false } }, @@ -403,31 +366,16 @@ "description": "Help text shown when the prerequisite check fails" } }, - "required": [ - "id", - "stackName", - "name", - "type", - "config", - "installationHelp" - ], + "required": ["id", "stackName", "name", "type", "config", "installationHelp"], "additionalProperties": false } } }, - "required": [ - "stack", - "services", - "repositories", - "prerequisites" - ], + "required": ["stack", "services", "repositories", "prerequisites"], "additionalProperties": false } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false }, "ServiceFile": { @@ -442,10 +390,7 @@ "description": "File content (usually plain text)" } }, - "required": [ - "relativePath", - "content" - ], + "required": ["relativePath", "content"], "additionalProperties": false, "description": "A file to be placed relative to the service root (e.g. .env, appConfig.local.json)" }, @@ -473,9 +418,7 @@ "type": "string" } }, - "required": [ - "minimumVersion" - ], + "required": ["minimumVersion"], "additionalProperties": false }, { @@ -485,9 +428,7 @@ "type": "string" } }, - "required": [ - "minimumVersion" - ], + "required": ["minimumVersion"], "additionalProperties": false }, { @@ -497,9 +438,7 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false }, { @@ -509,9 +448,7 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false }, { @@ -524,9 +461,7 @@ "type": "string" } }, - "required": [ - "feedUrl" - ], + "required": ["feedUrl"], "additionalProperties": false }, { @@ -542,9 +477,7 @@ "type": "string" } }, - "required": [ - "variableName" - ], + "required": ["variableName"], "additionalProperties": false }, { @@ -554,9 +487,7 @@ "type": "string" } }, - "required": [ - "script" - ], + "required": ["script"], "additionalProperties": false } ], @@ -572,9 +503,7 @@ "type": "boolean" } }, - "required": [ - "success" - ], + "required": ["success"], "additionalProperties": false }, "body": { @@ -596,11 +525,7 @@ "description": "Optional description of what this stack does" } }, - "required": [ - "name", - "displayName", - "description" - ], + "required": ["name", "displayName", "description"], "additionalProperties": false }, "services": { @@ -705,13 +630,7 @@ "description": "Optional description" } }, - "required": [ - "id", - "stackName", - "url", - "displayName", - "description" - ], + "required": ["id", "stackName", "url", "displayName", "description"], "additionalProperties": false } }, @@ -745,14 +664,7 @@ "description": "Help text shown when the prerequisite check fails" } }, - "required": [ - "id", - "stackName", - "name", - "type", - "config", - "installationHelp" - ], + "required": ["id", "stackName", "name", "type", "config", "installationHelp"], "additionalProperties": false } }, @@ -796,26 +708,15 @@ } } }, - "required": [ - "mainDirectory" - ], + "required": ["mainDirectory"], "additionalProperties": false } }, - "required": [ - "stack", - "services", - "repositories", - "prerequisites", - "config" - ], + "required": ["stack", "services", "repositories", "prerequisites", "config"], "additionalProperties": false } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "StackSetupEndpoint": { @@ -828,9 +729,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { @@ -840,16 +739,11 @@ "type": "boolean" } }, - "required": [ - "success" - ], + "required": ["success"], "additionalProperties": false } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false }, "StacksApi": { @@ -868,11 +762,7 @@ "$ref": "#/definitions/ExportStackEndpoint" } }, - "required": [ - "/stacks", - "/stacks/:id", - "/stacks/:id/export" - ], + "required": ["/stacks", "/stacks/:id", "/stacks/:id/export"], "additionalProperties": false }, "POST": { @@ -888,11 +778,7 @@ "$ref": "#/definitions/StackSetupEndpoint" } }, - "required": [ - "/stacks", - "/stacks/import", - "/stacks/:id/setup" - ], + "required": ["/stacks", "/stacks/import", "/stacks/:id/setup"], "additionalProperties": false }, "PATCH": { @@ -902,9 +788,7 @@ "$ref": "#/definitions/PatchStackEndpoint" } }, - "required": [ - "/stacks/:id" - ], + "required": ["/stacks/:id"], "additionalProperties": false }, "PUT": { @@ -933,9 +817,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -946,9 +828,7 @@ "$ref": "#/definitions/DeleteEndpoint%3CStackDefinition%2C%22name%22%3E" } }, - "required": [ - "/stacks/:id" - ], + "required": ["/stacks/:id"], "additionalProperties": false }, "HEAD": { @@ -977,9 +857,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -1009,9 +887,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -1041,9 +917,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -1073,19 +947,12 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET", - "POST", - "PATCH", - "DELETE" - ], + "required": ["GET", "POST", "PATCH", "DELETE"], "additionalProperties": false }, "GetCollectionEndpoint": { @@ -1104,10 +971,7 @@ "$ref": "#/definitions/GetCollectionResult%3CStackView%3E" } }, - "required": [ - "query", - "result" - ], + "required": ["query", "result"], "additionalProperties": false, "description": "Rest endpoint model for getting / querying collections" }, @@ -1127,59 +991,35 @@ "properties": { "name": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "displayName": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "description": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "createdAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "updatedAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "stackName": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "mainDirectory": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "environmentVariables": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] } }, "additionalProperties": false, @@ -1709,10 +1549,7 @@ "description": "List of the selected entities" } }, - "required": [ - "count", - "entries" - ], + "required": ["count", "entries"], "additionalProperties": false, "description": "Response Model for GetCollection" }, @@ -1750,20 +1587,14 @@ "description": "The entity's unique identifier" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "$ref": "#/definitions/StackView" } }, - "required": [ - "query", - "url", - "result" - ], + "required": ["query", "url", "result"], "additionalProperties": false, "description": "Endpoint model for getting a single entity" }, @@ -1778,21 +1609,16 @@ "description": "Unique kebab-case identifier for the stack" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false, "description": "Endpoint model for deleting entities" } } -} \ No newline at end of file +} diff --git a/common/schemas/system-api.json b/common/schemas/system-api.json index af21841..af53375 100644 --- a/common/schemas/system-api.json +++ b/common/schemas/system-api.json @@ -20,16 +20,11 @@ } } }, - "required": [ - "variableNames" - ], + "required": ["variableNames"], "additionalProperties": false } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "SystemApi": { @@ -61,9 +56,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -74,9 +67,7 @@ "$ref": "#/definitions/CheckEnvAvailabilityEndpoint" } }, - "required": [ - "/system/check-env-availability" - ], + "required": ["/system/check-env-availability"], "additionalProperties": false }, "PATCH": { @@ -105,9 +96,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -137,9 +126,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -169,9 +156,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -201,9 +186,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -233,9 +216,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -265,9 +246,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -297,17 +276,13 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "POST" - ], + "required": ["POST"], "additionalProperties": false } } -} \ No newline at end of file +} diff --git a/common/schemas/tokens-api.json b/common/schemas/tokens-api.json index 2def4b2..5f768cf 100644 --- a/common/schemas/tokens-api.json +++ b/common/schemas/tokens-api.json @@ -14,10 +14,7 @@ "type": "string" } }, - "required": [ - "token", - "plainTextToken" - ], + "required": ["token", "plainTextToken"], "additionalProperties": false }, "body": { @@ -27,16 +24,11 @@ "type": "string" } }, - "required": [ - "name" - ], + "required": ["name"], "additionalProperties": false } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "PublicApiToken": { @@ -58,12 +50,7 @@ "type": "string" } }, - "required": [ - "id", - "username", - "name", - "createdAt" - ], + "required": ["id", "username", "name", "createdAt"], "additionalProperties": false }, "TokensApi": { @@ -76,9 +63,7 @@ "$ref": "#/definitions/GetCollectionEndpoint%3CPublicApiToken%3E" } }, - "required": [ - "/tokens" - ], + "required": ["/tokens"], "additionalProperties": false }, "POST": { @@ -88,9 +73,7 @@ "$ref": "#/definitions/CreateTokenEndpoint" } }, - "required": [ - "/tokens" - ], + "required": ["/tokens"], "additionalProperties": false }, "PATCH": { @@ -119,9 +102,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -151,9 +132,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -164,9 +143,7 @@ "$ref": "#/definitions/DeleteEndpoint%3CApiToken%2C%22id%22%3E" } }, - "required": [ - "/tokens/:id" - ], + "required": ["/tokens/:id"], "additionalProperties": false }, "HEAD": { @@ -195,9 +172,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -227,9 +202,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -259,9 +232,7 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -291,18 +262,12 @@ "type": "string" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET", - "POST", - "DELETE" - ], + "required": ["GET", "POST", "DELETE"], "additionalProperties": false }, "GetCollectionEndpoint": { @@ -321,10 +286,7 @@ "$ref": "#/definitions/GetCollectionResult%3CPublicApiToken%3E" } }, - "required": [ - "query", - "result" - ], + "required": ["query", "result"], "additionalProperties": false, "description": "Rest endpoint model for getting / querying collections" }, @@ -344,38 +306,23 @@ "properties": { "id": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "username": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "name": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "lastUsedAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] }, "createdAt": { "type": "string", - "enum": [ - "ASC", - "DESC" - ] + "enum": ["ASC", "DESC"] } }, "additionalProperties": false, @@ -385,13 +332,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "id", - "username", - "name", - "lastUsedAt", - "createdAt" - ] + "enum": ["id", "username", "name", "lastUsedAt", "createdAt"] }, "description": "The result set will be limited to these fields" }, @@ -704,10 +645,7 @@ "description": "List of the selected entities" } }, - "required": [ - "count", - "entries" - ], + "required": ["count", "entries"], "additionalProperties": false, "description": "Response Model for GetCollection" }, @@ -721,21 +659,16 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "result": { "type": "object" } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false, "description": "Endpoint model for deleting entities" } } -} \ No newline at end of file +} diff --git a/frontend/src/components/branch-selector.tsx b/frontend/src/components/branch-selector.tsx index acb9d52..1b75bdd 100644 --- a/frontend/src/components/branch-selector.tsx +++ b/frontend/src/components/branch-selector.tsx @@ -1,5 +1,5 @@ import { createComponent, Shade } from '@furystack/shades' -import { Button, Chip, Icon, icons, NotyService, ThemeProviderService } from '@furystack/shades-common-components' +import { Button, Chip, cssVariableTheme, Icon, icons, NotyService } from '@furystack/shades-common-components' import { ServicesApiClient } from '../services/api-clients/services-api-client.js' @@ -22,7 +22,6 @@ export const BranchSelector = Shade({ const api = injector.getInstance(ServicesApiClient) const noty = injector.getInstance(NotyService) - const { theme } = injector.getInstance(ThemeProviderService) if (!isCloned) { return ( @@ -92,9 +91,7 @@ export const BranchSelector = Shade({ const allBranches = branches ? [ ...branches.local.map((b) => ({ name: b, type: 'local' as const })), - ...branches.remote - .filter((b) => !b.endsWith('/HEAD')) - .map((b) => ({ name: b, type: 'remote' as const })), + ...branches.remote.filter((b) => !b.endsWith('/HEAD')).map((b) => ({ name: b, type: 'remote' as const })), ] : [] @@ -114,7 +111,9 @@ export const BranchSelector = Shade({ startIcon={} > {branchLabel} - {isOpen ? '▲' : '▼'} + + {isOpen ? '▲' : '▼'} + {isOpen ? ( @@ -125,16 +124,17 @@ export const BranchSelector = Shade({ left: '0', marginTop: '4px', minWidth: '280px', + maxWidth: 'calc(100vw - 32px)', maxHeight: '320px', overflow: 'auto', - backgroundColor: theme.background.paper, - border: `1px solid ${theme.divider}`, - borderRadius: '8px', - boxShadow: '0 8px 24px rgba(0,0,0,0.3)', + backgroundColor: cssVariableTheme.background.paper, + border: `1px solid ${cssVariableTheme.divider}`, + borderRadius: cssVariableTheme.shape.borderRadius.md, + boxShadow: cssVariableTheme.shadows.md, zIndex: '100', }} > -
+
({ style={{ width: '100%', padding: '6px 10px', - border: `1px solid ${theme.divider}`, - borderRadius: '4px', - backgroundColor: theme.background.default, - color: theme.text.primary, - fontSize: '13px', + border: `1px solid ${cssVariableTheme.divider}`, + borderRadius: cssVariableTheme.shape.borderRadius.sm, + backgroundColor: cssVariableTheme.background.default, + color: cssVariableTheme.text.primary, + fontSize: cssVariableTheme.typography.fontSize.sm, outline: 'none', boxSizing: 'border-box', fontFamily: 'inherit', @@ -156,9 +156,11 @@ export const BranchSelector = Shade({
{isLoading ? ( -
Loading...
+
+ Loading... +
) : filteredBranches.length === 0 ? ( -
+
{filter ? 'No matching branches' : 'No branches found'}
) : ( @@ -177,10 +179,10 @@ export const BranchSelector = Shade({ width: '100%', padding: '8px 12px', border: 'none', - background: isCurrent ? `${theme.palette.primary.main}22` : 'transparent', - color: isCurrent ? theme.palette.primary.main : theme.text.primary, + background: isCurrent ? cssVariableTheme.button.hover : 'transparent', + color: isCurrent ? cssVariableTheme.palette.primary.main : cssVariableTheme.text.primary, cursor: isCurrent ? 'default' : 'pointer', - fontSize: '13px', + fontSize: cssVariableTheme.typography.fontSize.sm, fontFamily: 'monospace', textAlign: 'left', }} @@ -189,9 +191,17 @@ export const BranchSelector = Shade({ {branch.name} {branch.type === 'remote' ? ( - remote + + remote + ) : null} - {isCurrent ? : null} + {isCurrent ? : null} ) })} diff --git a/frontend/src/components/entity-forms/service-form.tsx b/frontend/src/components/entity-forms/service-form.tsx index 6459900..774499f 100644 --- a/frontend/src/components/entity-forms/service-form.tsx +++ b/frontend/src/components/entity-forms/service-form.tsx @@ -185,7 +185,7 @@ export const ServiceForm = Shade({

Shared Files

-

+

Files placed relative to the service root (e.g. .env, appConfig.local.json).

{sharedFiles.length > 0 ? ( @@ -193,7 +193,7 @@ export const ServiceForm = Shade({ {sharedFiles.map((file, index) => (
({ flex: '1', padding: '6px 10px', borderRadius: '4px', - border: '1px solid rgba(255,255,255,0.2)', + border: `1px solid ${cssVariableTheme.divider}`, background: 'transparent', color: 'inherit', fontFamily: 'monospace', - fontSize: '13px', + fontSize: cssVariableTheme.typography.fontSize.sm, }} />
) : ( -

+

No prerequisites defined for this stack yet.

)} @@ -326,7 +326,7 @@ export const ServiceForm = Shade({ {props.otherServices && props.otherServices.length > 0 ? (

Prerequisite Services

-

+

Services that must be running before this one starts.

@@ -343,8 +343,8 @@ export const ServiceForm = Shade({ cursor: 'pointer', border: isSelected ? `2px solid ${cssVariableTheme.palette.primary.main}` - : '2px solid rgba(255,255,255,0.1)', - background: isSelected ? 'rgba(255,255,255,0.03)' : 'transparent', + : `2px solid ${cssVariableTheme.divider}`, + background: isSelected ? cssVariableTheme.button.hover : 'transparent', transition: 'all 0.15s', }} > @@ -354,7 +354,9 @@ export const ServiceForm = Shade({ onchange={() => togglePrereqServiceId(svc.id)} style={{ margin: '0' }} /> - {svc.displayName} + + {svc.displayName} + ) })} @@ -426,9 +428,9 @@ export const ServiceForm = Shade({ elevation={3} style={{ padding: '24px', - minWidth: '480px', + width: 'min(480px, calc(100vw - 32px))', maxWidth: '600px', - borderRadius: '12px', + borderRadius: cssVariableTheme.shape.borderRadius.lg, background: cssVariableTheme.background.paper, }} > @@ -467,9 +469,9 @@ export const ServiceForm = Shade({ elevation={3} style={{ padding: '24px', - minWidth: '480px', + width: 'min(480px, calc(100vw - 32px))', maxWidth: '600px', - borderRadius: '12px', + borderRadius: cssVariableTheme.shape.borderRadius.lg, background: cssVariableTheme.background.paper, }} > diff --git a/frontend/src/components/environment-variables-manager.tsx b/frontend/src/components/environment-variables-manager.tsx index 396365b..64817c0 100644 --- a/frontend/src/components/environment-variables-manager.tsx +++ b/frontend/src/components/environment-variables-manager.tsx @@ -1,7 +1,7 @@ import { useCollectionSync } from '@furystack/entity-sync-client' import { createComponent, Shade } from '@furystack/shades' -import { Button, cssVariableTheme, Icon, icons, Paper } from '@furystack/shades-common-components' +import { Button, cssVariableTheme, Icon, icons, Input, Paper, Select } from '@furystack/shades-common-components' import type { EnvironmentVariableValue, Prerequisite } from 'common' import { Prerequisite as PrerequisiteModel } from 'common' @@ -71,7 +71,7 @@ export const EnvironmentVariablesManager = Shade - Environment Variables + Environment Variables {envPrereqs.map((prereq) => { const varName = (prereq.config as { variableName: string }).variableName const current = editState[varName] @@ -92,11 +92,13 @@ export const EnvironmentVariablesManager = Shade
{varName} - ({prereq.name}) + + ({prereq.name}) + {isGloballyAvailable ? (
- + /> {source === 'custom' ? ( - { setEditState({ ...editState, diff --git a/frontend/src/components/log-line.tsx b/frontend/src/components/log-line.tsx index 069b9d3..67ef056 100644 --- a/frontend/src/components/log-line.tsx +++ b/frontend/src/components/log-line.tsx @@ -1,4 +1,5 @@ import { createComponent, Shade } from '@furystack/shades' +import { cssVariableTheme } from '@furystack/shades-common-components' import type { AnsiStyle } from '../utils/parse-ansi.js' import { parseAnsi } from '../utils/parse-ansi.js' @@ -26,7 +27,7 @@ const renderTextWithLinks = (text: string, style: AnsiStyle) => { rel="noopener noreferrer" style={{ ...style, - color: style.color ?? '#58a6ff', + color: style.color ?? cssVariableTheme.palette.primary.main, textDecoration: 'underline', cursor: 'pointer', }} diff --git a/frontend/src/components/log-viewer.tsx b/frontend/src/components/log-viewer.tsx index d76c8f5..10883c4 100644 --- a/frontend/src/components/log-viewer.tsx +++ b/frontend/src/components/log-viewer.tsx @@ -1,6 +1,6 @@ import { useCollectionSync } from '@furystack/entity-sync-client' import { createComponent, Shade } from '@furystack/shades' -import { Input, Loader } from '@furystack/shades-common-components' +import { cssVariableTheme, Input, Loader } from '@furystack/shades-common-components' import { ServiceLogEntry } from 'common' import { LogLine } from './log-line.js' @@ -44,7 +44,7 @@ export const LogViewer = Shade({ return (
-
+
({ overflow: 'auto', padding: '8px 16px', fontFamily: "'Cascadia Code', 'Fira Code', 'Consolas', monospace", - fontSize: '13px', + fontSize: cssVariableTheme.typography.fontSize.sm, lineHeight: '1.5', - background: '#0d1117', - color: '#c9d1d9', + background: cssVariableTheme.background.default, + color: cssVariableTheme.text.primary, whiteSpace: 'pre-wrap', wordBreak: 'break-all', }} @@ -72,7 +72,11 @@ export const LogViewer = Shade({
No log output yet.
) : null} {filteredEntries.map((entry) => ( -
+
))} diff --git a/frontend/src/components/service-env-overrides.tsx b/frontend/src/components/service-env-overrides.tsx index d623e12..7357b58 100644 --- a/frontend/src/components/service-env-overrides.tsx +++ b/frontend/src/components/service-env-overrides.tsx @@ -1,6 +1,6 @@ import { createComponent, Shade } from '@furystack/shades' -import { Button, cssVariableTheme, Icon, icons, Paper } from '@furystack/shades-common-components' +import { Button, cssVariableTheme, Icon, icons, Input, Paper, Select } from '@furystack/shades-common-components' import type { EnvironmentVariableValue, Prerequisite, ServiceView } from 'common' import { ServicesApiClient } from '../services/api-clients/services-api-client.js' @@ -60,7 +60,7 @@ export const ServiceEnvOverrides = Shade({ return (

Environment Variable Overrides

-

+

Override stack-level environment variable values for this service. Leave unset to use the stack default.

{envPrereqs.map((prereq) => { @@ -87,11 +87,13 @@ export const ServiceEnvOverrides = Shade({ >
{varName} - ({prereq.name}) + + ({prereq.name}) + {stackValue ? ( ({ {hasOverride ? ( ({ ) : null}
- + /> {hasOverride && override.source === 'custom' ? ( - { setEditState({ ...editState, diff --git a/frontend/src/components/service-pipeline-stepper.tsx b/frontend/src/components/service-pipeline-stepper.tsx index 249f95b..139df7d 100644 --- a/frontend/src/components/service-pipeline-stepper.tsx +++ b/frontend/src/components/service-pipeline-stepper.tsx @@ -1,5 +1,5 @@ import { createComponent, Shade } from '@furystack/shades' -import { Button, Timeline, TimelineItem } from '@furystack/shades-common-components' +import { Button, cssVariableTheme, Timeline, TimelineItem } from '@furystack/shades-common-components' import type { ServiceView } from 'common' import type { PipelineStageStatus } from '../utils/service-pipeline.js' @@ -46,7 +46,9 @@ export const ServicePipelineStepper = Shade({ {visibleStages.map((stage) => { const color = getStageColor(stage.status) const actionLabel = stageActionLabels[stage.id]?.[stage.status] ?? null - const dot = {statusDots[stage.status]} + const dot = ( + {statusDots[stage.status]} + ) const getActionTarget = () => { if (stage.id === 'run' && stage.status === 'done') return '/services/:id/stop' @@ -60,7 +62,7 @@ export const ServicePipelineStepper = Shade({ ({ {entry.displayName} {entry.description ? ( -
{entry.description}
+
+ {entry.description} +
) : null} {summary ? (
@@ -130,7 +133,13 @@ export const ServiceTable = Shade({ }, pipeline: (entry) => , branch: (entry) => ( - + {entry.currentBranch ?? '—'} ), diff --git a/frontend/src/pages/dashboard/index.tsx b/frontend/src/pages/dashboard/index.tsx index 25f05ba..4e3fcb1 100644 --- a/frontend/src/pages/dashboard/index.tsx +++ b/frontend/src/pages/dashboard/index.tsx @@ -4,6 +4,7 @@ import { createComponent, Shade } from '@furystack/shades' import { Button, Chip, + cssVariableTheme, Icon, icons, Loader, @@ -162,7 +163,7 @@ export const Dashboard = Shade({
-

Services

+

Services

{services.length} @@ -187,10 +188,10 @@ export const Dashboard = Shade({ display: 'flex', alignItems: 'center', gap: '6px', - fontSize: '13px', + fontSize: cssVariableTheme.typography.fontSize.sm, padding: '2px 8px', borderRadius: '4px', - border: '1px solid rgba(255,255,255,0.1)', + border: `1px solid ${cssVariableTheme.divider}`, }} > @@ -198,13 +199,17 @@ export const Dashboard = Shade({
))} {services.length > 5 ? ( - + +{services.length - 5} more ) : null}
) : ( -

No services yet.

+

+ No services yet. +

)} @@ -218,7 +223,7 @@ export const Dashboard = Shade({
-

Repositories

+

Repositories

{repos.length} @@ -233,13 +238,17 @@ export const Dashboard = Shade({ ))} {repos.length > 5 ? ( - + +{repos.length - 5} more ) : null}
) : ( -

No repositories yet.

+

+ No repositories yet. +

)} @@ -253,7 +262,7 @@ export const Dashboard = Shade({
-

Prerequisites

+

Prerequisites

{prereqs.length} @@ -268,13 +277,17 @@ export const Dashboard = Shade({ ))} {prereqs.length > 5 ? ( - + +{prereqs.length - 5} more ) : null}
) : ( -

No prerequisites yet.

+

+ No prerequisites yet. +

)} diff --git a/frontend/src/pages/installer/check-prerequisites-step.tsx b/frontend/src/pages/installer/check-prerequisites-step.tsx index fe8abb5..c882694 100644 --- a/frontend/src/pages/installer/check-prerequisites-step.tsx +++ b/frontend/src/pages/installer/check-prerequisites-step.tsx @@ -1,5 +1,6 @@ import { createComponent, Shade } from '@furystack/shades' import type { WizardStepProps } from '@furystack/shades-common-components' +import { cssVariableTheme } from '@furystack/shades-common-components' import { WizardStep } from '../../components/wizard-step.js' export const CheckPrerequisitesStep = Shade({ @@ -21,13 +22,15 @@ export const CheckPrerequisitesStep = Shade({ padding: '12px 16px', marginBottom: '8px', borderRadius: '8px', - border: '1px solid rgba(255, 255, 255, 0.1)', - background: 'rgba(255, 255, 255, 0.03)', + border: `1px solid ${cssVariableTheme.divider}`, + background: cssVariableTheme.button.hover, }} > {prereq.name}
- {prereq.description} + + {prereq.description} + ))} diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index c7a2061..8344847 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -92,7 +92,7 @@ export const Login = Shade({ borderRadius: cssVariableTheme.shape.borderRadius.sm, backgroundColor: 'rgba(211, 47, 47, 0.1)', border: '1px solid rgba(211, 47, 47, 0.3)', - color: '#f44336', + color: cssVariableTheme.palette.error.main, fontSize: cssVariableTheme.typography.fontSize.sm, display: 'flex', alignItems: 'center', diff --git a/frontend/src/pages/offline.tsx b/frontend/src/pages/offline.tsx index e5a7780..1b65df2 100644 --- a/frontend/src/pages/offline.tsx +++ b/frontend/src/pages/offline.tsx @@ -1,4 +1,5 @@ import { createComponent, Shade } from '@furystack/shades' +import { cssVariableTheme } from '@furystack/shades-common-components' import { StackCraftNestedRouteLink } from '../components/app-routes.js' import { environmentOptions } from '../environment-options.js' @@ -17,10 +18,10 @@ export const Offline = Shade({ perspective: '400px', }, '& a': { - color: '#6cf', + color: cssVariableTheme.palette.primary.main, }, '& a:hover': { - color: '#9df', + color: cssVariableTheme.palette.primary.light, }, }, render: () => { diff --git a/frontend/src/pages/services/service-detail.tsx b/frontend/src/pages/services/service-detail.tsx index a7d1883..d0fc555 100644 --- a/frontend/src/pages/services/service-detail.tsx +++ b/frontend/src/pages/services/service-detail.tsx @@ -7,6 +7,7 @@ import { Chip, CollectionService, ConfirmDialog, + cssVariableTheme, DataGrid, Icon, icons, @@ -16,7 +17,6 @@ import { PageContainer, PageHeader, Paper, - ThemeProviderService, } from '@furystack/shades-common-components' import type { PrerequisiteCheckStatus, ServiceView, StackView } from 'common' import { @@ -209,7 +209,6 @@ export const ServiceDetail = Shade({ const prereqsApi = injector.getInstance(PrerequisitesApiClient) const reposApi = injector.getInstance(GitHubReposApiClient) const noty = injector.getInstance(NotyService) - const { theme } = injector.getInstance(ThemeProviderService) const [actionInProgress, setActionInProgress] = useState('actionInProgress', null) const runAction = async (action: string, apiAction: string) => { @@ -405,36 +404,7 @@ export const ServiceDetail = Shade({ /> {/* Tab bar */} -
- {tabs.map((tab) => ( - - ))} -
+ {/* Tab content */} {activeTab === 'overview' ? ( @@ -456,13 +426,9 @@ export const ServiceDetail = Shade({ /> ) : null} - {activeTab === 'logs' ? ( - - ) : null} + {activeTab === 'logs' ? : null} - {activeTab === 'history' ? ( - - ) : null} + {activeTab === 'history' ? : null} {activeTab === 'configuration' ? ( ({ }, }) +/* ============================================ + * Tab Bar + * ============================================ */ + +type ServiceTabBarProps = { + tabs: Array<{ id: TabId; label: string }> + activeTab: TabId + onTabChange: (tab: TabId) => void +} + +const ServiceTabBar = Shade({ + customElementName: 'shade-service-tab-bar', + css: { + display: 'flex', + gap: '0', + borderBottom: `1px solid ${cssVariableTheme.divider}`, + marginBottom: cssVariableTheme.spacing.md, + + '& button': { + padding: `${cssVariableTheme.spacing.sm} ${cssVariableTheme.spacing.lg}`, + cursor: 'pointer', + border: 'none', + borderBottom: '2px solid transparent', + background: 'transparent', + color: cssVariableTheme.text.secondary, + fontWeight: cssVariableTheme.typography.fontWeight.normal, + fontSize: cssVariableTheme.typography.fontSize.md, + transition: `all ${cssVariableTheme.transitions.duration.normal} ${cssVariableTheme.transitions.easing.easeInOut}`, + fontFamily: 'inherit', + }, + '& button:hover': { + color: cssVariableTheme.text.primary, + }, + '& button[data-active]': { + borderBottomColor: cssVariableTheme.palette.primary.main, + color: cssVariableTheme.palette.primary.main, + fontWeight: cssVariableTheme.typography.fontWeight.semibold, + }, + }, + render: ({ props }) => { + return ( +
+ {props.tabs.map((tab) => ( + + ))} +
+ ) + }, +}) + /* ============================================ * Overview Tab * ============================================ */ @@ -551,11 +573,7 @@ const OverviewTab = Shade({ {/* Pipeline stepper */}

Pipeline

- +
{/* Service info */} @@ -564,9 +582,9 @@ const OverviewTab = Shade({
@@ -611,7 +629,9 @@ const OverviewTab = Shade({
) : null} Working Directory - {fullCwd ?? '(loading…)'} + + {fullCwd ?? '(loading…)'} + {fullCwd ? ( @@ -712,17 +732,13 @@ const LogsTab = Shade({ href="/stacks/:stackName/services/:serviceId/logs" params={{ stackName: props.stackName, serviceId: props.serviceId }} > -
- +
@@ -822,7 +838,9 @@ const ServiceHistory = Shade({ triggeredBy: (entry) => {entry.triggeredBy}, triggerSource: (entry) => {entry.triggerSource}, metadata: (entry) => ( - + {entry.metadata ?? ''} ), @@ -874,15 +892,7 @@ type ConfigurationTabProps = { const ConfigurationTab = Shade({ customElementName: 'shade-service-config-tab', render: ({ props }) => { - const { - service, - repos, - allPrereqs, - otherServices, - servicePrereqs, - stackConfig, - actionInProgress, - } = props + const { service, repos, allPrereqs, otherServices, servicePrereqs, stackConfig, actionInProgress } = props return (
@@ -931,9 +941,9 @@ const ConfigurationTab = Shade({ gap: '12px', padding: '8px 12px', borderRadius: '8px', - border: '1px solid rgba(255,255,255,0.1)', + border: `1px solid ${cssVariableTheme.divider}`, fontFamily: 'monospace', - fontSize: '13px', + fontSize: cssVariableTheme.typography.fontSize.sm, }} > {file.relativePath} diff --git a/frontend/src/pages/wizards/create-service-wizard.tsx b/frontend/src/pages/wizards/create-service-wizard.tsx index 8128bb0..095a870 100644 --- a/frontend/src/pages/wizards/create-service-wizard.tsx +++ b/frontend/src/pages/wizards/create-service-wizard.tsx @@ -156,7 +156,7 @@ export const CreateServiceWizard = Shade({ style={{ width: '32px', height: '2px', - background: state.step > 0 ? cssVariableTheme.palette.primary.main : 'rgba(255,255,255,0.15)', + background: state.step > 0 ? cssVariableTheme.palette.primary.main : cssVariableTheme.divider, }} /> @@ -328,7 +328,7 @@ const StepDot = Shade({ ? cssVariableTheme.palette.primary.main : props.completed ? cssVariableTheme.palette.success.main - : 'rgba(255,255,255,0.15)' + : cssVariableTheme.divider return (
diff --git a/frontend/src/utils/service-pipeline.spec.ts b/frontend/src/utils/service-pipeline.spec.ts new file mode 100644 index 0000000..ada3a58 --- /dev/null +++ b/frontend/src/utils/service-pipeline.spec.ts @@ -0,0 +1,231 @@ +import { describe, it, expect } from 'vitest' +import type { ServiceView } from 'common' + +import { + getPipelineStages, + getPrimaryAction, + getSecondaryActions, + getStageColor, + needsSetup, +} from './service-pipeline.js' + +const baseService: ServiceView = { + id: 'svc-1', + serviceId: 'svc-1', + stackName: 'stack-1', + displayName: 'Test Service', + description: '', + runCommand: 'npm start', + prerequisiteIds: [], + prerequisiteServiceIds: [], + files: [], + autoFetchEnabled: false, + autoFetchIntervalMinutes: 60, + autoRestartOnFetch: false, + environmentVariableOverrides: {}, + cloneStatus: 'not-cloned', + installStatus: 'not-installed', + buildStatus: 'not-built', + runStatus: 'stopped', + createdAt: '', + updatedAt: '', +} + +describe('getStageColor', () => { + it('should return correct palette key for each status', () => { + expect(getStageColor('skipped')).toBe('secondary') + expect(getStageColor('pending')).toBe('secondary') + expect(getStageColor('in-progress')).toBe('warning') + expect(getStageColor('done')).toBe('success') + expect(getStageColor('failed')).toBe('error') + }) +}) + +describe('getPipelineStages', () => { + it('should mark clone as skipped when no repositoryId', () => { + const stages = getPipelineStages({ ...baseService, repositoryId: undefined }) + expect(stages[0]).toMatchObject({ id: 'clone', status: 'skipped' }) + }) + + it('should mark clone as pending when repositoryId present but not cloned', () => { + const stages = getPipelineStages({ ...baseService, repositoryId: 'repo-1' }) + expect(stages[0]).toMatchObject({ id: 'clone', status: 'pending' }) + }) + + it('should mark clone as done when cloned', () => { + const stages = getPipelineStages({ ...baseService, repositoryId: 'repo-1', cloneStatus: 'cloned' }) + expect(stages[0]).toMatchObject({ id: 'clone', status: 'done' }) + }) + + it('should mark clone as in-progress when cloning', () => { + const stages = getPipelineStages({ ...baseService, repositoryId: 'repo-1', cloneStatus: 'cloning' }) + expect(stages[0]).toMatchObject({ id: 'clone', status: 'in-progress' }) + }) + + it('should mark clone as failed when clone failed', () => { + const stages = getPipelineStages({ ...baseService, repositoryId: 'repo-1', cloneStatus: 'failed' }) + expect(stages[0]).toMatchObject({ id: 'clone', status: 'failed' }) + }) + + it('should mark install as skipped when no installCommand', () => { + const stages = getPipelineStages({ ...baseService, installCommand: undefined }) + expect(stages[1]).toMatchObject({ id: 'install', status: 'skipped' }) + }) + + it('should mark install as pending when command present but not installed', () => { + const stages = getPipelineStages({ ...baseService, installCommand: 'npm install' }) + expect(stages[1]).toMatchObject({ id: 'install', status: 'pending', command: 'npm install' }) + }) + + it('should mark build as skipped when no buildCommand', () => { + const stages = getPipelineStages({ ...baseService, buildCommand: undefined }) + expect(stages[2]).toMatchObject({ id: 'build', status: 'skipped' }) + }) + + it('should map run status correctly', () => { + expect(getPipelineStages({ ...baseService, runStatus: 'running' })[3]).toMatchObject({ + id: 'run', + status: 'done', + }) + expect(getPipelineStages({ ...baseService, runStatus: 'starting' })[3]).toMatchObject({ + id: 'run', + status: 'in-progress', + }) + expect(getPipelineStages({ ...baseService, runStatus: 'error' })[3]).toMatchObject({ + id: 'run', + status: 'failed', + }) + expect(getPipelineStages({ ...baseService, runStatus: 'stopped' })[3]).toMatchObject({ + id: 'run', + status: 'pending', + }) + }) + + it('should always return 4 stages', () => { + const stages = getPipelineStages(baseService) + expect(stages).toHaveLength(4) + expect(stages.map((s) => s.id)).toEqual(['clone', 'install', 'build', 'run']) + }) +}) + +describe('getPrimaryAction', () => { + it('should return Stop when service is running', () => { + const action = getPrimaryAction({ ...baseService, runStatus: 'running' }) + expect(action).toMatchObject({ label: 'Stop', color: 'error', apiAction: '/services/:id/stop' }) + }) + + it('should return Stop with warning color when starting/stopping', () => { + expect(getPrimaryAction({ ...baseService, runStatus: 'starting' })).toMatchObject({ + label: 'Stop', + color: 'warning', + }) + expect(getPrimaryAction({ ...baseService, runStatus: 'stopping' })).toMatchObject({ + label: 'Stop', + color: 'warning', + }) + }) + + it('should return retry action when a stage has failed', () => { + const action = getPrimaryAction({ + ...baseService, + repositoryId: 'repo-1', + cloneStatus: 'failed', + }) + expect(action).toMatchObject({ label: 'Retry Clone', color: 'error', apiAction: '/services/:id/pull' }) + }) + + it('should return in-progress label when a stage is in progress', () => { + const action = getPrimaryAction({ + ...baseService, + repositoryId: 'repo-1', + cloneStatus: 'cloning', + }) + expect(action.label).toContain('ing...') + expect(action.apiAction).toBe('') + }) + + it('should return Clone when repo is pending', () => { + const action = getPrimaryAction({ ...baseService, repositoryId: 'repo-1' }) + expect(action).toMatchObject({ label: 'Clone', apiAction: '/services/:id/pull' }) + }) + + it('should return Start when all done and stopped', () => { + const action = getPrimaryAction({ + ...baseService, + repositoryId: 'repo-1', + cloneStatus: 'cloned', + installCommand: 'npm i', + installStatus: 'installed', + buildCommand: 'npm run build', + buildStatus: 'built', + runStatus: 'stopped', + }) + expect(action).toMatchObject({ label: 'Start', color: 'success', apiAction: '/services/:id/start' }) + }) +}) + +describe('getSecondaryActions', () => { + it('should return Restart and Update when running', () => { + const actions = getSecondaryActions({ ...baseService, runStatus: 'running' }) + expect(actions.some((a) => a.label === 'Restart')).toBe(true) + expect(actions.some((a) => a.label === 'Update')).toBe(true) + }) + + it('should return Set Up All when there are pending stages', () => { + const actions = getSecondaryActions({ ...baseService, repositoryId: 'repo-1' }) + expect(actions.some((a) => a.label === 'Set Up All')).toBe(true) + }) + + it('should return Update when stopped and cloned', () => { + const actions = getSecondaryActions({ + ...baseService, + repositoryId: 'repo-1', + cloneStatus: 'cloned', + runStatus: 'stopped', + }) + expect(actions.some((a) => a.label === 'Update')).toBe(true) + }) + + it('should return empty when nothing applicable', () => { + const actions = getSecondaryActions({ + ...baseService, + cloneStatus: 'cloned', + installCommand: 'npm i', + installStatus: 'installed', + runStatus: 'stopped', + }) + expect(actions).toHaveLength(0) + }) +}) + +describe('needsSetup', () => { + it('should return true when repo not cloned', () => { + expect(needsSetup({ ...baseService, repositoryId: 'repo-1', cloneStatus: 'not-cloned' })).toBe(true) + }) + + it('should return true when install command present but not installed', () => { + expect(needsSetup({ ...baseService, installCommand: 'npm i', installStatus: 'not-installed' })).toBe(true) + }) + + it('should return true when build command present but not built', () => { + expect(needsSetup({ ...baseService, buildCommand: 'npm run build', buildStatus: 'not-built' })).toBe(true) + }) + + it('should return false when everything is set up', () => { + expect( + needsSetup({ + ...baseService, + repositoryId: 'repo-1', + cloneStatus: 'cloned', + installCommand: 'npm i', + installStatus: 'installed', + buildCommand: 'npm run build', + buildStatus: 'built', + }), + ).toBe(false) + }) + + it('should return false when no setup is needed', () => { + expect(needsSetup(baseService)).toBe(false) + }) +}) diff --git a/frontend/src/utils/service-pipeline.ts b/frontend/src/utils/service-pipeline.ts index 567f51c..fa57da5 100644 --- a/frontend/src/utils/service-pipeline.ts +++ b/frontend/src/utils/service-pipeline.ts @@ -134,9 +134,7 @@ export const getSecondaryActions = (service: ServiceView): ServiceAction[] => { } const stages = getPipelineStages(service) - const hasPendingStages = stages.some( - (s) => s.status === 'pending' && s.id !== 'run', - ) + const hasPendingStages = stages.some((s) => s.status === 'pending' && s.id !== 'run') if (hasPendingStages) { actions.push({ label: 'Set Up All', apiAction: '/services/:id/setup', color: 'primary', icon: 'settings' }) } diff --git a/service/src/app-models/services/actions/service-branches-action.spec.ts b/service/src/app-models/services/actions/service-branches-action.spec.ts new file mode 100644 index 0000000..cbe9c29 --- /dev/null +++ b/service/src/app-models/services/actions/service-branches-action.spec.ts @@ -0,0 +1,124 @@ +import { addStore, InMemoryStore } from '@furystack/core' +import { Injector } from '@furystack/inject' +import { useLogging, VerboseConsoleLogger } from '@furystack/logging' +import { getRepository } from '@furystack/repository' +import { RequestError } from '@furystack/rest' +import { GitHubRepository, ServiceDefinition, ServiceStatus, StackConfig } from 'common' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import { GitService } from '../../../services/git-service.js' +import { ServiceBranchesAction } from './service-branches-action.js' + +const createMockActionContext = (options: { injector: Injector; urlParams?: Record }) => ({ + injector: options.injector, + getBody: () => Promise.resolve({} as never), + getUrlParams: () => (options.urlParams ?? {}) as { id: string }, + getQuery: () => ({}) as never, + request: {} as never, + response: {} as never, +}) + +describe('ServiceBranchesAction', () => { + let injector: Injector + let mockGit: { getCurrentBranch: ReturnType; getBranches: ReturnType } + + beforeEach(async () => { + injector = new Injector() + useLogging(injector, VerboseConsoleLogger) + + addStore(injector, new InMemoryStore({ model: ServiceDefinition, primaryKey: 'id' })) + addStore(injector, new InMemoryStore({ model: ServiceStatus, primaryKey: 'serviceId' })) + addStore(injector, new InMemoryStore({ model: StackConfig, primaryKey: 'stackName' })) + addStore(injector, new InMemoryStore({ model: GitHubRepository, primaryKey: 'id' })) + + const repo = getRepository(injector) + repo.createDataSet(ServiceDefinition, 'id') + repo.createDataSet(ServiceStatus, 'serviceId') + repo.createDataSet(StackConfig, 'stackName') + repo.createDataSet(GitHubRepository, 'id') + + mockGit = { + getCurrentBranch: vi.fn().mockResolvedValue('main'), + getBranches: vi.fn().mockResolvedValue({ local: ['main', 'dev'], remote: ['origin/main'] }), + } + injector.setExplicitInstance(mockGit as unknown as GitService, GitService) + }) + + afterEach(async () => { + await injector[Symbol.asyncDispose]() + }) + + it('should throw 404 when service not found', async () => { + const ctx = createMockActionContext({ injector, urlParams: { id: 'nonexistent' } }) + await expect(ServiceBranchesAction(ctx)).rejects.toThrow(RequestError) + }) + + it('should throw 400 when service is not cloned', async () => { + const ds = getRepository(injector).getDataSetFor(ServiceDefinition, 'id') + await ds.add(injector, { + id: 'svc-1', + stackName: 'stack-1', + displayName: 'Test', + description: '', + runCommand: 'npm start', + prerequisiteIds: [], + prerequisiteServiceIds: [], + files: [], + createdAt: '', + updatedAt: '', + }) + const statusDs = getRepository(injector).getDataSetFor(ServiceStatus, 'serviceId') + await statusDs.add(injector, { + serviceId: 'svc-1', + cloneStatus: 'not-cloned', + installStatus: 'not-installed', + buildStatus: 'not-built', + runStatus: 'stopped', + updatedAt: '', + }) + + const ctx = createMockActionContext({ injector, urlParams: { id: 'svc-1' } }) + await expect(ServiceBranchesAction(ctx)).rejects.toThrow('Repository is not cloned yet') + }) + + it('should return branches when service is cloned', async () => { + const repo = getRepository(injector) + await repo.getDataSetFor(StackConfig, 'stackName').add(injector, { + stackName: 'stack-1', + mainDirectory: '/tmp/stacks', + environmentVariables: {}, + createdAt: '', + updatedAt: '', + }) + const ds = repo.getDataSetFor(ServiceDefinition, 'id') + await ds.add(injector, { + id: 'svc-1', + stackName: 'stack-1', + displayName: 'Test', + description: '', + runCommand: 'npm start', + prerequisiteIds: [], + prerequisiteServiceIds: [], + files: [], + createdAt: '', + updatedAt: '', + }) + const statusDs = repo.getDataSetFor(ServiceStatus, 'serviceId') + await statusDs.add(injector, { + serviceId: 'svc-1', + cloneStatus: 'cloned', + installStatus: 'not-installed', + buildStatus: 'not-built', + runStatus: 'stopped', + updatedAt: '', + }) + + const ctx = createMockActionContext({ injector, urlParams: { id: 'svc-1' } }) + const result = await ServiceBranchesAction(ctx) + const body = result.chunk as { currentBranch: string; local: string[]; remote: string[] } + + expect(body.currentBranch).toBe('main') + expect(body.local).toEqual(['main', 'dev']) + expect(body.remote).toEqual(['origin/main']) + }) +}) diff --git a/service/src/app-models/services/actions/service-branches-action.ts b/service/src/app-models/services/actions/service-branches-action.ts index 85bab99..05c8b65 100644 --- a/service/src/app-models/services/actions/service-branches-action.ts +++ b/service/src/app-models/services/actions/service-branches-action.ts @@ -6,10 +6,7 @@ import { ServiceDefinition, ServiceStatus } from 'common' import { GitService } from '../../../services/git-service.js' import { resolveServiceCwd } from '../../../utils/resolve-service-cwd.js' -export const ServiceBranchesAction: RequestAction = async ({ - injector, - getUrlParams, -}) => { +export const ServiceBranchesAction: RequestAction = async ({ injector, getUrlParams }) => { const { id: serviceId } = getUrlParams() const repo = getRepository(injector) @@ -30,10 +27,7 @@ export const ServiceBranchesAction: RequestAction = asy const cwd = await resolveServiceCwd(injector, svc) const git = injector.getInstance(GitService) - const [currentBranch, branches] = await Promise.all([ - git.getCurrentBranch(cwd), - git.getBranches(cwd), - ]) + const [currentBranch, branches] = await Promise.all([git.getCurrentBranch(cwd), git.getBranches(cwd)]) return JsonResult({ currentBranch, diff --git a/service/src/app-models/services/actions/service-checkout-action.spec.ts b/service/src/app-models/services/actions/service-checkout-action.spec.ts new file mode 100644 index 0000000..a33af03 --- /dev/null +++ b/service/src/app-models/services/actions/service-checkout-action.spec.ts @@ -0,0 +1,136 @@ +import { addStore, InMemoryStore } from '@furystack/core' +import { Injector } from '@furystack/inject' +import { useLogging, VerboseConsoleLogger } from '@furystack/logging' +import { getRepository } from '@furystack/repository' +import { RequestError } from '@furystack/rest' +import { GitHubRepository, ServiceDefinition, ServiceStatus, StackConfig } from 'common' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import { GitService } from '../../../services/git-service.js' +import { ProcessManager } from '../../../services/process-manager.js' +import { ServiceCheckoutAction } from './service-checkout-action.js' + +const createMockActionContext = (options: { + injector: Injector + urlParams?: Record + body?: { branch: string } +}) => ({ + injector: options.injector, + getBody: () => Promise.resolve(options.body ?? { branch: 'dev' }), + getUrlParams: () => (options.urlParams ?? {}) as { id: string }, + getQuery: () => ({}) as never, + request: {} as never, + response: {} as never, +}) + +describe('ServiceCheckoutAction', () => { + let injector: Injector + let mockGit: { checkout: ReturnType; getCurrentBranch: ReturnType } + let mockPm: { updateBranch: ReturnType } + + beforeEach(async () => { + injector = new Injector() + useLogging(injector, VerboseConsoleLogger) + + addStore(injector, new InMemoryStore({ model: ServiceDefinition, primaryKey: 'id' })) + addStore(injector, new InMemoryStore({ model: ServiceStatus, primaryKey: 'serviceId' })) + addStore(injector, new InMemoryStore({ model: StackConfig, primaryKey: 'stackName' })) + addStore(injector, new InMemoryStore({ model: GitHubRepository, primaryKey: 'id' })) + + const repo = getRepository(injector) + repo.createDataSet(ServiceDefinition, 'id') + repo.createDataSet(ServiceStatus, 'serviceId') + repo.createDataSet(StackConfig, 'stackName') + repo.createDataSet(GitHubRepository, 'id') + + mockGit = { + checkout: vi.fn().mockResolvedValue(undefined), + getCurrentBranch: vi.fn().mockResolvedValue('dev'), + } + injector.setExplicitInstance(mockGit as unknown as GitService, GitService) + + mockPm = { + updateBranch: vi.fn().mockResolvedValue(undefined), + } + injector.setExplicitInstance(mockPm as unknown as ProcessManager, ProcessManager) + }) + + afterEach(async () => { + await injector[Symbol.asyncDispose]() + }) + + it('should throw 404 when service not found', async () => { + const ctx = createMockActionContext({ injector, urlParams: { id: 'nonexistent' } }) + await expect(ServiceCheckoutAction(ctx)).rejects.toThrow(RequestError) + }) + + it('should throw 400 when not cloned', async () => { + const ds = getRepository(injector).getDataSetFor(ServiceDefinition, 'id') + await ds.add(injector, { + id: 'svc-1', + stackName: 'stack-1', + displayName: 'Test', + description: '', + runCommand: 'npm start', + prerequisiteIds: [], + prerequisiteServiceIds: [], + files: [], + createdAt: '', + updatedAt: '', + }) + const statusDs = getRepository(injector).getDataSetFor(ServiceStatus, 'serviceId') + await statusDs.add(injector, { + serviceId: 'svc-1', + cloneStatus: 'not-cloned', + installStatus: 'not-installed', + buildStatus: 'not-built', + runStatus: 'stopped', + updatedAt: '', + }) + + const ctx = createMockActionContext({ injector, urlParams: { id: 'svc-1' } }) + await expect(ServiceCheckoutAction(ctx)).rejects.toThrow('Repository is not cloned yet') + }) + + it('should checkout branch and update status when cloned', async () => { + const repo = getRepository(injector) + await repo.getDataSetFor(StackConfig, 'stackName').add(injector, { + stackName: 'stack-1', + mainDirectory: '/tmp/stacks', + environmentVariables: {}, + createdAt: '', + updatedAt: '', + }) + const ds = repo.getDataSetFor(ServiceDefinition, 'id') + await ds.add(injector, { + id: 'svc-1', + stackName: 'stack-1', + displayName: 'Test', + description: '', + runCommand: 'npm start', + prerequisiteIds: [], + prerequisiteServiceIds: [], + files: [], + createdAt: '', + updatedAt: '', + }) + const statusDs = repo.getDataSetFor(ServiceStatus, 'serviceId') + await statusDs.add(injector, { + serviceId: 'svc-1', + cloneStatus: 'cloned', + installStatus: 'not-installed', + buildStatus: 'not-built', + runStatus: 'stopped', + updatedAt: '', + }) + + const ctx = createMockActionContext({ injector, urlParams: { id: 'svc-1' }, body: { branch: 'dev' } }) + const result = await ServiceCheckoutAction(ctx) + const body = result.chunk as { success: boolean; serviceId: string } + + expect(body.success).toBe(true) + expect(body.serviceId).toBe('svc-1') + expect(mockGit.checkout).toHaveBeenCalledWith(expect.any(String), 'dev') + expect(mockPm.updateBranch).toHaveBeenCalledWith('svc-1', 'dev', expect.objectContaining({ triggerSource: 'api' })) + }) +}) From 70fc7b5d64dd6678c92c89aa2974b42e0e9f5dc5 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Sat, 28 Mar 2026 10:27:08 +0100 Subject: [PATCH 06/20] breadcrumbs to header --- e2e/dogfooding.spec.ts | 26 ++++++++++++-- frontend/src/components/body.tsx | 10 +----- frontend/src/components/breadcrumbs.tsx | 1 - frontend/src/components/header.tsx | 10 +++--- frontend/src/components/layout.tsx | 2 +- frontend/src/components/sidebar.tsx | 2 ++ frontend/src/pages/dashboard/index.tsx | 45 ++++++++++++++++++++----- 7 files changed, 69 insertions(+), 27 deletions(-) diff --git a/e2e/dogfooding.spec.ts b/e2e/dogfooding.spec.ts index b638989..26d7bf7 100644 --- a/e2e/dogfooding.spec.ts +++ b/e2e/dogfooding.spec.ts @@ -8,7 +8,29 @@ test('DOG FOODING TIME - Create a service that uses the StackCraft GitHub reposi const uuid = crypto.randomUUID() const stackName = `e2e-dog-fooding-time-${uuid}` - const displayName = `E2E DOG FOODING TIMETest Stack - ${browserName} - ${uuid}` + const displayName = `E2E DOG FOODING Stack - ${browserName} - ${uuid}` + const description = ` + ### 🐶🦴 E2E - IT'S DOG FOODING TIME 🐶🦴 + This stack is used to test the dogfooding of the StackCraft application. + It is used to test the following features: + - Creating a stack + - Creating a service that uses the StackCraft GitHub repository + - Cloning, installing, building and running the service + + ### Test Steps + 1. Create a stack + 2. Create a service that uses the StackCraft GitHub repository + 3. Clone, install, build and run the service + 4. Verify that the service is running + 5. Verify that the service is logging to the console + 6. Verify that the service is accessible via the browser + 7. Verify that the service is accessible via the API + 8. Remove the service + 9. Remove the stack + 10. Verify that the stack and service are removed + 11. The dog has eaten the food. Woof woof! + + ` const workingDirectory = `/tmp/e2e-dog-fooding-time-${uuid}` @@ -21,7 +43,7 @@ test('DOG FOODING TIME - Create a service that uses the StackCraft GitHub reposi await page.locator('input[name="name"]').fill(stackName) await page.locator('input[name="displayName"]').fill(displayName) - await page.locator('textarea[name="description"]').fill('Created by E2E test') + await page.locator('textarea[name="description"]').fill(description) await page.locator('input[name="mainDirectory"]').fill('/tmp/e2e-test') await page.locator('button', { hasText: 'Create' }).click() diff --git a/frontend/src/components/body.tsx b/frontend/src/components/body.tsx index 6d267e3..21de290 100644 --- a/frontend/src/components/body.tsx +++ b/frontend/src/components/body.tsx @@ -4,7 +4,6 @@ import { Dashboard } from '../pages/dashboard/index.js' import { Init, Offline } from '../pages/index.js' import { SessionService } from '../services/session.js' import { appRoutes } from './app-routes.js' -import { Breadcrumbs } from './breadcrumbs.js' export const Body = Shade<{ style?: Partial; injector?: Injector }>({ customElementName: 'shade-app-body', @@ -16,14 +15,7 @@ export const Body = Shade<{ style?: Partial; injector?: Inj {(() => { switch (sessionState) { case 'authenticated': - return ( -
-
- -
- } /> -
- ) + return } /> case 'offline': return default: diff --git a/frontend/src/components/breadcrumbs.tsx b/frontend/src/components/breadcrumbs.tsx index a08d138..14717da 100644 --- a/frontend/src/components/breadcrumbs.tsx +++ b/frontend/src/components/breadcrumbs.tsx @@ -108,7 +108,6 @@ const EntityNameResolver = Shade({ gap: '6px', fontSize: '13px', color: cssVariableTheme.text.secondary, - padding: '8px 0', flexWrap: 'wrap', '& a': { color: cssVariableTheme.text.secondary, diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx index 28081e8..0f6ac87 100644 --- a/frontend/src/components/header.tsx +++ b/frontend/src/components/header.tsx @@ -1,10 +1,13 @@ +import type { Injector } from '@furystack/inject' import { createComponent, Shade } from '@furystack/shades' import { AppBar, AppBarLink, Button, DrawerToggleButton, Icon, icons } from '@furystack/shades-common-components' import { SessionService } from '../services/session.js' +import { Breadcrumbs } from './breadcrumbs.js' import { ThemeSwitch } from './theme-switch/index.js' export type HeaderProps = { title: string + injector?: Injector } export const Header = Shade({ @@ -36,12 +39,7 @@ export const Header = Shade({ {props.title}
- {sessionState === 'authenticated' ? ( -
- Dashboard - Settings -
- ) : null} + {sessionState === 'authenticated' ? : null}
diff --git a/frontend/src/components/layout.tsx b/frontend/src/components/layout.tsx index 16125ef..4e2afff 100644 --- a/frontend/src/components/layout.tsx +++ b/frontend/src/components/layout.tsx @@ -90,7 +90,7 @@ const AuthenticatedLayout = Shade({ , + component:
, }} drawer={{ left: { diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx index 53a8145..1047d8e 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar.tsx @@ -256,6 +256,8 @@ export const Sidebar = Shade<{ injector?: Injector }>({ return (
) : null} diff --git a/frontend/src/pages/import-export/export-stack.tsx b/frontend/src/pages/import-export/export-stack.tsx index ab04bd2..fd4bcc8 100644 --- a/frontend/src/pages/import-export/export-stack.tsx +++ b/frontend/src/pages/import-export/export-stack.tsx @@ -43,15 +43,6 @@ export const ExportStack = Shade({ icon="📤" title="Export Stack" description="Copy the JSON below and share it with other developers to quickly set up the same stack." - actions={ - - } /> {isLoading ? ( diff --git a/frontend/src/pages/repositories/edit-repository.tsx b/frontend/src/pages/repositories/edit-repository.tsx index fe4ddd4..8bca306 100644 --- a/frontend/src/pages/repositories/edit-repository.tsx +++ b/frontend/src/pages/repositories/edit-repository.tsx @@ -14,7 +14,7 @@ import { } from '@furystack/shades-common-components' import type { GitHubRepository } from 'common' import { GitHubRepository as GitHubRepositoryModel } from 'common' -import { StackCraftNestedRouteLink, stackCraftNavigate } from '../../components/app-routes.js' +import { stackCraftNavigate } from '../../components/app-routes.js' import { GitHubRepoForm } from '../../components/entity-forms/github-repo-form.js' import { GitHubReposApiClient } from '../../services/api-clients/github-repos-api-client.js' @@ -44,17 +44,7 @@ export const EditRepository = Shade({ if (repoState.status === 'error') { return ( - - - - } - /> + ) } @@ -63,16 +53,7 @@ export const EditRepository = Shade({ if (!repo) { return ( - - - - } - /> + ) } @@ -136,22 +117,16 @@ export const EditRepository = Shade({ - - - - -
+ } /> diff --git a/frontend/src/pages/services/service-detail.tsx b/frontend/src/pages/services/service-detail.tsx index d0fc555..9157af2 100644 --- a/frontend/src/pages/services/service-detail.tsx +++ b/frontend/src/pages/services/service-detail.tsx @@ -103,19 +103,7 @@ export const ServiceDetail = Shade({ if (serviceState.status === 'error') { return ( - history.back()} - startIcon={} - > - Back - - } - /> + ) } @@ -124,18 +112,7 @@ export const ServiceDetail = Shade({ if (!serviceData) { return ( - history.back()} - startIcon={} - > - Back - - } - /> + ) } diff --git a/frontend/src/pages/services/service-logs.tsx b/frontend/src/pages/services/service-logs.tsx index 8bdf74b..b55db28 100644 --- a/frontend/src/pages/services/service-logs.tsx +++ b/frontend/src/pages/services/service-logs.tsx @@ -43,25 +43,17 @@ export const ServiceLogs = Shade({ icon="📋" title={props.processUid ? 'Process Logs' : 'Service Logs'} actions={ -
- {!props.processUid && ( - - )} + !props.processUid ? ( -
+ ) : undefined } /> diff --git a/frontend/src/pages/stacks/edit-stack.tsx b/frontend/src/pages/stacks/edit-stack.tsx index 2d28176..7f83bda 100644 --- a/frontend/src/pages/stacks/edit-stack.tsx +++ b/frontend/src/pages/stacks/edit-stack.tsx @@ -14,7 +14,7 @@ import { } from '@furystack/shades-common-components' import type { EnvironmentVariableValue, StackView } from 'common' import { StackConfig, StackDefinition } from 'common' -import { StackCraftNestedRouteLink, stackCraftNavigate } from '../../components/app-routes.js' +import { stackCraftNavigate } from '../../components/app-routes.js' import { StackForm } from '../../components/entity-forms/stack-form.js' import { EnvironmentVariablesManager } from '../../components/environment-variables-manager.js' import { StacksApiClient } from '../../services/api-clients/stacks-api-client.js' @@ -45,17 +45,7 @@ export const EditStack = Shade({ if (stackState.status === 'error') { return ( - - - - } - /> + ) } @@ -68,16 +58,7 @@ export const EditStack = Shade({ if (!stack) { return ( - - - - } - /> + ) } @@ -141,22 +122,16 @@ export const EditStack = Shade({ - - - - - + } /> diff --git a/service/data-backup-mp/service-logs.sqlite b/service/data-backup-mp/service-logs.sqlite deleted file mode 100644 index 8aef413053e6d015af17997b3cb8f0018e42d9d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1671168 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 z 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�J`cO-%ip`ZqrDYt(D_ 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 OdWOEUH~fWcoc{*}f#F{O diff --git a/service/data-backup-mp/stack-craft.sqlite b/service/data-backup-mp/stack-craft.sqlite deleted file mode 100644 index 82e800a697048ee7deb19b8ef7f029afa052c88f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 241664 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 yS69{mA^6X?yIU)Swz|u-t diff --git a/service/src/app-models/services/actions/service-checkout-action.ts b/service/src/app-models/services/actions/service-checkout-action.ts index 41a7f8e..1a7c0cf 100644 --- a/service/src/app-models/services/actions/service-checkout-action.ts +++ b/service/src/app-models/services/actions/service-checkout-action.ts @@ -35,7 +35,14 @@ export const ServiceCheckoutAction: RequestAction = asy const git = injector.getInstance(GitService) const pm = injector.getInstance(ProcessManager) - await git.checkout(cwd, branch) + const localBranch = branch.replace(/^origin\//, '') + + try { + await git.checkout(cwd, localBranch) + } catch (error) { + const message = error instanceof Error ? error.message : 'Checkout failed' + throw new RequestError(`Failed to checkout branch "${localBranch}": ${message}`, 409) + } const currentBranch = await git.getCurrentBranch(cwd) diff --git a/service/src/services/process-manager.ts b/service/src/services/process-manager.ts index 00ae777..ab6d0d8 100644 --- a/service/src/services/process-manager.ts +++ b/service/src/services/process-manager.ts @@ -427,8 +427,10 @@ export class ProcessManager { await getRepository(elevated) .getDataSetFor(ServiceStatus, 'serviceId') .update(elevated, serviceId, { currentBranch: branch }) - } catch { - // Best-effort; branch info is non-critical + } catch (error) { + void this.logger.verbose({ + message: `Failed to refresh branch for ${serviceId}: ${(error as Error).message}`, + }) } } @@ -906,6 +908,23 @@ export class ProcessManager { }) await this.updateServiceStatus(status.serviceId, update, 'state-reconciled', reconcileTrigger, staleMetadata) } + + if (status.cloneStatus === 'cloned' && !status.currentBranch) { + try { + const services = await getRepository(elevated) + .getDataSetFor(ServiceDefinition, 'id') + .find(elevated, { filter: { id: { $eq: status.serviceId } }, top: 1 }) + const svc = services[0] + if (svc) { + const cwd = await resolveServiceCwd(getInjectorReference(this), svc, elevated) + await this.refreshCurrentBranch(status.serviceId, cwd) + } + } catch (error) { + await this.logger.verbose({ + message: `Could not refresh branch for service ${status.serviceId}: ${(error as Error).message}`, + }) + } + } } } From 5cfec7c24015613987f01c9dbfb60083a11184fe Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Sat, 28 Mar 2026 11:05:13 +0100 Subject: [PATCH 08/20] display branch fix --- common/src/models/service-status.ts | 3 --- service/src/app-models/data-store/setup-data-store.ts | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/common/src/models/service-status.ts b/common/src/models/service-status.ts index 4765468..3935467 100644 --- a/common/src/models/service-status.ts +++ b/common/src/models/service-status.ts @@ -18,14 +18,11 @@ export class ServiceStatus { installStatus: InstallStatus = 'not-installed' buildStatus: BuildStatus = 'not-built' runStatus: RunStatus = 'stopped' - currentBranch?: string - lastClonedAt?: string lastInstalledAt?: string lastBuiltAt?: string lastStartedAt?: string lastFetchedAt?: string - updatedAt!: string } diff --git a/service/src/app-models/data-store/setup-data-store.ts b/service/src/app-models/data-store/setup-data-store.ts index ca2e5d5..bdd4a78 100644 --- a/service/src/app-models/data-store/setup-data-store.ts +++ b/service/src/app-models/data-store/setup-data-store.ts @@ -80,6 +80,7 @@ class ServiceStatusModel extends Model implements declare lastBuiltAt: string | undefined declare lastStartedAt: string | undefined declare lastFetchedAt: string | undefined + declare currentBranch: string | undefined declare updatedAt: string } @@ -372,6 +373,7 @@ async function initAllModels(sequelize: Sequelize): Promise { lastBuiltAt: { type: DataTypes.DATE, allowNull: true }, lastStartedAt: { type: DataTypes.DATE, allowNull: true }, lastFetchedAt: { type: DataTypes.DATE, allowNull: true }, + currentBranch: { type: DataTypes.STRING, allowNull: true }, updatedAt: { type: DataTypes.DATE }, }, { sequelize, createdAt: false }, From 65059f6b39a36c8f5832bdc1b532371aaba878a3 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Sat, 28 Mar 2026 13:18:59 +0100 Subject: [PATCH 09/20] SqLite to Postgres --- .env.example | 1 + .github/workflows/ui-tests.yml | 18 + README.md | 12 +- docker-compose.yml | 14 + service/package.json | 8 +- .../app-models/data-store/setup-data-store.ts | 112 ++-- service/src/config.ts | 8 - yarn.lock | 495 +++++------------- 8 files changed, 220 insertions(+), 448 deletions(-) create mode 100644 .env.example create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f16fa76 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATABASE_URL=postgres://stackcraft:stackcraft@localhost:5432/stackcraft diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 1dcb862..025626d 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -10,6 +10,24 @@ jobs: matrix: node-version: [22.x, 24.x] + services: + postgres: + image: postgres:17-alpine + env: + POSTGRES_USER: stackcraft + POSTGRES_PASSWORD: stackcraft + POSTGRES_DB: stackcraft + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + DATABASE_URL: postgres://stackcraft:stackcraft@localhost:5432/stackcraft + steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} diff --git a/README.md b/README.md index d799fa7..c8c91e6 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,19 @@ Example web app with common type API definitions, a FuryStack-based backend serv 1. Clone the repository 1. Install the dependencies with `yarn` +1. Start PostgreSQL (e.g. `docker compose up -d`) +1. Copy `.env.example` to `.env` and adjust if needed 1. Start the frontend and the backend service with `yarn start` (you can stop / start them individually, check the NPM scripts for further details) +## Docker + +When running the Docker image, pass the `DATABASE_URL` environment variable: + +```bash +docker run -e DATABASE_URL=postgres://user:password@host:5432/stackcraft furystack/stack-craft +``` + # Testing - You can execute the example Vitest tests with `yarn test` -- You can execute E2E tests with `yarn test:e2e` +- You can execute E2E tests with `yarn test:e2e` (requires a running PostgreSQL instance and `DATABASE_URL` set) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e32fbb5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + postgres: + image: postgres:17-alpine + environment: + POSTGRES_USER: stackcraft + POSTGRES_PASSWORD: stackcraft + POSTGRES_DB: stackcraft + ports: + - '5432:5432' + volumes: + - pgdata:/var/lib/postgresql/data + +volumes: + pgdata: diff --git a/service/package.json b/service/package.json index a6bdd96..89958f0 100644 --- a/service/package.json +++ b/service/package.json @@ -6,7 +6,7 @@ "private": true, "type": "module", "scripts": { - "start": "yarn node ./dist/service.js", + "start": "yarn node --env-file-if-exists=.env ./dist/service.js", "clean": "rimraf dist && rimraf tsconfig.tsbuildinfo", "build": "tsc -b" }, @@ -18,7 +18,6 @@ "dependencies": { "@furystack/core": "^16.0.3", "@furystack/entity-sync-service": "^1.0.11", - "@furystack/filesystem-store": "^7.1.6", "@furystack/inject": "^12.0.35", "@furystack/logging": "^8.1.4", "@furystack/repository": "^10.1.10", @@ -28,7 +27,8 @@ "@furystack/websocket-api": "^13.2.6", "@modelcontextprotocol/sdk": "1.28.0", "common": "workspace:^", - "sequelize": "^6.37.8", - "sqlite3": "^6.0.1" + "pg": "^8.16.0", + "pg-hstore": "^2.3.4", + "sequelize": "^6.37.8" } } diff --git a/service/src/app-models/data-store/setup-data-store.ts b/service/src/app-models/data-store/setup-data-store.ts index bdd4a78..f085687 100644 --- a/service/src/app-models/data-store/setup-data-store.ts +++ b/service/src/app-models/data-store/setup-data-store.ts @@ -19,10 +19,8 @@ import { import type { EnvironmentVariableValue, PrerequisiteConfig, PrerequisiteType, ServiceFile } from 'common' import { DataTypes, Model } from 'sequelize' import type { Options, Sequelize } from 'sequelize' -import sqlite from 'sqlite3' -import { join } from 'path' -import { authorizedDataSet, dataDir, ensureDataDir } from '../../config.js' +import { authorizedDataSet } from '../../config.js' // --- Sequelize Model classes --- @@ -139,33 +137,34 @@ class ApiTokenModel extends Model implements ApiToken { declare createdAt: string } -const getDbOptions = (): Options => ({ - dialect: 'sqlite', - dialectModule: sqlite, - storage: join(dataDir, 'stack-craft.sqlite'), - logging: false, -}) +const getDbOptions = (): Options => { + const databaseUrl = process.env.DATABASE_URL + if (!databaseUrl) { + throw new Error('DATABASE_URL environment variable is required') + } + const parsed = new URL(databaseUrl) + return { + dialect: 'postgres', + host: parsed.hostname, + port: parseInt(parsed.port, 10) || 5432, + database: parsed.pathname.replace(/^\//, ''), + username: decodeURIComponent(parsed.username), + password: decodeURIComponent(parsed.password), + logging: false, + } +} /** * Initializes ALL Sequelize models and their associations on the shared connection. * Called once via the first useSequelize's initModel callback. */ async function initAllModels(sequelize: Sequelize): Promise { - await sequelize.query('PRAGMA foreign_keys = ON') - UserModel.init( { username: { type: DataTypes.STRING, primaryKey: true }, roles: { - type: DataTypes.TEXT, - defaultValue: '[]', - get() { - const raw = this.getDataValue('roles') - return typeof raw === 'string' ? (JSON.parse(raw) as string[]) : raw - }, - set(val: string[]) { - this.setDataValue('roles', JSON.stringify(val) as unknown as string[]) - }, + type: DataTypes.JSONB, + defaultValue: [], }, }, { sequelize, timestamps: false }, @@ -205,18 +204,8 @@ async function initAllModels(sequelize: Sequelize): Promise { }, mainDirectory: { type: DataTypes.STRING, allowNull: false }, environmentVariables: { - type: DataTypes.TEXT, - defaultValue: '{}', - get() { - const raw = this.getDataValue('environmentVariables') - return typeof raw === 'string' ? (JSON.parse(raw) as Record) : (raw ?? {}) - }, - set(val: Record) { - this.setDataValue( - 'environmentVariables', - JSON.stringify(val) as unknown as Record, - ) - }, + type: DataTypes.JSONB, + defaultValue: {}, }, createdAt: { type: DataTypes.DATE }, updatedAt: { type: DataTypes.DATE }, @@ -252,15 +241,8 @@ async function initAllModels(sequelize: Sequelize): Promise { name: { type: DataTypes.STRING, allowNull: false }, type: { type: DataTypes.STRING, allowNull: false }, config: { - type: DataTypes.TEXT, - defaultValue: '{}', - get() { - const raw = this.getDataValue('config') - return typeof raw === 'string' ? (JSON.parse(raw) as PrerequisiteConfig) : raw - }, - set(val: PrerequisiteConfig) { - this.setDataValue('config', JSON.stringify(val) as unknown as PrerequisiteConfig) - }, + type: DataTypes.JSONB, + defaultValue: {}, }, installationHelp: { type: DataTypes.TEXT, defaultValue: '' }, createdAt: { type: DataTypes.DATE }, @@ -286,40 +268,19 @@ async function initAllModels(sequelize: Sequelize): Promise { references: { model: GitHubRepositoryModel, key: 'id' }, }, prerequisiteIds: { - type: DataTypes.TEXT, - defaultValue: '[]', - get() { - const raw = this.getDataValue('prerequisiteIds') - return typeof raw === 'string' ? (JSON.parse(raw) as string[]) : raw - }, - set(val: string[]) { - this.setDataValue('prerequisiteIds', JSON.stringify(val) as unknown as string[]) - }, + type: DataTypes.JSONB, + defaultValue: [], }, prerequisiteServiceIds: { - type: DataTypes.TEXT, - defaultValue: '[]', - get() { - const raw = this.getDataValue('prerequisiteServiceIds') - return typeof raw === 'string' ? (JSON.parse(raw) as string[]) : raw - }, - set(val: string[]) { - this.setDataValue('prerequisiteServiceIds', JSON.stringify(val) as unknown as string[]) - }, + type: DataTypes.JSONB, + defaultValue: [], }, installCommand: { type: DataTypes.STRING, allowNull: true }, buildCommand: { type: DataTypes.STRING, allowNull: true }, runCommand: { type: DataTypes.STRING, allowNull: false }, files: { - type: DataTypes.TEXT, - defaultValue: '[]', - get() { - const raw = this.getDataValue('files') - return typeof raw === 'string' ? (JSON.parse(raw) as ServiceFile[]) : raw - }, - set(val: ServiceFile[]) { - this.setDataValue('files', JSON.stringify(val) as unknown as ServiceFile[]) - }, + type: DataTypes.JSONB, + defaultValue: [], }, createdAt: { type: DataTypes.DATE }, updatedAt: { type: DataTypes.DATE }, @@ -338,18 +299,8 @@ async function initAllModels(sequelize: Sequelize): Promise { autoFetchIntervalMinutes: { type: DataTypes.INTEGER, defaultValue: 60 }, autoRestartOnFetch: { type: DataTypes.BOOLEAN, defaultValue: false }, environmentVariableOverrides: { - type: DataTypes.TEXT, - defaultValue: '{}', - get() { - const raw = this.getDataValue('environmentVariableOverrides') - return typeof raw === 'string' ? (JSON.parse(raw) as Record) : (raw ?? {}) - }, - set(val: Record) { - this.setDataValue( - 'environmentVariableOverrides', - JSON.stringify(val) as unknown as Record, - ) - }, + type: DataTypes.JSONB, + defaultValue: {}, }, createdAt: { type: DataTypes.DATE }, updatedAt: { type: DataTypes.DATE }, @@ -453,7 +404,6 @@ async function initAllModels(sequelize: Sequelize): Promise { } export const setupDataStore = async (injector: Injector) => { - ensureDataDir() const logger = getLogger(injector).withScope('DataStore') const dbOptions = getDbOptions() diff --git a/service/src/config.ts b/service/src/config.ts index 321363f..f47111b 100644 --- a/service/src/config.ts +++ b/service/src/config.ts @@ -6,14 +6,6 @@ import { getRepository } from '@furystack/repository' import { DefaultSession } from '@furystack/rest-service' import { PasswordResetToken, usePasswordPolicy } from '@furystack/security' import { PrerequisiteCheckResult, PublicApiToken } from 'common' -import { mkdirSync } from 'fs' -import { join } from 'path' - -export const dataDir = process.env.STACK_CRAFT_DATA_DIR || join(process.cwd(), 'data') - -export const ensureDataDir = () => { - mkdirSync(dataDir, { recursive: true }) -} export const authorizedOnly = async (options: { injector: Injector }): Promise => { const isAllowed = await isAuthenticated(options.injector) diff --git a/yarn.lock b/yarn.lock index 7b44095..add7ea5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -446,17 +446,6 @@ __metadata: languageName: node linkType: hard -"@furystack/filesystem-store@npm:^7.1.6": - version: 7.1.6 - resolution: "@furystack/filesystem-store@npm:7.1.6" - dependencies: - "@furystack/core": "npm:^16.0.3" - "@furystack/inject": "npm:^12.0.35" - "@furystack/utils": "npm:^8.2.4" - checksum: 10c0/cdb57385d5ed552a0dff9a7eb437255e7cb6adbc3a305c8133b236189d79df4c630fb874715ffd5681d8e506c3799d2d77699b6a3c456faf6c531dd773c1b5d6 - languageName: node - linkType: hard - "@furystack/inject@npm:^12.0.35": version: 12.0.35 resolution: "@furystack/inject@npm:12.0.35" @@ -1645,13 +1634,6 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1": - version: 1.5.1 - resolution: "base64-js@npm:1.5.1" - checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf - languageName: node - linkType: hard - "before-after-hook@npm:^2.2.0": version: 2.2.3 resolution: "before-after-hook@npm:2.2.3" @@ -1668,26 +1650,6 @@ __metadata: languageName: node linkType: hard -"bindings@npm:^1.5.0": - version: 1.5.0 - resolution: "bindings@npm:1.5.0" - dependencies: - file-uri-to-path: "npm:1.0.0" - checksum: 10c0/3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba - languageName: node - linkType: hard - -"bl@npm:^4.0.3": - version: 4.1.0 - resolution: "bl@npm:4.1.0" - dependencies: - buffer: "npm:^5.5.0" - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.4.0" - checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f - languageName: node - linkType: hard - "body-parser@npm:^2.2.1": version: 2.2.2 resolution: "body-parser@npm:2.2.2" @@ -1724,16 +1686,6 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.5.0": - version: 5.7.1 - resolution: "buffer@npm:5.7.1" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.1.13" - checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e - languageName: node - linkType: hard - "bytes@npm:^3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" @@ -1809,13 +1761,6 @@ __metadata: languageName: node linkType: hard -"chownr@npm:^1.1.1": - version: 1.1.4 - resolution: "chownr@npm:1.1.4" - checksum: 10c0/ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db - languageName: node - linkType: hard - "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -2045,22 +1990,6 @@ __metadata: languageName: node linkType: hard -"decompress-response@npm:^6.0.0": - version: 6.0.0 - resolution: "decompress-response@npm:6.0.0" - dependencies: - mimic-response: "npm:^3.1.0" - checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e - languageName: node - linkType: hard - -"deep-extend@npm:^0.6.0": - version: 0.6.0 - resolution: "deep-extend@npm:0.6.0" - checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 - languageName: node - linkType: hard - "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -2104,7 +2033,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.3": +"detect-libc@npm:^2.0.3": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 @@ -2168,15 +2097,6 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": - version: 1.4.5 - resolution: "end-of-stream@npm:1.4.5" - dependencies: - once: "npm:^1.4.0" - checksum: 10c0/b0701c92a10b89afb1cb45bf54a5292c6f008d744eb4382fa559d54775ff31617d1d7bc3ef617575f552e24fad2c7c1a1835948c66b3f3a4be0a6c1f35c883d8 - languageName: node - linkType: hard - "entities@npm:^6.0.0": version: 6.0.1 resolution: "entities@npm:6.0.1" @@ -2621,13 +2541,6 @@ __metadata: languageName: node linkType: hard -"expand-template@npm:^2.0.3": - version: 2.0.3 - resolution: "expand-template@npm:2.0.3" - checksum: 10c0/1c9e7afe9acadf9d373301d27f6a47b34e89b3391b1ef38b7471d381812537ef2457e620ae7f819d2642ce9c43b189b3583813ec395e2938319abe356a9b2f51 - languageName: node - linkType: hard - "expect-type@npm:^1.3.0": version: 1.3.0 resolution: "expect-type@npm:1.3.0" @@ -2745,13 +2658,6 @@ __metadata: languageName: node linkType: hard -"file-uri-to-path@npm:1.0.0": - version: 1.0.0 - resolution: "file-uri-to-path@npm:1.0.0" - checksum: 10c0/3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519 - languageName: node - linkType: hard - "finalhandler@npm:^2.1.0": version: 2.1.1 resolution: "finalhandler@npm:2.1.1" @@ -2839,13 +2745,6 @@ __metadata: languageName: unknown linkType: soft -"fs-constants@npm:^1.0.0": - version: 1.0.0 - resolution: "fs-constants@npm:1.0.0" - checksum: 10c0/a0cde99085f0872f4d244e83e03a46aa387b74f5a5af750896c6b05e9077fac00e9932fdf5aef84f2f16634cd473c63037d7a512576da7d5c2b9163d1909f3a8 - languageName: node - linkType: hard - "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -2977,13 +2876,6 @@ __metadata: languageName: node linkType: hard -"github-from-package@npm:0.0.0": - version: 0.0.0 - resolution: "github-from-package@npm:0.0.0" - checksum: 10c0/737ee3f52d0a27e26332cde85b533c21fcdc0b09fb716c3f8e522cfaa9c600d4a631dec9fcde179ec9d47cca89017b7848ed4d6ae6b6b78f936c06825b1fcc12 - languageName: node - linkType: hard - "glob-parent@npm:^6.0.2": version: 6.0.2 resolution: "glob-parent@npm:6.0.2" @@ -3204,13 +3096,6 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13": - version: 1.2.1 - resolution: "ieee754@npm:1.2.1" - checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb - languageName: node - linkType: hard - "ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -3239,20 +3124,13 @@ __metadata: languageName: node linkType: hard -"inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.4": +"inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 languageName: node linkType: hard -"ini@npm:~1.3.0": - version: 1.3.8 - resolution: "ini@npm:1.3.8" - checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a - languageName: node - linkType: hard - "internal-slot@npm:^1.1.0": version: 1.1.0 resolution: "internal-slot@npm:1.1.0" @@ -4018,13 +3896,6 @@ __metadata: languageName: node linkType: hard -"mimic-response@npm:^3.1.0": - version: 3.1.0 - resolution: "mimic-response@npm:3.1.0" - checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 - languageName: node - linkType: hard - "minimatch@npm:^10.1.1": version: 10.1.1 resolution: "minimatch@npm:10.1.1" @@ -4061,7 +3932,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6": +"minimist@npm:^1.2.0, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 @@ -4151,13 +4022,6 @@ __metadata: languageName: node linkType: hard -"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": - version: 0.5.3 - resolution: "mkdirp-classic@npm:0.5.3" - checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 - languageName: node - linkType: hard - "moment-timezone@npm:^0.5.43": version: 0.5.48 resolution: "moment-timezone@npm:0.5.48" @@ -4190,13 +4054,6 @@ __metadata: languageName: node linkType: hard -"napi-build-utils@npm:^2.0.0": - version: 2.0.0 - resolution: "napi-build-utils@npm:2.0.0" - checksum: 10c0/5833aaeb5cc5c173da47a102efa4680a95842c13e0d9cc70428bd3ee8d96bb2172f8860d2811799b5daa5cbeda779933601492a2028a6a5351c6d0fcf6de83db - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -4211,44 +4068,6 @@ __metadata: languageName: node linkType: hard -"node-abi@npm:^3.3.0": - version: 3.87.0 - resolution: "node-abi@npm:3.87.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/41cfc361edd1b0711d412ca9e1a475180c5b897868bd5583df7ff73e30e6044cc7de307df36c2257203320f17fadf7e82dfdf5a9f6fd510a8578e3fe3ed67ebb - languageName: node - linkType: hard - -"node-addon-api@npm:^8.0.0": - version: 8.7.0 - resolution: "node-addon-api@npm:8.7.0" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/31a03b00f6b0753ab08360952fdf80a1abb619dcf8125fa1ab07e3a414da050963440c3a86c77a0334c0be7a71acb5e242dc468b79201ee6151c7b943afe946d - languageName: node - linkType: hard - -"node-gyp@npm:12.x": - version: 12.2.0 - resolution: "node-gyp@npm:12.2.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^15.0.0" - nopt: "npm:^9.0.0" - proc-log: "npm:^6.0.0" - semver: "npm:^7.3.5" - tar: "npm:^7.5.4" - tinyglobby: "npm:^0.2.12" - which: "npm:^6.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/3ed046746a5a7d90950cd8b0547332b06598443f31fe213ef4332a7174c7b7d259e1704835feda79b87d3f02e59d7791842aac60642ede4396ab25fdf0f8f759 - languageName: node - linkType: hard - "node-gyp@npm:latest": version: 12.1.0 resolution: "node-gyp@npm:12.1.0" @@ -4380,7 +4199,7 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -4549,6 +4368,20 @@ __metadata: languageName: node linkType: hard +"pg-cloudflare@npm:^1.3.0": + version: 1.3.0 + resolution: "pg-cloudflare@npm:1.3.0" + checksum: 10c0/b0866c88af8e54c7b3ed510719d92df37714b3af5e3a3a10d9f761fcec99483e222f5b78a1f2de590368127648087c45c01aaf66fadbe46edb25673eedc4f8fc + languageName: node + linkType: hard + +"pg-connection-string@npm:^2.12.0": + version: 2.12.0 + resolution: "pg-connection-string@npm:2.12.0" + checksum: 10c0/3a26c62884a9f0464718f652bd5d6bce276ebda830c0fef4de4f88ae73c2507d70cae1d45c2f5b49bebd76187fb4c94f889d07c53fca6acd06b2eecbebcdc336 + languageName: node + linkType: hard + "pg-connection-string@npm:^2.6.1": version: 2.11.0 resolution: "pg-connection-string@npm:2.11.0" @@ -4556,6 +4389,82 @@ __metadata: languageName: node linkType: hard +"pg-hstore@npm:^2.3.4": + version: 2.3.4 + resolution: "pg-hstore@npm:2.3.4" + dependencies: + underscore: "npm:^1.13.1" + checksum: 10c0/2f4e352ba82b200cda55b796e7e8ef7e50377914f1ab4d91fa6dc9701b3d4ae4ddf168376a08a21d1f5a38d87f434be1c55d2287ec6b5135db3745ca296c99e2 + languageName: node + linkType: hard + +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: 10c0/be6a02d851fc2a4ae3e9de81710d861de3ba35ac927268973eb3cb618873a05b9424656df464dd43bd7dc3fc5295c3f5b3c8349494f87c7af50ec59ef14e0b98 + languageName: node + linkType: hard + +"pg-pool@npm:^3.13.0": + version: 3.13.0 + resolution: "pg-pool@npm:3.13.0" + peerDependencies: + pg: ">=8.0" + checksum: 10c0/2756f79cda14e3834356f2ca035deab806bca2172a38a488b62ada54bd3e65d33f583661bbe96da0c0e75e6bc59807ada733c37efca6e24ae2893429936a1549 + languageName: node + linkType: hard + +"pg-protocol@npm:^1.13.0": + version: 1.13.0 + resolution: "pg-protocol@npm:1.13.0" + checksum: 10c0/a4e851e6bb8ff404ca19d561cf49b6b0caf45163bd3f289889edaf6c4e9fb25b08fb57f50d37a8cc86007efcf2cbb3dd2372c97a353a546f45eb49ddebc84fa9 + languageName: node + linkType: hard + +"pg-types@npm:2.2.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: "npm:1.0.1" + postgres-array: "npm:~2.0.0" + postgres-bytea: "npm:~1.0.0" + postgres-date: "npm:~1.0.4" + postgres-interval: "npm:^1.1.0" + checksum: 10c0/ab3f8069a323f601cd2d2279ca8c425447dab3f9b61d933b0601d7ffc00d6200df25e26a4290b2b0783b59278198f7dd2ed03e94c4875797919605116a577c65 + languageName: node + linkType: hard + +"pg@npm:^8.16.0": + version: 8.20.0 + resolution: "pg@npm:8.20.0" + dependencies: + pg-cloudflare: "npm:^1.3.0" + pg-connection-string: "npm:^2.12.0" + pg-pool: "npm:^3.13.0" + pg-protocol: "npm:^1.13.0" + pg-types: "npm:2.2.0" + pgpass: "npm:1.0.5" + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 10c0/e21d44b9fb3ec188e67778d7abd32d945a546f2da5128b6c8c16da8ae1e42fdc953c0d6f0a2ee65d11f31808c1dffaf908cb9c880cd2e8f0ae05525e4b8bc832 + languageName: node + linkType: hard + +"pgpass@npm:1.0.5": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" + dependencies: + split2: "npm:^4.1.0" + checksum: 10c0/5ea6c9b2de04c33abb08d33a2dded303c4a3c7162a9264519cbe85c0a9857d712463140ba42fad0c7cd4b21f644dd870b45bb2e02fcbe505b4de0744fd802c1d + languageName: node + linkType: hard + "picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -4626,25 +4535,33 @@ __metadata: languageName: node linkType: hard -"prebuild-install@npm:^7.1.3": - version: 7.1.3 - resolution: "prebuild-install@npm:7.1.3" - dependencies: - detect-libc: "npm:^2.0.0" - expand-template: "npm:^2.0.3" - github-from-package: "npm:0.0.0" - minimist: "npm:^1.2.3" - mkdirp-classic: "npm:^0.5.3" - napi-build-utils: "npm:^2.0.0" - node-abi: "npm:^3.3.0" - pump: "npm:^3.0.0" - rc: "npm:^1.2.7" - simple-get: "npm:^4.0.0" - tar-fs: "npm:^2.0.0" - tunnel-agent: "npm:^0.6.0" - bin: - prebuild-install: bin.js - checksum: 10c0/25919a42b52734606a4036ab492d37cfe8b601273d8dfb1fa3c84e141a0a475e7bad3ab848c741d2f810cef892fcf6059b8c7fe5b29f98d30e0c29ad009bedff +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 10c0/cbd56207e4141d7fbf08c86f2aebf21fa7064943d3f808ec85f442ff94b48d891e7a144cc02665fb2de5dbcb9b8e3183a2ac749959e794b4a4cfd379d7a21d08 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.1 + resolution: "postgres-bytea@npm:1.0.1" + checksum: 10c0/10b28a27c9d703d5befd97c443e62b551096d1014bc59ab574c65bf0688de7f3f068003b2aea8dcff83cf0f6f9a35f9f74457c38856cf8eb81b00cf3fb44f164 + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 10c0/0ff91fccc64003e10b767fcfeefb5eaffbc522c93aa65d5051c49b3c4ce6cb93ab091a7d22877a90ad60b8874202c6f1d0f935f38a7235ed3b258efd54b97ca9 + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: "npm:^4.0.0" + checksum: 10c0/c1734c3cb79e7f22579af0b268a463b1fa1d084e742a02a7a290c4f041e349456f3bee3b4ee0bb3f226828597f7b76deb615c1b857db9a742c45520100456272 languageName: node linkType: hard @@ -4700,16 +4617,6 @@ __metadata: languageName: node linkType: hard -"pump@npm:^3.0.0": - version: 3.0.3 - resolution: "pump@npm:3.0.3" - dependencies: - end-of-stream: "npm:^1.1.0" - once: "npm:^1.3.1" - checksum: 10c0/ada5cdf1d813065bbc99aa2c393b8f6beee73b5de2890a8754c9f488d7323ffd2ca5f5a0943b48934e3fcbd97637d0337369c3c631aeb9614915db629f1c75c9 - languageName: node - linkType: hard - "punycode@npm:^2.1.0, punycode@npm:^2.3.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -4745,31 +4652,6 @@ __metadata: languageName: node linkType: hard -"rc@npm:^1.2.7": - version: 1.2.8 - resolution: "rc@npm:1.2.8" - dependencies: - deep-extend: "npm:^0.6.0" - ini: "npm:~1.3.0" - minimist: "npm:^1.2.0" - strip-json-comments: "npm:~2.0.1" - bin: - rc: ./cli.js - checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 - languageName: node - linkType: hard - -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" - dependencies: - inherits: "npm:^2.0.3" - string_decoder: "npm:^1.1.1" - util-deprecate: "npm:^1.0.1" - checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 - languageName: node - linkType: hard - "reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": version: 1.0.10 resolution: "reflect.getprototypeof@npm:1.0.10" @@ -4967,13 +4849,6 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 - languageName: node - linkType: hard - "safe-push-apply@npm:^1.0.0": version: 1.0.0 resolution: "safe-push-apply@npm:1.0.0" @@ -5132,7 +5007,6 @@ __metadata: dependencies: "@furystack/core": "npm:^16.0.3" "@furystack/entity-sync-service": "npm:^1.0.11" - "@furystack/filesystem-store": "npm:^7.1.6" "@furystack/inject": "npm:^12.0.35" "@furystack/logging": "npm:^8.1.4" "@furystack/repository": "npm:^10.1.10" @@ -5143,8 +5017,9 @@ __metadata: "@modelcontextprotocol/sdk": "npm:1.28.0" "@types/node": "npm:^25.5.0" common: "workspace:^" + pg: "npm:^8.16.0" + pg-hstore: "npm:^2.3.4" sequelize: "npm:^6.37.8" - sqlite3: "npm:^6.0.1" typescript: "npm:^5.9.3" vitest: "npm:^4.1.2" languageName: unknown @@ -5272,24 +5147,6 @@ __metadata: languageName: node linkType: hard -"simple-concat@npm:^1.0.0": - version: 1.0.1 - resolution: "simple-concat@npm:1.0.1" - checksum: 10c0/62f7508e674414008910b5397c1811941d457dfa0db4fd5aa7fa0409eb02c3609608dfcd7508cace75b3a0bf67a2a77990711e32cd213d2c76f4fd12ee86d776 - languageName: node - linkType: hard - -"simple-get@npm:^4.0.0": - version: 4.0.1 - resolution: "simple-get@npm:4.0.1" - dependencies: - decompress-response: "npm:^6.0.0" - once: "npm:^1.3.1" - simple-concat: "npm:^1.0.0" - checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0 - languageName: node - linkType: hard - "slice-ansi@npm:^7.1.0": version: 7.1.2 resolution: "slice-ansi@npm:7.1.2" @@ -5359,24 +5216,10 @@ __metadata: languageName: node linkType: hard -"sqlite3@npm:^6.0.1": - version: 6.0.1 - resolution: "sqlite3@npm:6.0.1" - dependencies: - bindings: "npm:^1.5.0" - node-addon-api: "npm:^8.0.0" - node-gyp: "npm:12.x" - prebuild-install: "npm:^7.1.3" - tar: "npm:^7.5.10" - peerDependencies: - node-gyp: 12.x - dependenciesMeta: - node-gyp: - optional: true - peerDependenciesMeta: - node-gyp: - optional: true - checksum: 10c0/519e1b84168e4404872d68b5261d73326dd6551bbf2a6b9d6318f3193efe5392ee2c1061a10e18f3d7dcbe6dc4c753513e3f83986d3bca703f363545eba2fb8e +"split2@npm:^4.1.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 languageName: node linkType: hard @@ -5512,15 +5355,6 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" - dependencies: - safe-buffer: "npm:~5.2.0" - checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d - languageName: node - linkType: hard - "strip-ansi@npm:^7.1.0": version: 7.1.2 resolution: "strip-ansi@npm:7.1.2" @@ -5537,13 +5371,6 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:~2.0.1": - version: 2.0.1 - resolution: "strip-json-comments@npm:2.0.1" - checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 - languageName: node - linkType: hard - "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -5576,44 +5403,6 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^2.0.0": - version: 2.1.4 - resolution: "tar-fs@npm:2.1.4" - dependencies: - chownr: "npm:^1.1.1" - mkdirp-classic: "npm:^0.5.2" - pump: "npm:^3.0.0" - tar-stream: "npm:^2.1.4" - checksum: 10c0/decb25acdc6839182c06ec83cba6136205bda1db984e120c8ffd0d80182bc5baa1d916f9b6c5c663ea3f9975b4dd49e3c6bb7b1707cbcdaba4e76042f43ec84c - languageName: node - linkType: hard - -"tar-stream@npm:^2.1.4": - version: 2.2.0 - resolution: "tar-stream@npm:2.2.0" - dependencies: - bl: "npm:^4.0.3" - end-of-stream: "npm:^1.4.1" - fs-constants: "npm:^1.0.0" - inherits: "npm:^2.0.3" - readable-stream: "npm:^3.1.1" - checksum: 10c0/2f4c910b3ee7196502e1ff015a7ba321ec6ea837667220d7bcb8d0852d51cb04b87f7ae471008a6fb8f5b1a1b5078f62f3a82d30c706f20ada1238ac797e7692 - languageName: node - linkType: hard - -"tar@npm:^7.5.10, tar@npm:^7.5.4": - version: 7.5.13 - resolution: "tar@npm:7.5.13" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.1.0" - yallist: "npm:^5.0.0" - checksum: 10c0/5c65b8084799bde7a791593a1c1a45d3d6ee98182e3700b24c247b7b8f8654df4191642abbdb07ff25043d45dcff35620827c3997b88ae6c12040f64bed5076b - languageName: node - linkType: hard - "tar@npm:^7.5.2": version: 7.5.7 resolution: "tar@npm:7.5.7" @@ -5771,15 +5560,6 @@ __metadata: languageName: node linkType: hard -"tunnel-agent@npm:^0.6.0": - version: 0.6.0 - resolution: "tunnel-agent@npm:0.6.0" - dependencies: - safe-buffer: "npm:^5.0.1" - checksum: 10c0/4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a - languageName: node - linkType: hard - "tunnel@npm:^0.0.6": version: 0.0.6 resolution: "tunnel@npm:0.0.6" @@ -5907,6 +5687,13 @@ __metadata: languageName: node linkType: hard +"underscore@npm:^1.13.1": + version: 1.13.8 + resolution: "underscore@npm:1.13.8" + checksum: 10c0/6677688daeda30484823e77c0b89ce4dcf29964a77d5a06f37299c007ab4bb1c66a0ff75e0d274620b62a1fe2a6ba29879f8214533ca611d71a1ae504f2bfc9b + languageName: node + linkType: hard + "undici-types@npm:~7.18.0": version: 7.18.2 resolution: "undici-types@npm:7.18.2" @@ -5981,13 +5768,6 @@ __metadata: languageName: node linkType: hard -"util-deprecate@npm:^1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 - languageName: node - linkType: hard - "uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -6329,6 +6109,13 @@ __metadata: languageName: node linkType: hard +"xtend@npm:^4.0.0": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" From 359a527930e4a29dfc940de061949ab80af31d1f Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Sun, 29 Mar 2026 09:16:52 +0200 Subject: [PATCH 10/20] different db port --- .env.example | 2 +- .github/workflows/ui-tests.yml | 4 ++-- README.md | 2 +- docker-compose.yml | 2 +- service/package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index f16fa76..d63ae07 100644 --- a/.env.example +++ b/.env.example @@ -1 +1 @@ -DATABASE_URL=postgres://stackcraft:stackcraft@localhost:5432/stackcraft +DATABASE_URL=postgres://stackcraft:stackcraft@localhost:5433/stackcraft diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 025626d..75dbe8b 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -18,7 +18,7 @@ jobs: POSTGRES_PASSWORD: stackcraft POSTGRES_DB: stackcraft ports: - - 5432:5432 + - 5433:5432 options: >- --health-cmd pg_isready --health-interval 10s @@ -26,7 +26,7 @@ jobs: --health-retries 5 env: - DATABASE_URL: postgres://stackcraft:stackcraft@localhost:5432/stackcraft + DATABASE_URL: postgres://stackcraft:stackcraft@localhost:5433/stackcraft steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index c8c91e6..29d57ca 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Example web app with common type API definitions, a FuryStack-based backend serv When running the Docker image, pass the `DATABASE_URL` environment variable: ```bash -docker run -e DATABASE_URL=postgres://user:password@host:5432/stackcraft furystack/stack-craft +docker run -e DATABASE_URL=postgres://user:password@host:5433/stackcraft furystack/stack-craft ``` # Testing diff --git a/docker-compose.yml b/docker-compose.yml index e32fbb5..946c2f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: POSTGRES_PASSWORD: stackcraft POSTGRES_DB: stackcraft ports: - - '5432:5432' + - '5433:5432' volumes: - pgdata:/var/lib/postgresql/data diff --git a/service/package.json b/service/package.json index 89958f0..35aeb12 100644 --- a/service/package.json +++ b/service/package.json @@ -6,7 +6,7 @@ "private": true, "type": "module", "scripts": { - "start": "yarn node --env-file-if-exists=.env ./dist/service.js", + "start": "yarn node --env-file-if-exists=../.env ./dist/service.js", "clean": "rimraf dist && rimraf tsconfig.tsbuildinfo", "build": "tsc -b" }, From 11abe599e3f34af71518c0b4fca595da33c9af2c Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Sun, 29 Mar 2026 09:24:44 +0200 Subject: [PATCH 11/20] cleaned up legacy websocket announcements, branch name sync --- common/schemas/entities.json | 26 +++- common/schemas/services-api.json | 64 ++++++---- common/src/index.ts | 1 - common/src/models/index.ts | 1 + common/src/models/service-git-status.ts | 11 ++ common/src/models/service-status.ts | 2 +- common/src/models/views.ts | 5 +- common/src/websocket/index.ts | 23 ---- frontend/src/services/websocket-service.ts | 82 ------------ .../app-models/data-store/setup-data-store.ts | 7 +- .../actions/service-checkout-action.spec.ts | 8 -- .../actions/service-checkout-action.ts | 18 --- .../services/setup-services-rest-api.ts | 17 ++- service/src/services/git-head-watcher.ts | 120 ++++++++++++++++++ service/src/services/git-watcher.ts | 9 -- service/src/services/process-manager.spec.ts | 9 +- service/src/services/process-manager.ts | 52 ++------ service/src/services/websocket-service.ts | 14 +- service/src/setup-entity-sync.ts | 2 + 19 files changed, 227 insertions(+), 244 deletions(-) create mode 100644 common/src/models/service-git-status.ts delete mode 100644 common/src/websocket/index.ts delete mode 100644 frontend/src/services/websocket-service.ts create mode 100644 service/src/services/git-head-watcher.ts diff --git a/common/schemas/entities.json b/common/schemas/entities.json index edc88b8..eff3391 100644 --- a/common/schemas/entities.json +++ b/common/schemas/entities.json @@ -216,6 +216,21 @@ "additionalProperties": false, "description": "Shareable service definition. Contains the immutable description of a service and its commands. Included in stack exports and shared between installations." }, + "ServiceGitStatus": { + "type": "object", + "properties": { + "serviceId": { + "type": "string", + "description": "FK to {@link import ('./service-definition.js').ServiceDefinition.id }" + }, + "currentBranch": { + "type": "string" + } + }, + "required": ["serviceId"], + "additionalProperties": false, + "description": "In-memory git state for a service. Derived from the filesystem (`.git/HEAD`), never persisted to DB." + }, "CloneStatus": { "type": "string", "enum": ["not-cloned", "cloning", "cloned", "failed"] @@ -251,9 +266,6 @@ "runStatus": { "$ref": "#/definitions/RunStatus" }, - "currentBranch": { - "type": "string" - }, "lastClonedAt": { "type": "string" }, @@ -335,6 +347,9 @@ "type": "string", "description": "FK to {@link ServiceDefinition.id }" }, + "currentBranch": { + "type": "string" + }, "cloneStatus": { "$ref": "#/definitions/CloneStatus" }, @@ -347,9 +362,6 @@ "runStatus": { "$ref": "#/definitions/RunStatus" }, - "currentBranch": { - "type": "string" - }, "lastClonedAt": { "type": "string" }, @@ -469,7 +481,7 @@ "stackName", "updatedAt" ], - "description": "Full service view combining definition, config, and status for API responses" + "description": "Full service view combining definition, config, status, and git state for API responses" }, "User": { "type": "object", diff --git a/common/schemas/services-api.json b/common/schemas/services-api.json index eb685e2..5e8e7b3 100644 --- a/common/schemas/services-api.json +++ b/common/schemas/services-api.json @@ -260,6 +260,9 @@ "type": "string", "description": "FK to {@link ServiceDefinition.id }" }, + "currentBranch": { + "type": "string" + }, "cloneStatus": { "$ref": "#/definitions/CloneStatus" }, @@ -272,9 +275,6 @@ "runStatus": { "$ref": "#/definitions/RunStatus" }, - "currentBranch": { - "type": "string" - }, "lastClonedAt": { "type": "string" }, @@ -394,7 +394,7 @@ "stackName", "updatedAt" ], - "description": "Full service view combining definition, config, and status for API responses" + "description": "Full service view combining definition, config, status, and git state for API responses" }, "CloneStatus": { "type": "string", @@ -1252,7 +1252,7 @@ "type": "object", "properties": { "findOptions": { - "$ref": "#/definitions/FindOptions%3CServiceView%2C(%22id%22%7C%22stackName%22%7C%22displayName%22%7C%22description%22%7C%22workingDirectory%22%7C%22repositoryId%22%7C%22prerequisiteIds%22%7C%22prerequisiteServiceIds%22%7C%22installCommand%22%7C%22buildCommand%22%7C%22runCommand%22%7C%22files%22%7C%22createdAt%22%7C%22updatedAt%22%7C%22serviceId%22%7C%22autoFetchEnabled%22%7C%22autoFetchIntervalMinutes%22%7C%22autoRestartOnFetch%22%7C%22environmentVariableOverrides%22%7C%22cloneStatus%22%7C%22installStatus%22%7C%22buildStatus%22%7C%22runStatus%22%7C%22currentBranch%22%7C%22lastClonedAt%22%7C%22lastInstalledAt%22%7C%22lastBuiltAt%22%7C%22lastStartedAt%22%7C%22lastFetchedAt%22)%5B%5D%3E" + "$ref": "#/definitions/FindOptions%3CServiceView%2C(%22id%22%7C%22stackName%22%7C%22displayName%22%7C%22description%22%7C%22workingDirectory%22%7C%22repositoryId%22%7C%22prerequisiteIds%22%7C%22prerequisiteServiceIds%22%7C%22installCommand%22%7C%22buildCommand%22%7C%22runCommand%22%7C%22files%22%7C%22createdAt%22%7C%22updatedAt%22%7C%22serviceId%22%7C%22autoFetchEnabled%22%7C%22autoFetchIntervalMinutes%22%7C%22autoRestartOnFetch%22%7C%22environmentVariableOverrides%22%7C%22cloneStatus%22%7C%22installStatus%22%7C%22buildStatus%22%7C%22runStatus%22%7C%22lastClonedAt%22%7C%22lastInstalledAt%22%7C%22lastBuiltAt%22%7C%22lastStartedAt%22%7C%22lastFetchedAt%22%7C%22currentBranch%22)%5B%5D%3E" } }, "additionalProperties": false @@ -1265,7 +1265,7 @@ "additionalProperties": false, "description": "Rest endpoint model for getting / querying collections" }, - "FindOptions": { + "FindOptions": { "type": "object", "properties": { "top": { @@ -1371,10 +1371,6 @@ "type": "string", "enum": ["ASC", "DESC"] }, - "currentBranch": { - "type": "string", - "enum": ["ASC", "DESC"] - }, "lastClonedAt": { "type": "string", "enum": ["ASC", "DESC"] @@ -1394,6 +1390,10 @@ "lastFetchedAt": { "type": "string", "enum": ["ASC", "DESC"] + }, + "currentBranch": { + "type": "string", + "enum": ["ASC", "DESC"] } }, "additionalProperties": false, @@ -1427,12 +1427,12 @@ "installStatus", "buildStatus", "runStatus", - "currentBranch", "lastClonedAt", "lastInstalledAt", "lastBuiltAt", "lastStartedAt", - "lastFetchedAt" + "lastFetchedAt", + "currentBranch" ] }, "description": "The result set will be limited to these fields" @@ -2241,19 +2241,23 @@ "type": "object", "properties": { "$startsWith": { - "type": "string", + "type": "object", + "properties": {}, "description": "FK to {@link ServiceDefinition.id }" }, "$endsWith": { - "type": "string", + "type": "object", + "properties": {}, "description": "FK to {@link ServiceDefinition.id }" }, "$like": { - "type": "string", + "type": "object", + "properties": {}, "description": "FK to {@link ServiceDefinition.id }" }, "$regex": { - "type": "string", + "type": "object", + "properties": {}, "description": "FK to {@link ServiceDefinition.id }" } }, @@ -2263,11 +2267,13 @@ "type": "object", "properties": { "$eq": { - "type": "string", + "type": "object", + "properties": {}, "description": "FK to {@link ServiceDefinition.id }" }, "$ne": { - "type": "string", + "type": "object", + "properties": {}, "description": "FK to {@link ServiceDefinition.id }" } }, @@ -2279,14 +2285,16 @@ "$in": { "type": "array", "items": { - "type": "string", + "type": "object", + "properties": {}, "description": "FK to {@link ServiceDefinition.id }" } }, "$nin": { "type": "array", "items": { - "type": "string", + "type": "object", + "properties": {}, "description": "FK to {@link ServiceDefinition.id }" } } @@ -2689,7 +2697,7 @@ } ] }, - "currentBranch": { + "lastClonedAt": { "anyOf": [ { "type": "object", @@ -2737,7 +2745,7 @@ } ] }, - "lastClonedAt": { + "lastInstalledAt": { "anyOf": [ { "type": "object", @@ -2785,7 +2793,7 @@ } ] }, - "lastInstalledAt": { + "lastBuiltAt": { "anyOf": [ { "type": "object", @@ -2833,7 +2841,7 @@ } ] }, - "lastBuiltAt": { + "lastStartedAt": { "anyOf": [ { "type": "object", @@ -2881,7 +2889,7 @@ } ] }, - "lastStartedAt": { + "lastFetchedAt": { "anyOf": [ { "type": "object", @@ -2929,7 +2937,7 @@ } ] }, - "lastFetchedAt": { + "currentBranch": { "anyOf": [ { "type": "object", @@ -3032,12 +3040,12 @@ "installStatus", "buildStatus", "runStatus", - "currentBranch", "lastClonedAt", "lastInstalledAt", "lastBuiltAt", "lastStartedAt", - "lastFetchedAt" + "lastFetchedAt", + "currentBranch" ] }, "description": "The list of fields to select" diff --git a/common/src/index.ts b/common/src/index.ts index ff71825..4f8d99c 100644 --- a/common/src/index.ts +++ b/common/src/index.ts @@ -1,4 +1,3 @@ export * from './models/index.js' export * from './apis/index.js' -export * from './websocket/index.js' export * from './utils/service-path-utils.js' diff --git a/common/src/models/index.ts b/common/src/models/index.ts index 0530dc7..6b9ced6 100644 --- a/common/src/models/index.ts +++ b/common/src/models/index.ts @@ -5,6 +5,7 @@ export * from './stack-config.js' export * from './service-definition.js' export * from './service-config.js' export * from './service-status.js' +export * from './service-git-status.js' export * from './service-state-history.js' export * from './service-log-entry.js' export * from './github-repository.js' diff --git a/common/src/models/service-git-status.ts b/common/src/models/service-git-status.ts new file mode 100644 index 0000000..15e0d58 --- /dev/null +++ b/common/src/models/service-git-status.ts @@ -0,0 +1,11 @@ +/** + * In-memory git state for a service. + * Derived from the filesystem (`.git/HEAD`), never persisted to DB. + * @see ServiceStatus for the persisted runtime status + */ +export class ServiceGitStatus { + /** FK to {@link import('./service-definition.js').ServiceDefinition.id} */ + serviceId!: string + + currentBranch?: string +} diff --git a/common/src/models/service-status.ts b/common/src/models/service-status.ts index 3935467..ed810f1 100644 --- a/common/src/models/service-status.ts +++ b/common/src/models/service-status.ts @@ -18,7 +18,7 @@ export class ServiceStatus { installStatus: InstallStatus = 'not-installed' buildStatus: BuildStatus = 'not-built' runStatus: RunStatus = 'stopped' - currentBranch?: string + lastClonedAt?: string lastInstalledAt?: string lastBuiltAt?: string diff --git a/common/src/models/views.ts b/common/src/models/views.ts index ab6a3b5..7e6a7f9 100644 --- a/common/src/models/views.ts +++ b/common/src/models/views.ts @@ -2,10 +2,11 @@ import type { StackConfig } from './stack-config.js' import type { StackDefinition } from './stack-definition.js' import type { ServiceConfig } from './service-config.js' import type { ServiceDefinition } from './service-definition.js' +import type { ServiceGitStatus } from './service-git-status.js' import type { ServiceStatus } from './service-status.js' /** Full stack view combining definition and config for API responses */ export type StackView = StackDefinition & StackConfig -/** Full service view combining definition, config, and status for API responses */ -export type ServiceView = ServiceDefinition & ServiceConfig & ServiceStatus +/** Full service view combining definition, config, status, and git state for API responses */ +export type ServiceView = ServiceDefinition & ServiceConfig & ServiceStatus & ServiceGitStatus diff --git a/common/src/websocket/index.ts b/common/src/websocket/index.ts deleted file mode 100644 index 2e56c80..0000000 --- a/common/src/websocket/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { CloneStatus, InstallStatus, BuildStatus, RunStatus } from '../models/service-status.js' - -export type WebsocketMessage = - | { - type: 'service-status-changed' - serviceId: string - cloneStatus: CloneStatus - installStatus: InstallStatus - buildStatus: BuildStatus - runStatus: RunStatus - currentBranch?: string - } - | { - type: 'git-branches-changed' - serviceId: string - newBranches: string[] - } - | { - type: 'dependency-check-result' - dependencyId: string - satisfied: boolean - output: string - } diff --git a/frontend/src/services/websocket-service.ts b/frontend/src/services/websocket-service.ts deleted file mode 100644 index 59d6808..0000000 --- a/frontend/src/services/websocket-service.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Injectable } from '@furystack/inject' -import { ObservableValue } from '@furystack/utils' -import type { WebsocketMessage } from 'common' -import { environmentOptions } from '../environment-options.js' - -type WebSocketEventHandler = (message: WebsocketMessage) => void - -@Injectable({ lifetime: 'singleton' }) -export class WebSocketService { - private ws: WebSocket | null = null - private listeners = new Set() - private reconnectTimer: ReturnType | null = null - private reconnectAttempt = 0 - - public connectionState = new ObservableValue<'connecting' | 'connected' | 'disconnected'>('disconnected') - - public connect() { - if (this.ws) return - - const serviceUrl = new URL(environmentOptions.serviceUrl) - const protocol = serviceUrl.protocol === 'https:' ? 'wss:' : 'ws:' - const url = `${protocol}//${serviceUrl.host}/api/ws` - - this.connectionState.setValue('connecting') - this.ws = new WebSocket(url) - - this.ws.onopen = () => { - this.reconnectAttempt = 0 - this.connectionState.setValue('connected') - } - - this.ws.onmessage = (event) => { - try { - const message = JSON.parse(event.data as string) as WebsocketMessage - this.listeners.forEach((listener) => listener(message)) - } catch { - // Ignore non-JSON messages - } - } - - this.ws.onclose = () => { - this.ws = null - this.connectionState.setValue('disconnected') - this.scheduleReconnect() - } - - this.ws.onerror = () => { - this.ws?.close() - } - } - - public disconnect() { - if (this.reconnectTimer) { - clearTimeout(this.reconnectTimer) - this.reconnectTimer = null - } - this.reconnectAttempt = 0 - this.ws?.close() - this.ws = null - this.connectionState.setValue('disconnected') - } - - public addListener(handler: WebSocketEventHandler) { - this.listeners.add(handler) - return { [Symbol.dispose]: () => this.listeners.delete(handler) } - } - - private scheduleReconnect() { - if (this.reconnectTimer) return - const delay = Math.min(1000 * 2 ** this.reconnectAttempt, 30_000) - this.reconnectAttempt++ - this.reconnectTimer = setTimeout(() => { - this.reconnectTimer = null - this.connect() - }, delay) - } - - public [Symbol.dispose]() { - this.disconnect() - this.connectionState[Symbol.dispose]() - } -} diff --git a/service/src/app-models/data-store/setup-data-store.ts b/service/src/app-models/data-store/setup-data-store.ts index f085687..0e90727 100644 --- a/service/src/app-models/data-store/setup-data-store.ts +++ b/service/src/app-models/data-store/setup-data-store.ts @@ -1,4 +1,5 @@ import type { Injector } from '@furystack/inject' +import { addStore, InMemoryStore } from '@furystack/core' import { getLogger } from '@furystack/logging' import { getRepository } from '@furystack/repository' import { useSequelize } from '@furystack/sequelize-store' @@ -10,6 +11,7 @@ import { PrerequisiteCheckResult, ServiceConfig, ServiceDefinition, + ServiceGitStatus, ServiceStateHistory, ServiceStatus, StackConfig, @@ -78,7 +80,6 @@ class ServiceStatusModel extends Model implements declare lastBuiltAt: string | undefined declare lastStartedAt: string | undefined declare lastFetchedAt: string | undefined - declare currentBranch: string | undefined declare updatedAt: string } @@ -324,7 +325,6 @@ async function initAllModels(sequelize: Sequelize): Promise { lastBuiltAt: { type: DataTypes.DATE, allowNull: true }, lastStartedAt: { type: DataTypes.DATE, allowNull: true }, lastFetchedAt: { type: DataTypes.DATE, allowNull: true }, - currentBranch: { type: DataTypes.STRING, allowNull: true }, updatedAt: { type: DataTypes.DATE }, }, { sequelize, createdAt: false }, @@ -522,5 +522,8 @@ export const setupDataStore = async (injector: Injector) => { getRepository(injector).createDataSet(ApiToken, 'id', { ...authorizedDataSet }) getRepository(injector).createDataSet(PrerequisiteCheckResult, 'prerequisiteId', { ...authorizedDataSet }) + addStore(injector, new InMemoryStore({ model: ServiceGitStatus, primaryKey: 'serviceId' })) + getRepository(injector).createDataSet(ServiceGitStatus, 'serviceId', { ...authorizedDataSet }) + await logger.information({ message: 'Data store initialized' }) } diff --git a/service/src/app-models/services/actions/service-checkout-action.spec.ts b/service/src/app-models/services/actions/service-checkout-action.spec.ts index a33af03..e1bb6c4 100644 --- a/service/src/app-models/services/actions/service-checkout-action.spec.ts +++ b/service/src/app-models/services/actions/service-checkout-action.spec.ts @@ -7,7 +7,6 @@ import { GitHubRepository, ServiceDefinition, ServiceStatus, StackConfig } from import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { GitService } from '../../../services/git-service.js' -import { ProcessManager } from '../../../services/process-manager.js' import { ServiceCheckoutAction } from './service-checkout-action.js' const createMockActionContext = (options: { @@ -26,7 +25,6 @@ const createMockActionContext = (options: { describe('ServiceCheckoutAction', () => { let injector: Injector let mockGit: { checkout: ReturnType; getCurrentBranch: ReturnType } - let mockPm: { updateBranch: ReturnType } beforeEach(async () => { injector = new Injector() @@ -48,11 +46,6 @@ describe('ServiceCheckoutAction', () => { getCurrentBranch: vi.fn().mockResolvedValue('dev'), } injector.setExplicitInstance(mockGit as unknown as GitService, GitService) - - mockPm = { - updateBranch: vi.fn().mockResolvedValue(undefined), - } - injector.setExplicitInstance(mockPm as unknown as ProcessManager, ProcessManager) }) afterEach(async () => { @@ -131,6 +124,5 @@ describe('ServiceCheckoutAction', () => { expect(body.success).toBe(true) expect(body.serviceId).toBe('svc-1') expect(mockGit.checkout).toHaveBeenCalledWith(expect.any(String), 'dev') - expect(mockPm.updateBranch).toHaveBeenCalledWith('svc-1', 'dev', expect.objectContaining({ triggerSource: 'api' })) }) }) diff --git a/service/src/app-models/services/actions/service-checkout-action.ts b/service/src/app-models/services/actions/service-checkout-action.ts index 1a7c0cf..5ee25fd 100644 --- a/service/src/app-models/services/actions/service-checkout-action.ts +++ b/service/src/app-models/services/actions/service-checkout-action.ts @@ -1,11 +1,9 @@ -import { getCurrentUser } from '@furystack/core' import { getRepository } from '@furystack/repository' import { RequestError } from '@furystack/rest' import { JsonResult, type RequestAction } from '@furystack/rest-service' import type { ServiceCheckoutEndpoint } from 'common' import { ServiceDefinition, ServiceStatus } from 'common' import { GitService } from '../../../services/git-service.js' -import { ProcessManager } from '../../../services/process-manager.js' import { resolveServiceCwd } from '../../../utils/resolve-service-cwd.js' export const ServiceCheckoutAction: RequestAction = async ({ @@ -33,7 +31,6 @@ export const ServiceCheckoutAction: RequestAction = asy const cwd = await resolveServiceCwd(injector, svc) const git = injector.getInstance(GitService) - const pm = injector.getInstance(ProcessManager) const localBranch = branch.replace(/^origin\//, '') @@ -44,20 +41,5 @@ export const ServiceCheckoutAction: RequestAction = asy throw new RequestError(`Failed to checkout branch "${localBranch}": ${message}`, 409) } - const currentBranch = await git.getCurrentBranch(cwd) - - let username = 'unknown' - try { - const { username: resolvedUsername } = (await getCurrentUser(injector)) ?? {} - if (resolvedUsername) username = resolvedUsername - } catch { - // Identity context may not be available - } - - await pm.updateBranch(serviceId, currentBranch, { - triggeredBy: username, - triggerSource: 'api', - }) - return JsonResult({ success: true, serviceId }) } diff --git a/service/src/app-models/services/setup-services-rest-api.ts b/service/src/app-models/services/setup-services-rest-api.ts index 8011d9a..feb4a98 100644 --- a/service/src/app-models/services/setup-services-rest-api.ts +++ b/service/src/app-models/services/setup-services-rest-api.ts @@ -4,7 +4,7 @@ import { getRepository } from '@furystack/repository' import { RequestError } from '@furystack/rest' import { JsonResult, useRestService, Validate } from '@furystack/rest-service' import type { ServicesApi, ServiceView } from 'common' -import { ServiceConfig, ServiceDefinition, ServiceStatus } from 'common' +import { ServiceConfig, ServiceDefinition, ServiceGitStatus, ServiceStatus } from 'common' import servicesApiSchema from 'common/schemas/services-api.json' with { type: 'json' } import { randomUUID } from 'crypto' @@ -22,6 +22,7 @@ const mergeServiceView = ( def: ServiceDefinition, config: ServiceConfig | undefined, status: ServiceStatus | undefined, + gitStatus: ServiceGitStatus | undefined, ): ServiceView => ({ serviceId: def.id, autoFetchEnabled: false, @@ -35,6 +36,7 @@ const mergeServiceView = ( ...def, ...(config ?? {}), ...(status ?? {}), + ...(gitStatus ?? {}), }) export const setupServicesRestApi = async (injector: Injector) => { @@ -57,10 +59,14 @@ export const setupServicesRestApi = async (injector: Injector) => { }) const configs = await repo.getDataSetFor(ServiceConfig, 'serviceId').find(i, {}) const statuses = await repo.getDataSetFor(ServiceStatus, 'serviceId').find(i, {}) + const gitStatuses = await repo.getDataSetFor(ServiceGitStatus, 'serviceId').find(i, {}) const configMap = new Map(configs.map((c) => [c.serviceId, c])) const statusMap = new Map(statuses.map((s) => [s.serviceId, s])) + const gitStatusMap = new Map(gitStatuses.map((g) => [g.serviceId, g])) - const entries = defs.map((def) => mergeServiceView(def, configMap.get(def.id), statusMap.get(def.id))) + const entries = defs.map((def) => + mergeServiceView(def, configMap.get(def.id), statusMap.get(def.id), gitStatusMap.get(def.id)), + ) const count = await repo.getDataSetFor(ServiceDefinition, 'id').count(i, query.findOptions?.filter) return JsonResult({ count, entries }) }, @@ -81,8 +87,11 @@ export const setupServicesRestApi = async (injector: Injector) => { const statuses = await repo .getDataSetFor(ServiceStatus, 'serviceId') .find(i, { filter: { serviceId: { $eq: id } }, top: 1 }) + const gitStatuses = await repo + .getDataSetFor(ServiceGitStatus, 'serviceId') + .find(i, { filter: { serviceId: { $eq: id } }, top: 1 }) - return JsonResult(mergeServiceView(def, configs[0], statuses[0])) + return JsonResult(mergeServiceView(def, configs[0], statuses[0], gitStatuses[0])) }, ), '/services/:id/logs': Validate({ schema: servicesApiSchema, schemaName: 'ServiceLogsEndpoint' })( @@ -143,7 +152,7 @@ export const setupServicesRestApi = async (injector: Injector) => { await repo.getDataSetFor(ServiceConfig, 'serviceId').add(i, config) await repo.getDataSetFor(ServiceStatus, 'serviceId').add(i, status) - return JsonResult(mergeServiceView(def, config, status)) + return JsonResult(mergeServiceView(def, config, status, undefined)) }, ), '/services/:id/start': Validate({ schema: servicesApiSchema, schemaName: 'ServiceActionEndpoint' })( diff --git a/service/src/services/git-head-watcher.ts b/service/src/services/git-head-watcher.ts new file mode 100644 index 0000000..a57148b --- /dev/null +++ b/service/src/services/git-head-watcher.ts @@ -0,0 +1,120 @@ +import { useSystemIdentityContext } from '@furystack/core' +import { Injectable, Injected, type Injector, getInjectorReference } from '@furystack/inject' +import { getLogger } from '@furystack/logging' +import { getRepository } from '@furystack/repository' +import { ServiceGitStatus } from 'common' +import { existsSync } from 'fs' +import { watch, type FSWatcher } from 'fs' +import { join } from 'path' + +import { GitService } from './git-service.js' + +type WatchedEntry = { + cwd: string + watcher: FSWatcher + debounceTimer?: ReturnType +} + +const DEBOUNCE_MS = 200 + +@Injectable({ lifetime: 'singleton' }) +export class GitHeadWatcher { + private watchers = new Map() + private elevatedInjector?: Injector + + private getElevatedInjector(): Injector { + if (!this.elevatedInjector) { + this.elevatedInjector = useSystemIdentityContext({ injector: getInjectorReference(this) }) + } + return this.elevatedInjector + } + + @Injected((injector) => getLogger(injector).withScope('GitHeadWatcher')) + declare private logger: ReturnType['withScope']> + + @Injected(GitService) + declare private git: GitService + + /** + * Starts watching `.git/HEAD` for a cloned service. + * Reads the current branch immediately and writes it to the in-memory store. + */ + public async watch(serviceId: string, cwd: string): Promise { + this.unwatch(serviceId) + + const headPath = join(cwd, '.git', 'HEAD') + if (!existsSync(headPath)) return + + const branch = await this.readBranch(serviceId, cwd) + await this.upsertGitStatus(serviceId, branch) + + try { + const watcher = watch(headPath, () => { + const entry = this.watchers.get(serviceId) + if (!entry) return + if (entry.debounceTimer) clearTimeout(entry.debounceTimer) + entry.debounceTimer = setTimeout(() => { + void this.onHeadChanged(serviceId, cwd) + }, DEBOUNCE_MS) + }) + + this.watchers.set(serviceId, { cwd, watcher }) + } catch { + void this.logger.verbose({ message: `Could not watch .git/HEAD for service ${serviceId}` }) + } + } + + public unwatch(serviceId: string): void { + const entry = this.watchers.get(serviceId) + if (entry) { + if (entry.debounceTimer) clearTimeout(entry.debounceTimer) + entry.watcher.close() + this.watchers.delete(serviceId) + } + } + + private async onHeadChanged(serviceId: string, cwd: string): Promise { + const branch = await this.readBranch(serviceId, cwd) + await this.upsertGitStatus(serviceId, branch) + } + + private async readBranch(serviceId: string, cwd: string): Promise { + try { + return await this.git.getCurrentBranch(cwd) + } catch { + void this.logger.verbose({ message: `Could not read branch for service ${serviceId}` }) + return undefined + } + } + + private async upsertGitStatus(serviceId: string, currentBranch: string | undefined): Promise { + try { + const elevated = this.getElevatedInjector() + const ds = getRepository(elevated).getDataSetFor(ServiceGitStatus, 'serviceId') + const existing = await ds.find(elevated, { filter: { serviceId: { $eq: serviceId } }, top: 1 }) + + if (existing.length > 0) { + await ds.update(elevated, serviceId, { currentBranch }) + } else { + await ds.add(elevated, { serviceId, currentBranch }) + } + } catch (error) { + void this.logger.verbose({ + message: `Failed to update git status for ${serviceId}: ${(error as Error).message}`, + }) + } + } + + public async [Symbol.asyncDispose]() { + for (const [, entry] of this.watchers) { + if (entry.debounceTimer) clearTimeout(entry.debounceTimer) + entry.watcher.close() + } + this.watchers.clear() + try { + await this.elevatedInjector?.[Symbol.asyncDispose]() + } catch { + // May already be disposed by the parent injector + } + } +} diff --git a/service/src/services/git-watcher.ts b/service/src/services/git-watcher.ts index 42ad10b..557d195 100644 --- a/service/src/services/git-watcher.ts +++ b/service/src/services/git-watcher.ts @@ -7,7 +7,6 @@ import { useSystemIdentityContext } from '@furystack/core' import { resolveServiceCwd } from '../utils/resolve-service-cwd.js' import { GitService } from './git-service.js' import { ProcessManager } from './process-manager.js' -import { WebsocketService } from './websocket-service.js' type WatchEntry = { serviceId: string @@ -34,9 +33,6 @@ export class GitWatcher { @Injected(GitService) declare private git: GitService - @Injected(WebsocketService) - declare private ws: WebsocketService - @Injected(ProcessManager) declare private pm: ProcessManager @@ -112,11 +108,6 @@ export class GitWatcher { message: `New branches detected for ${svc.displayName}: ${newBranches.join(', ')}`, }) entry.lastBranches = new Set(remote) - void this.ws.announce({ - type: 'git-branches-changed', - serviceId, - newBranches, - }) } if (config?.autoRestartOnFetch) { diff --git a/service/src/services/process-manager.spec.ts b/service/src/services/process-manager.spec.ts index e2bef6a..c9bdc48 100644 --- a/service/src/services/process-manager.spec.ts +++ b/service/src/services/process-manager.spec.ts @@ -7,6 +7,7 @@ import { Prerequisite, ServiceConfig, ServiceDefinition, + ServiceGitStatus, ServiceLogEntry, ServiceStateHistory, ServiceStatus, @@ -17,9 +18,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { TriggerSource } from 'common' +import { GitHeadWatcher } from './git-head-watcher.js' import { LogStorageService } from './log-storage-service.js' import { ProcessManager } from './process-manager.js' -import { WebsocketService } from './websocket-service.js' const testTrigger = { triggeredBy: 'test', triggerSource: 'api' as TriggerSource } @@ -225,18 +226,20 @@ describe('ProcessManager', () => { addStore(injector, new InMemoryStore({ model: Prerequisite, primaryKey: 'id' })) addStore(injector, new InMemoryStore({ model: ServiceLogEntry, primaryKey: 'id' })) addStore(injector, new InMemoryStore({ model: ServiceStateHistory, primaryKey: 'id' })) + addStore(injector, new InMemoryStore({ model: ServiceGitStatus, primaryKey: 'serviceId' })) getRepository(injector).createDataSet(ServiceDefinition, 'id', {}) getRepository(injector).createDataSet(ServiceConfig, 'serviceId', {}) getRepository(injector).createDataSet(ServiceStatus, 'serviceId', {}) + getRepository(injector).createDataSet(ServiceGitStatus, 'serviceId', {}) getRepository(injector).createDataSet(StackConfig, 'stackName', {}) getRepository(injector).createDataSet(GitHubRepository, 'id', {}) getRepository(injector).createDataSet(Prerequisite, 'id', {}) getRepository(injector).createDataSet(ServiceLogEntry, 'id', {}) getRepository(injector).createDataSet(ServiceStateHistory, 'id', {}) - const mockWs = { announce: vi.fn().mockResolvedValue(undefined) } - injector.setExplicitInstance(mockWs as unknown as WebsocketService, WebsocketService) + const mockGitHeadWatcher = { watch: vi.fn().mockResolvedValue(undefined), unwatch: vi.fn() } + injector.setExplicitInstance(mockGitHeadWatcher as unknown as GitHeadWatcher, GitHeadWatcher) mockLogStorage = { addEntry: vi.fn().mockResolvedValue(undefined), diff --git a/service/src/services/process-manager.ts b/service/src/services/process-manager.ts index ab6d0d8..143ad5e 100644 --- a/service/src/services/process-manager.ts +++ b/service/src/services/process-manager.ts @@ -21,9 +21,9 @@ import { useSystemIdentityContext } from '@furystack/core' import { applyServiceFiles } from '../utils/apply-service-files.js' import { resolvePath } from '../utils/resolve-path.js' import { resolveServiceCwd } from '../utils/resolve-service-cwd.js' +import { GitHeadWatcher } from './git-head-watcher.js' import { GitService } from './git-service.js' import { LogStorageService } from './log-storage-service.js' -import { WebsocketService } from './websocket-service.js' const MAX_HISTORY_PER_SERVICE = 10_000 const HISTORY_PRUNE_CHECK_INTERVAL = 100 @@ -60,8 +60,8 @@ export class ProcessManager { @Injected(LogStorageService) declare private logStorage: LogStorageService - @Injected(WebsocketService) - declare private ws: WebsocketService + @Injected(GitHeadWatcher) + declare private gitHeadWatcher: GitHeadWatcher private logBuffer: Array<{ serviceId: string; processUid: string; stream: 'stdout' | 'stderr'; line: string }> = [] private flushTimer: ReturnType | null = null @@ -149,16 +149,6 @@ export class ProcessManager { void this.pruneHistory(serviceId, elevated) } } - - void this.ws.announce({ - type: 'service-status-changed', - serviceId, - cloneStatus: update.cloneStatus ?? current.cloneStatus, - installStatus: update.installStatus ?? current.installStatus, - buildStatus: update.buildStatus ?? current.buildStatus, - runStatus: update.runStatus ?? current.runStatus, - currentBranch: current.currentBranch, - }) } catch { // May fire after disposal during shutdown — safe to ignore } @@ -377,14 +367,14 @@ export class ProcessManager { mkdirSync(dirname(cwd), { recursive: true }) await git.clone(repo.url, cwd) await this.updateServiceStatus(serviceId, { cloneStatus: 'cloned' }, 'clone-completed', trigger) - await this.refreshCurrentBranch(serviceId, cwd) + await this.gitHeadWatcher.watch(serviceId, cwd) this.applySharedFiles(svc, cwd) return { cloned: true, pulled: false, updated: true } } else if (isGitRepo) { await this.logger.information({ message: `Pulling in ${cwd}` }) const { updated } = await git.pull(cwd) await this.updateServiceStatus(serviceId, { cloneStatus: 'cloned' }, 'clone-completed', trigger) - await this.refreshCurrentBranch(serviceId, cwd) + await this.gitHeadWatcher.watch(serviceId, cwd) this.applySharedFiles(svc, cwd) return { cloned: false, pulled: true, updated } } else { @@ -398,7 +388,7 @@ export class ProcessManager { mkdirSync(dirname(cwd), { recursive: true }) await git.clone(repo.url, cwd) await this.updateServiceStatus(serviceId, { cloneStatus: 'cloned' }, 'clone-completed', trigger) - await this.refreshCurrentBranch(serviceId, cwd) + await this.gitHeadWatcher.watch(serviceId, cwd) this.applySharedFiles(svc, cwd) return { cloned: true, pulled: false, updated: true } } @@ -410,30 +400,6 @@ export class ProcessManager { } } - /** - * Updates the currentBranch field on ServiceStatus. - */ - public async updateBranch(serviceId: string, branch: string, _trigger: TriggerContext): Promise { - const elevated = this.getElevatedInjector() - const statusDs = getRepository(elevated).getDataSetFor(ServiceStatus, 'serviceId') - await statusDs.update(elevated, serviceId, { currentBranch: branch, updatedAt: new Date().toISOString() }) - } - - private async refreshCurrentBranch(serviceId: string, cwd: string): Promise { - try { - const git = getInjectorReference(this).getInstance(GitService) - const branch = await git.getCurrentBranch(cwd) - const elevated = this.getElevatedInjector() - await getRepository(elevated) - .getDataSetFor(ServiceStatus, 'serviceId') - .update(elevated, serviceId, { currentBranch: branch }) - } catch (error) { - void this.logger.verbose({ - message: `Failed to refresh branch for ${serviceId}: ${(error as Error).message}`, - }) - } - } - private applySharedFiles(svc: ServiceDefinition, cwd: string): void { const files = svc.files ?? [] if (files.length === 0) return @@ -909,7 +875,7 @@ export class ProcessManager { await this.updateServiceStatus(status.serviceId, update, 'state-reconciled', reconcileTrigger, staleMetadata) } - if (status.cloneStatus === 'cloned' && !status.currentBranch) { + if (status.cloneStatus === 'cloned') { try { const services = await getRepository(elevated) .getDataSetFor(ServiceDefinition, 'id') @@ -917,11 +883,11 @@ export class ProcessManager { const svc = services[0] if (svc) { const cwd = await resolveServiceCwd(getInjectorReference(this), svc, elevated) - await this.refreshCurrentBranch(status.serviceId, cwd) + await this.gitHeadWatcher.watch(status.serviceId, cwd) } } catch (error) { await this.logger.verbose({ - message: `Could not refresh branch for service ${status.serviceId}: ${(error as Error).message}`, + message: `Could not start git HEAD watcher for service ${status.serviceId}: ${(error as Error).message}`, }) } } diff --git a/service/src/services/websocket-service.ts b/service/src/services/websocket-service.ts index 6f680e7..d88e74d 100644 --- a/service/src/services/websocket-service.ts +++ b/service/src/services/websocket-service.ts @@ -1,28 +1,16 @@ import type { Injector } from '@furystack/inject' import { Injectable } from '@furystack/inject' import { SyncSubscribeAction, SyncUnsubscribeAction } from '@furystack/entity-sync-service' -import { useWebsockets, WebSocketApi } from '@furystack/websocket-api' -import type { WebsocketMessage } from 'common' +import { useWebsockets } from '@furystack/websocket-api' import { getPort } from '../get-port.js' @Injectable({ lifetime: 'singleton' }) export class WebsocketService { - private webSocketApi: WebSocketApi | null = null - public async init(injector: Injector) { await useWebsockets(injector, { port: getPort(), path: '/api/ws', actions: [SyncSubscribeAction, SyncUnsubscribeAction], }) - this.webSocketApi = injector.getInstance(WebSocketApi) - } - - public announce = async (message: WebsocketMessage) => { - if (!this.webSocketApi) return - const data = JSON.stringify(message) - await this.webSocketApi.broadcast(({ ws }: { ws: { send: (data: string) => void } }) => { - ws.send(data) - }) } } diff --git a/service/src/setup-entity-sync.ts b/service/src/setup-entity-sync.ts index 2d80588..d285d86 100644 --- a/service/src/setup-entity-sync.ts +++ b/service/src/setup-entity-sync.ts @@ -7,6 +7,7 @@ import { PublicApiToken, ServiceConfig, ServiceDefinition, + ServiceGitStatus, ServiceLogEntry, ServiceStateHistory, ServiceStatus, @@ -23,6 +24,7 @@ export const setupEntitySync = (injector: Injector) => { { model: ServiceDefinition, primaryKey: 'id' }, { model: ServiceConfig, primaryKey: 'serviceId' }, { model: ServiceStatus, primaryKey: 'serviceId' }, + { model: ServiceGitStatus, primaryKey: 'serviceId' }, { model: GitHubRepository, primaryKey: 'id' }, { model: Prerequisite, primaryKey: 'id' }, { model: PrerequisiteCheckResult, primaryKey: 'prerequisiteId' }, From 0ead660fac72a56d95341cbced00abaca324bf82 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Sun, 29 Mar 2026 11:01:43 +0200 Subject: [PATCH 12/20] "commits behind" implementation --- common/schemas/entities.json | 10 ++- common/schemas/services-api.json | 70 ++++++++++++++++- common/src/models/service-git-status.ts | 5 +- .../components/service-pipeline-stepper.tsx | 78 +++++++++++++++++-- frontend/src/components/service-table.tsx | 63 ++++++++++----- .../src/pages/services/service-detail.tsx | 1 + frontend/src/pages/services/services-list.tsx | 10 ++- frontend/src/utils/service-pipeline.ts | 25 +++++- service/src/services/git-head-watcher.ts | 13 +++- service/src/services/git-service.ts | 12 +++ service/src/services/git-watcher.ts | 47 ++++++----- service/src/services/process-manager.ts | 10 ++- 12 files changed, 281 insertions(+), 63 deletions(-) diff --git a/common/schemas/entities.json b/common/schemas/entities.json index eff3391..62907ba 100644 --- a/common/schemas/entities.json +++ b/common/schemas/entities.json @@ -225,11 +225,15 @@ }, "currentBranch": { "type": "string" + }, + "commitsBehind": { + "type": "number", + "description": "Number of commits the local branch is behind `origin/`. Updated by GitWatcher after each fetch." } }, "required": ["serviceId"], "additionalProperties": false, - "description": "In-memory git state for a service. Derived from the filesystem (`.git/HEAD`), never persisted to DB." + "description": "In-memory git state for a service. Derived from the filesystem (`.git/HEAD`) and periodic remote checks, never persisted to DB." }, "CloneStatus": { "type": "string", @@ -350,6 +354,10 @@ "currentBranch": { "type": "string" }, + "commitsBehind": { + "type": "number", + "description": "Number of commits the local branch is behind `origin/`. Updated by GitWatcher after each fetch." + }, "cloneStatus": { "$ref": "#/definitions/CloneStatus" }, diff --git a/common/schemas/services-api.json b/common/schemas/services-api.json index 5e8e7b3..340700c 100644 --- a/common/schemas/services-api.json +++ b/common/schemas/services-api.json @@ -263,6 +263,10 @@ "currentBranch": { "type": "string" }, + "commitsBehind": { + "type": "number", + "description": "Number of commits the local branch is behind `origin/`. Updated by GitWatcher after each fetch." + }, "cloneStatus": { "$ref": "#/definitions/CloneStatus" }, @@ -1252,7 +1256,7 @@ "type": "object", "properties": { "findOptions": { - "$ref": "#/definitions/FindOptions%3CServiceView%2C(%22id%22%7C%22stackName%22%7C%22displayName%22%7C%22description%22%7C%22workingDirectory%22%7C%22repositoryId%22%7C%22prerequisiteIds%22%7C%22prerequisiteServiceIds%22%7C%22installCommand%22%7C%22buildCommand%22%7C%22runCommand%22%7C%22files%22%7C%22createdAt%22%7C%22updatedAt%22%7C%22serviceId%22%7C%22autoFetchEnabled%22%7C%22autoFetchIntervalMinutes%22%7C%22autoRestartOnFetch%22%7C%22environmentVariableOverrides%22%7C%22cloneStatus%22%7C%22installStatus%22%7C%22buildStatus%22%7C%22runStatus%22%7C%22lastClonedAt%22%7C%22lastInstalledAt%22%7C%22lastBuiltAt%22%7C%22lastStartedAt%22%7C%22lastFetchedAt%22%7C%22currentBranch%22)%5B%5D%3E" + "$ref": "#/definitions/FindOptions%3CServiceView%2C(%22id%22%7C%22stackName%22%7C%22displayName%22%7C%22description%22%7C%22workingDirectory%22%7C%22repositoryId%22%7C%22prerequisiteIds%22%7C%22prerequisiteServiceIds%22%7C%22installCommand%22%7C%22buildCommand%22%7C%22runCommand%22%7C%22files%22%7C%22createdAt%22%7C%22updatedAt%22%7C%22serviceId%22%7C%22autoFetchEnabled%22%7C%22autoFetchIntervalMinutes%22%7C%22autoRestartOnFetch%22%7C%22environmentVariableOverrides%22%7C%22cloneStatus%22%7C%22installStatus%22%7C%22buildStatus%22%7C%22runStatus%22%7C%22lastClonedAt%22%7C%22lastInstalledAt%22%7C%22lastBuiltAt%22%7C%22lastStartedAt%22%7C%22lastFetchedAt%22%7C%22currentBranch%22%7C%22commitsBehind%22)%5B%5D%3E" } }, "additionalProperties": false @@ -1265,7 +1269,7 @@ "additionalProperties": false, "description": "Rest endpoint model for getting / querying collections" }, - "FindOptions": { + "FindOptions": { "type": "object", "properties": { "top": { @@ -1394,6 +1398,10 @@ "currentBranch": { "type": "string", "enum": ["ASC", "DESC"] + }, + "commitsBehind": { + "type": "string", + "enum": ["ASC", "DESC"] } }, "additionalProperties": false, @@ -1432,7 +1440,8 @@ "lastBuiltAt", "lastStartedAt", "lastFetchedAt", - "currentBranch" + "currentBranch", + "commitsBehind" ] }, "description": "The result set will be limited to these fields" @@ -2984,6 +2993,58 @@ "additionalProperties": false } ] + }, + "commitsBehind": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "number", + "description": "Number of commits the local branch is behind `origin/`. Updated by GitWatcher after each fetch." + }, + "$ne": { + "type": "number", + "description": "Number of commits the local branch is behind `origin/`. Updated by GitWatcher after each fetch." + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "not": {} + } + ], + "description": "Number of commits the local branch is behind `origin/`. Updated by GitWatcher after each fetch." + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "not": {} + } + ], + "description": "Number of commits the local branch is behind `origin/`. Updated by GitWatcher after each fetch." + } + } + }, + "additionalProperties": false + } + ] } } }, @@ -3045,7 +3106,8 @@ "lastBuiltAt", "lastStartedAt", "lastFetchedAt", - "currentBranch" + "currentBranch", + "commitsBehind" ] }, "description": "The list of fields to select" diff --git a/common/src/models/service-git-status.ts b/common/src/models/service-git-status.ts index 15e0d58..5c3efa6 100644 --- a/common/src/models/service-git-status.ts +++ b/common/src/models/service-git-status.ts @@ -1,6 +1,6 @@ /** * In-memory git state for a service. - * Derived from the filesystem (`.git/HEAD`), never persisted to DB. + * Derived from the filesystem (`.git/HEAD`) and periodic remote checks, never persisted to DB. * @see ServiceStatus for the persisted runtime status */ export class ServiceGitStatus { @@ -8,4 +8,7 @@ export class ServiceGitStatus { serviceId!: string currentBranch?: string + + /** Number of commits the local branch is behind `origin/`. Updated by GitWatcher after each fetch. */ + commitsBehind?: number } diff --git a/frontend/src/components/service-pipeline-stepper.tsx b/frontend/src/components/service-pipeline-stepper.tsx index 139df7d..34da0a9 100644 --- a/frontend/src/components/service-pipeline-stepper.tsx +++ b/frontend/src/components/service-pipeline-stepper.tsx @@ -33,6 +33,37 @@ const statusDots: Record = { failed: '✗', } +const stageActionTooltips: Record> = { + clone: { + skipped: null, + pending: 'Clone the repository', + 'in-progress': null, + done: 'Pull latest changes from remote', + failed: 'Retry cloning the repository', + }, + install: { + skipped: null, + pending: 'Run install command', + 'in-progress': null, + done: 'Reinstall dependencies', + failed: 'Retry install', + }, + build: { + skipped: null, + pending: 'Run build command', + 'in-progress': null, + done: 'Rebuild the service', + failed: 'Retry build', + }, + run: { + skipped: null, + pending: 'Start the service', + 'in-progress': null, + done: null, + failed: 'Start the service', + }, +} + export const ServicePipelineStepper = Shade({ customElementName: 'shade-service-pipeline-stepper', render: ({ props }) => { @@ -40,12 +71,14 @@ export const ServicePipelineStepper = Shade({ const visibleStages = stages.filter((s) => s.status !== 'skipped') const hasInProgress = visibleStages.some((s) => s.status === 'in-progress') + const commitsBehind = props.service.commitsBehind return ( {visibleStages.map((stage) => { const color = getStageColor(stage.status) const actionLabel = stageActionLabels[stage.id]?.[stage.status] ?? null + const actionTooltip = stageActionTooltips[stage.id]?.[stage.status] ?? undefined const dot = ( {statusDots[stage.status]} ) @@ -55,6 +88,8 @@ export const ServicePipelineStepper = Shade({ return stageApiActions[stage.id] } + const showBadge = stage.id === 'clone' && stage.status === 'done' && commitsBehind && commitsBehind > 0 + return (
@@ -76,14 +111,41 @@ export const ServicePipelineStepper = Shade({ ) : null}
{actionLabel && props.onAction ? ( - +
+ + {showBadge ? ( + + {commitsBehind} + + ) : null} +
) : null} {(stage.status === 'failed' || stage.status === 'done') && stage.id !== 'clone' && diff --git a/frontend/src/components/service-table.tsx b/frontend/src/components/service-table.tsx index 3d39214..16f03fa 100644 --- a/frontend/src/components/service-table.tsx +++ b/frontend/src/components/service-table.tsx @@ -21,6 +21,7 @@ import { ServicesApiClient } from '../services/api-clients/services-api-client.j import { applyClientFindOptions } from '../utils/apply-client-find-options.js' import { getPrimaryAction } from '../utils/service-pipeline.js' import { StackCraftNestedRouteLink } from './app-routes.js' +import { BranchSelector } from './branch-selector.js' import { MiniPipelineDots } from './mini-pipeline-dots.js' type ServiceTableProps = { @@ -134,15 +135,13 @@ export const ServiceTable = Shade({ }, pipeline: (entry) => , branch: (entry) => ( - - {entry.currentBranch ?? '—'} - +
e.stopPropagation()}> + +
), actions: (entry) => { const primary = getPrimaryAction(entry) @@ -182,17 +181,43 @@ export const ServiceTable = Shade({ startIcon={} /> ) : null} - {/* Update */} + {/* Update (pull + install + build + restart if running) */} {entry.repositoryId && entry.cloneStatus === 'cloned' ? ( -
) : null} {/* Logs */} ({ variant="outlined" size="small" color={action.color === 'secondary' ? undefined : action.color} + title={action.tooltip} loading={actionInProgress === action.label} disabled={!!actionInProgress} onclick={() => void runAction(action.label, action.apiAction)} diff --git a/frontend/src/pages/services/services-list.tsx b/frontend/src/pages/services/services-list.tsx index f67d9f2..1ea7b39 100644 --- a/frontend/src/pages/services/services-list.tsx +++ b/frontend/src/pages/services/services-list.tsx @@ -10,7 +10,7 @@ import { PageHeader, } from '@furystack/shades-common-components' import type { ServiceView } from 'common' -import { ServiceConfig, ServiceDefinition, ServiceStatus } from 'common' +import { ServiceConfig, ServiceDefinition, ServiceGitStatus, ServiceStatus } from 'common' import { StackCraftNestedRouteLink } from '../../components/app-routes.js' import { ServiceTable } from '../../components/service-table.js' @@ -39,8 +39,13 @@ export const ServicesList = Shade({ const configs = configsState.status === 'synced' || configsState.status === 'cached' ? configsState.data.entries : [] + const gitStatusState = useCollectionSync(options, ServiceGitStatus, {}) + const gitStatuses = + gitStatusState.status === 'synced' || gitStatusState.status === 'cached' ? gitStatusState.data.entries : [] + const statusMap = new Map(statuses.map((s) => [s.serviceId, s])) const configMap = new Map(configs.map((c) => [c.serviceId, c])) + const gitStatusMap = new Map(gitStatuses.map((g) => [g.serviceId, g])) const services: ServiceView[] = defs.map((def) => ({ serviceId: def.id, @@ -55,6 +60,7 @@ export const ServicesList = Shade({ ...def, ...(configMap.get(def.id) ?? {}), ...(statusMap.get(def.id) ?? {}), + ...(gitStatusMap.get(def.id) ?? {}), })) const isLoading = servicesState.status === 'connecting' @@ -126,6 +132,7 @@ export const ServicesList = Shade({
+
+
+

Local Files

+ +
+

+ Per-installation secret files. Encrypted at rest and never included in stack exports. +

+ {localFiles.length > 0 ? ( +
+ {localFiles.map((file, index) => { + const isOverridingShared = sharedFiles.some((sf) => sf.relativePath === file.relativePath) + return ( +
+
+ { + const updated = [...localFiles] + updated[index] = { ...updated[index], relativePath: (ev.target as HTMLInputElement).value } + setLocalFiles(updated) + }} + style={{ + flex: '1', + padding: '6px 10px', + borderRadius: '4px', + border: `1px solid ${cssVariableTheme.divider}`, + background: 'transparent', + color: 'inherit', + fontFamily: 'monospace', + fontSize: cssVariableTheme.typography.fontSize.sm, + }} + /> + {isOverridingShared ? ( + + Overrides shared + + ) : null} + +
+

T`7OQhubE%d3qQv^M=$&=^DMpaub5xa3qQj=LofVG=9l!sKV*JLFZ^@n z=k&rqV}3?2{1o#Pz3@+&pVA9I$vjCf`~>p^z3@+%pU?|G&OA;p{222Xz3`8jAJYpz z$~;Oh{3GT^^unKDK0z;hEpsh)9?zlk2mi|am0t5J%q#T5e_{SYFZ?p|GQIGhnLpDD zKhHeR=l>h)zN@pen-8L!8-HtJjT`H;_=Erb(b6}N>Pg#4B-N|pNNS;0A|JIZ4yKX< zQ{2tT7nkBO#-QcfRaV;;j!yY${~lcVwkH+|$sHDVN*v_Yv`zgnR8mkO@_Bb32y&6` z10%7xKm=c#4#nWu@BmK2PhC!adx=^3u5{GEBi|d#oT%3G^oqUl#&YTPXE=vB|aW<+3MHbU8-G>i?u2^Jj zINH09i|>zzld>~pI_+Y8>c(^%F|C?g38}6}Y)-kZN8DOPwpZ_9=l(!E6pjvUj0Xmj zvY)gzp;|SyEo~)sT&EF;RxLHe*n~Z9rzwDHSIe1e6Dt5()2(@fUZH{B&?=W1UxNrd z8lduOa^ue-QJ!p1uORsAoDulLEgGPQ>2&y0%Zcjni~0ZN0i8{k)-@k(xxex1#vW5s z!?zk@4K2n;jfag+!}I!xVN?A->OWV1w(0e{N9vLO8>TPR9mM(l^XhDjNB3;w3%ay< zt7Xixr0L0~_cZyJGuGdpE4QOJLQdVAUOlRd*0p-Q}?AE`wEfDXh92th$R~)s4fdI{>S0Kdica zugH?A8thxzUb=ShG`#7w+ zkHV_^KUj5F!>aoTth%dU)n#GTWnk5%Vby&YR^5kS)qN0F-3MURjlimlVW;<6s(J%| zz{PQ)kiX5r*_~~!V1R2I2n0iIUboK|^x8u{hdac4685+I1nK|3L}%$SzaRa8nP)oR zcwPNp^bXx6RL=T45lT7JD@ia({R4O>r#TDLjJbR+hUBcFnIntSoRgPRreEAC4Xav} z_7k(Ix!s>Cmtu#G|5b>u^OX&F2Svt09B%uGgypA=b4sJEtZ5&KxU0EAt6XuxWEhb4 zanNLS6_Cpva(7DetNdv%ft#9%{^{U`r=EbaG*p3Ge{5uDAi7tP23Myv;%ZgeqyDq9 z9N4B*^5ChpPS84n$!MFrp0X7o(tImx+O7Vxk`8-z;Phl=?8$ysx zMeI4#4q`m5d2^Gmo_qyzExozP%PHi|IfcqNk^gUt`F-eNrpdI;_<{OA>Nlu?|8HTs zi`>3@Ra|URxP5m7hQd)y^cdR7CB`C2J}7`$TtNAfN|spNzqY+UmJCD`dE>flV)gTq z^af%k^^f>FeGC^JhhtG(FWVVS?C0V|g|g7~Tb0(5{dpu+Z_Q745=#X%2`C@%o2U$+ zdXn6*{FkU$jg?xJUQaGjHOJP<`HNG6GkBnGE&avG>nZCm`o&2ntJCX<`R1t~U{#)! z`0{dKwm;C)$SKPezgzPG(nR4C*$+sG3Uy_AEwP{assAQBbd%wL?T5oP4p17~YU^Rr zF|I$hYa|j2$lo?6k)y6oubCm}vt#$)3CXoNE&B5ABTpbFJNhJ&)K%%#GvxBCZS-a` z*cFc6NW7_z^eSRLM(dZK9Z&lLjdf24KiNSi@}=_rKdSqP&a%aPJ$jK@XL^6b9K*-z z|5m>afB3&YN__*VMd@vMFkPX6*#{+l6d(I=cz|F3eX+54kjt+$ghMPNv1o#8-!!sg zY#SZ8%6{_pNleSK9sUE~miwe`rzqG?Q-EuH&J{SONW>LUZ8Z zWO@q$f%@6*D@A)qAQ1L3S8*-u1}C`#$?20gW%q|en7oX?_a60s@0A0u{ERcu>$}3? z(-nwgs`~g_IDN{0?_vt2BYkTAdp}YSe3*3Xi4Aidk)c2&5*Y6Yar=r-7P^CGTP(yy z5*>wrX-CC9h!1}prvnWgR9vekq0ss75QE=K)0+uY5-K)X6{xu6fXcQ&d@q+Ai3EbO zyO%Dz$V~1@ZzAYY|BSMVJ07D8^CN+V1}d&4{2+g;L}h1vaM^_(<qm*6}gY3AF|Kbc1vNwGO+0jR< z_}n2o8oPy^p-`AlZU-WKJYE>Z!#g0*!|N+Arfe|MCB~Am3$u${8ESSN3-2=iO&yMN z{A{^=7+xYm1U-n#E%VmH{$*VluXt5=r27eE)bGMn-o+R)ZYyjJSmnj=Pn5U;yyCq* zFWpC=a)F9Zx$02C)&h0-%RuEVc=<44Fpm(fOD#+9BnDFRfKG~Q2v=Ca9%z;BJo#-U zlvAdbq)#WdQgbn!0H#0`I}KcXUpUCs>dq-y0{7&IODd6Vc1e9z?(`0FQ#Gsj*dwSC zJcTJP1iq+nos~@S08%ljR5U=GSe7~_-Af?Wt75}dh8Xx=RRk%FV%g6=xA>5-r?(SC zLFdBc4PVK?6$^IvAxWyO6=jdBq^RNWLJ2f9lYPN;$n&x&$=c0?bh@K>N2UsNu?I5S{L`W#{!HHT!T#}c*>bo8t&mgKjT_@uC= z&nA{qGc}P2qOwUFT>AuTr82pp`~`JJATq`&5<>HdPl@&Ev-0*jvRDhZROS3=4c>qi z7AyH!spp_eYz^eN?th%qks=EzV-R-dIZh}?!6TZBl_Ze7q*Tbs216JKvVb$FVtM1dV z>OKXl?whdc?u1qM16Xz6hgJ7oSam;yRrd(2x`$!aJp`-nd$8&rgjM$~SalD;s=FUn z-F>j?z74DHURZVaz^c0&R^4^5>OP56-hll7kHY?TKO*`6ET8|s7X6fQoAw(YGSusL z>$0^^)mK^})twHLxKf9T<4Qstuwrhuki~a{k^ojoJa_NI1>}Va0xr}Q7zqS%KL95? zotfJ$c1JEq?;!zQH8*aFq4)euc|J``2v+j;gRTX__A6aRq1=_ka^004B5>2d`1&gQ zpD&uRSzVwE^Hr8B)PHQkGh6VOmc4ykGKmLd%bh)OOXSzf(}M(T`&E2uRs|cj9w=v7 z8L&+%*OA{?Vx9QPG)FL}W~Mo+E6#6qc_FFFsz6R4e#s}mpUCFKX2 z$V8JmAw58VcD9PGSnW>pKzF81c|XdIF^AZgwmuyo7*lipp9*7Mt6Q#jV;*BW;<)Fk zD>jUI%&^m@0K$Za2=V{#*I9Pq`u}H{`Pl!z$)MMVb@x~D&eiuhE@_REQ$o!pt<_Em zr_JgFrA#XG^6g6o;nxxUby^VhtjTo z`LA7(pCq*~9VMVs^K6@HR~F_-c0l|6rP#)xQ^bpu=uFn7hY8{qY94V;ncc|ZR(}PM zCU#~lO-BgQp!ufYyOPVi+W70u4P8?{^?KXLC4%wr2u~j+`~4;noph)760~VQ@8__( zp+nhKgEn259(pK-y~;qOz~~`A<~&81yGY}hgJ#tz1MQ9B1g1nCSE7f*?G|Dr~Vv@y{9w!&JQ4`PODf_(!)g6=8D9xu3=qb>t?2%}&m!=PB`)+pP z$%3^Gmf|vjWIPZ|c1L9&Xy&mC+`Ord^nL;_HIr#pxlV9B2Q;_FBzR4I^yV)Z*y3fbNw@o8yn~9_t)Q6f297b`c~76rn~B1)<3Gh-t-|;ux>)XML*Bv zsSDTL$1JW}-S}EvbK}Fh=ghs9y_U6>*-g(k-QIL%)9xl)Q@!~K^Nr?94KJY!%?sF9 z*$3Hc*eKiC{222TI|sdh?qqIa4l+BD6E&c#nr~5G|w^J_VgZFMKjOnO^uLbP~Prap*XD z;dy8tz3|cKXnNrj&1Ee=`517k-U-jb8X4%s;4w(TV6p zdf~ZfF1_%v=vaE;qtH?G!n4tAdSMo^^uh=sYGLN@%-`vSUu9mU7Dfxu0(#*V)Iu*j zAI+y1UXRw(3$H`#=!Ms!we-R((Mo#Z4QKJ)mq8DC+*3b*DMyu(C zSD{t(!an4q7xp4Ay|4#)=!MJ_9 zqZhU!E4^?lYNZ!mfmYB9FGtJig_oga^ukNgQhMPfXbHXWVziiAnE4y?H+tc}GJnO+ z<2iKx;Hl_TYRzaf+DtFJ32mYm?nd48!W+>>dSO5Ell=c7U2~x6yXGb6lE$By?8YnW zf2#ND4!tEe`|S#+9GM0ZGE)E8NF2`D%tg6)Akq^ZjKzloe7#g5io@s6Yfz|Fc|8Tf zMjnqy{W&jVB=J}+3=J!ZYhz0}9PoWvVq6b*40Im|43Eg)|LF0Exx@C1LH&_fu_U7M zHpj-obHiY#yH$ZPWnBbZ@sLMKab)TVq|}_^sGN5y4FZ4ENajH zHNy4FK8ecX7LppN1(`ZxBQ^8RRDlw1Xab$_UI9u%ZY(b*XXU}^ufRK z6oih*6?5ps^kw<__1(aRc}LF~r!cUa^#6ZYXF1)RK`$~ZOcxu!VQA2A)_wTxI`r@G zzr*o>=KM}RqULyjba(d-4svlq7>DVJ#v;i=zc_vkLzmK*U8*}CkHz^eNd@8oUa6FB zRmMWBb|UaLuSwlwRd*A7{%6&&k%FCfUa7p!o@pZHQ}gK7=|P9rq0^KiJ8RSY<_luQxDQkw!o9_xOOuHH!Eeiw^^oI6<$}Y7f zHlV;2=aHsetl0~i zR%RLr(A3P0Dn?iz3O1B`Bxak|jETTZ8)LkSvvzzA*Wt1{p|efJ zYZNkr?VGt|;jf8h%Vh_f$Z#{$=l}f)c3#3CVb#3|tL_C@b-#vH_d8g1zkpTu8(4ME z!>W50R^6{))jb2N?w7FY{sF7*X;^hXhgJ78Sanaqs{1Ldx+h`PJprrkC$Q>%53BCC zuSaq+$s{0$Py1&Azdj(eAUtra}46E+Pu<9Pg`Ea=Te@GAd+o3v=|L@jW)?ohs zhnQncrx_>eAJ@;)b-#r?erj{(I1;f_Gch$?#LiKJ>d49=yZ+clF2W_b&QJ)?>Wf7K z5k8eKJA;SUEfx~3&&(yJ)WA#+Tp-5UqO_`|a->J);UT__$;t;@_JkW=$`6v~ID3x1wWuE5^|$Klbnkr&{y&81;bi6%0)7o}=i?VB?#$X6h;oVEeO!EBnANF>6+ zBKif~juqeFdoRF&+*G`(WsV`@X@q#1)m{C0)D`6@$y5)yap z!4s@yN20_-IuE#88ind8-OMCm13JyqxdZXLzYtFoZ*Qvy)=AEa&piG=^}6`f^6Q4%=M)bge`W=-;#@7`Hmwzg$~q$RpJ)y3igQkoWh}#a%GkiT0eNtM~n^{KecC_X}hjTQbai?ma!&3zN!@~+t;`zkv=pkoj zY2I{)pdF?7;AmSS*pUQ`cKRG*FR(weWTqVRHu-nlrg&^vZZFR#jvB=&oyB?MT`?n$ zc@Hi{1HGp!AM?`{pOs7L`0OJ65JJG3IRFi#l=-9Zm|MBBvOwlcGT>^3zgq{LWGMeFnBQa-B_y6Uas zdIbG=1Zr|je*fPu_)+=%f89em%O>+d*2J9J__2nc8r$lV_@n>*(bPAPT9R>*+jdmz zk8_U)n4x+{V2IB+VXSfOn?`nw4Mf60+22K8@x*jzoaDr8ROAh6LaZwgkB7Oq>=={gU#!pA3C3nbir#Oxx@wf8 zFAg!~t4w#q!$GbqHavneyV0cVZxf$55#!LZj4l5T-Vc0Cf|X_Yq_GgIG96^UshI$m z`b_sh`(5RIU8T30Td2#zp^A2Lva2~%LFL4FFo7B9bzJ;-+7^iK<&qj4|JRwWHok0F zrGLMU=Pl6UpOiPVhHOPO=cuSp6uS+2KW>V17zokeINzzzIm~mI1FgO;ERz$fHoAf6)m$8wqZTDY9HCWP3fOW}iMx;#M*a&vy0@McQcGph*F zw9%wKO(Bil23-d|32EX#`o~8Uz?j(CyePAhU~E_mH+3b9;X+yH?0vbVWPedf!yOi8 z{KQTgmja7S$j?h| z@7o(5ksVP|WE5F^uV!;I&4}qAP zhk1&x!m2@c{FVpp(!wY$hK5-SYA9lcEn>rE|KUP(DHWG1 z{(<}nSHCoO)}HAm=2NkYIXGIvU&RMj_-Z02{B+>SE8&HXJ#WY z8g!{~O~N?Edp%D(R)jfB;vpO66bk2vgKAR?GhKPJU7=!+vm(~8^8y_ZsD?GUFKoX! zuosUF+(68v3BHzU=z%DW>@LU8ciim*T?bHhDMgMyyMUt^KK~D$3_CAj z9;~_(VbvW6t8Nagy5nKhSzy&23#;xJSanCis+$d~ZWgS%W>|I0VAVCjsx!l?V`0@H zSal4nx<*)aCRlY1ujIinquh!m4Y9Rks3GT??$brLgLjz^Ypet8Njjx`nXn7Qm`I1y)@>tU5i;NdwORbHe_1 z4$}W`)mh@^udppR|Ns4l;|%Z8KTv0#l1u-u{~&ctrk4b^det0r#4XZ&dpIsBq@gkI z5NJ;l4&oOt(YGf&GOR?SsNW|9v2f4Fb`rx^bI*rZ<_tRXDpDEbku56xJ z(@P}vpitAy7Ah^Q4`bfbi*wT zhZ}Y^EHl1ge8hO2F=^avJVF1a;T}WAFlg`_%=OPSZfu;Z-(P=Q{gL{!>RU}On(nH5 zS^ud1deetY!MX|k7X3Vvr!HJ~AG5e_b>nMw&5aN1o-_Aa_FC3jW;Z?GbbHg4O}m?H zP4(s{%r}}ZHN1o_G%sLZWgld(VWVtk^JC0Y>>Tt0x|6wyImqlpPSk*|YQCX)ym@Q$ z$@mw3OMk3}U3$GMINM81Vbh0d#;~cputF zFPub4df`ztN-sQ&hUtalC{8cD2koI3j-eR6a1=%9g##!+FMJWYh+a61!t}ytp|j|P zImFQmhfs)KIEaGu!UJf4Uif@;KE3d6w3}Y|Jaitt@VV$*df{`>IrPG3qqFIS-;Lf) zFMK9ClV11?bOycfF0_kYxF7Y?3-_Tudf}aDC%y3L=yZDF9cTx=a4+hm7v7Gx(+h7y z+vtV2qOJ78BWQ$PID#Vd!h6wPdf_27L@zvu2I++_L>JNvUw|&47d{Q0MlZYtZJ`(5 zkM`3GkD)Po;RH(13+GUdUO0=g^uigGp%?xj`XIgVN6<&;g(uJiz3~4-|BqhyYIHTd za2loQg+GiwOfUQ)^dWlTtI$>S!tY1#rx$)7dLO;;mFP-(;rF8V(hI)_y@y_S9F5Zp zA3;axg|9$Y&0XX;^s$Bd~;5`9P6IWSLxkNFKF=-4| zOQw%tj8m@(Cs*$A`A0kk1bV(&QC>^=`^oNa_lv`>FirVR0y9m}lvmmGHA)>n$R&bK_Y~pe#;R7uT&6s=&r!b$~_~N@3HJP+*=ff3P{l$AUR?F0s%W^;)R-dBxu}HJDOVbd@QAm}1J! zbBH}uOu1PZG0<;-Qo!WznP^hhumO)3hZxLh%$!Z^26blYh3PkNUhIYl;1r^7OQvnaD_Q8U2zWkW3usf_bW3(|kw|wu9*fKGoB6~JE_)_SY^Z8p{{ldJqWi*0 zGG(>E?2o&)pmY0W*OFH59u9;f{n$N_{iyJX$M%NI9%8iRGXi+fL<`dauLn<5k0&wu zm3SQaL=SI8W{6zA4HX<^|}^*0RXMfs-@BiSR%;5>CoHo`-)biQ{=v z?#v*;T(f$u^%gEWXeNefFem$A=@ltyPReiuW-9hUD#Hw)ji8SbDGoUa(X!h|;>uW) z2@znOtN~!*H{?((9u5W~eO!EBILH-FL+r$ZzC*goE;RMiGXjw@PLXJ`#H%qs6C?mr zaac?Qn2Rx`QA!@mRgiakS(R+>D40WCtY>CtM72gbFC_rKDSE|zOGE1 z+^($}fd2F`8ap-|=peXh-mUFD(UGwvUvj5_uV{A&!9hF{Zj``H)g$4`I&c_ncE1(W zX!sG^8RX}4_K%Nn?VXWG&*1h@fd6PD$%W)T!|ir4n%0#WnNeuN!~8%EHyzsY;m~c^ zuz?}j>m%%Tv3q7oCPvUUs@_Ah^5tgt)e>#_yOawRpq}WBMr4o5*li-<)?}gta9VI4 zDXupg@TA)50N01Z>0`3zAM7?Ulznt&m_Y3`O+pPfMR}p;GKx2w>=&6$d~bARA~WeC z!qZAt8N3Vx=vVi>tWTM4XbV~thzO@>Q=+5 zTLr6bC9FC>tU4d8Ixnoc(_qz|3af4-tU3>@IybDkURZV8VAWj+tL_3=b?3mU8-P_8 zfK_)sth(K>>du2zcP^~D(_z(}1*`5%SaoN>s@nytt{+xiAFR5auYO-d4mkfW z1pC_sN&o+BoyBK9jDEtLY5HixEW>&FAJxrPI@hnz`l(f!OGyk;&GUK+?FE|!&bjVcncv;-rWi~dhC4epi6PGIsCE4*K5nnW9#tDAx>b2S6 zXaPSs!2(+UijBc*-pD0_@$d-GvMRqN#6}H&<^Vy@T=iN)Ku`BR+}&2-9O1L{BY`09 zniiVR)R43a-u8YGLXZ8~qsdz-02%{QQ`(x9~4jq@6M_!)U zN51|28ix+_wNnu~vKz}Oz7ianF>=l^>NQs7vL}bj>XIW{tcv1(yTp01F_R=#Yg4b) zgxhJlx(U8k6jL?IT65StmQ=FDSD61t{Qo&R%c%Kgc0O~C=^^7U4So7g*3D7K|5m}W zsm834Tpyb7A)%jFg-38_G!Pk2gcDoC(Y>uN@E2sD%$Hz(Y3G3~iWC8PnfPJReF3228Oiu2+PiU10%5?b72| zkuZ)#zhTZC)DWgzz*d@ysXPsl>}Q-;d|Ir_Tuv~h<~b>J$vJ46ziA*vl5>1ZsUnb) zh&R~GWdu^IwD3A~)*MpGm_z7%E7bwMRLX&rX4)Tt?J zBA8P3s2(A7>9@K;Ct4K3EPa@mn~Qe_lVMCZJ-#(IBs-#PV%BXi+dvSdVk#P1zX?}2 zfj-Tv1FA|2a2Y`xHg|D?5brA4vjz^E(Em^J|7YqfW9F~03z&fE@rI)e@7MpK?#y@W zeE&BqNX^R5B_~?92437Q=Y$LE>MWO)8P^C{g6w` zEfuKxaCn3}J;h{?COFbUxW{R$O%GJQ)Oiygzk?eB`??xGr&1@Kv&6D zg_DvbxDK~S%W28ZCTQwZ@3TR?l^it~hROTA{jp>qaz-pNHp~^8Y-E2YxW&qvHQ8A+ z2U5_~(p4d)B-5?KCANH+vdsiiTBs0p;0jUb`LZRDk{wGf;jKXO|G&~%R+_Iyzh}Bk z6UN^eeERq5el;`P_tfHS3)yNh^`73-IDHPc)d^bpDjX3-USGR1`F^K#sJCWMCWg91 zy@nbbEo5~0Z!kVwIuj83lqkPg?|#hk+8ZBIz{hZk=SIq!olnelnR?AN9jV#81@xwQyNf=F6Al$oYIt(1=$mcvBK&-3ai{)HRvBK4=p7wESnhpZ_S=Stfb=i z#dH@|t$f8|NGVzpAaTgg&mK?A70@K8potrBMy)KUWcM8XV$sHi>~VPu9Z_+%W*R`% zsx)8{o(dHKMW0E90Qf^i^8eyc@0rg3e+ZT@@gS_a%V5=A46AM*th!5J)s4WaI{>S0 zKdibWthxlOx;U)5QCM|X!m5kGs*A#^8-`UEfmOE`R^3Ig>cX(<_Q0yU6jt3hthzB+ zbwjY~24U5`3s&9bufQ&dE(5DB4Xf^huz5@V@2k5VC6&UaAQWlMJ{i7mTWer(MyA?DI>okp!% zVv9oZMU-O-q$yo?p&l>Qnq91Ec;Q+#&~D30iLZ8^=`W6{{<@R?~~7yXs!nKdQgp^dVEQZbH9BKhNZ;3)kJpEUsJK_*z|a;&7wLuXM0e5)e;s|DUic1l2fgs^=yrPHFQYHh3*Uxr zqZhsv-AXU~CG;hF;akux^ujlzo9Tsrj($!rd=t8fUib^>3-rRDN1vw`z7gF>FZ?<5 zIeOvGqR-L`-+*qQ7yb<_9^unJ&pP(21KKeer@I&Y!df^ArgY?2L zq8I6fe~*4oFZ?_7J9^>g(ew1ee?)(z7ybkK1HJIm=xKW4m(WY}!Y`l~=!JiaeoHU> z8}u7`;pfnE^uo`gXX%B1g?>dZ{0w@AUig>jm-NEFK);|Degr*2FZ?s~GkW2t&{Oon zKSe*K7k&~wNiX~adV*f~C+H{i!jGfJ>4hIdkI@VN82y-D_)+vIz3`9FkLZPeh<->f zd@Z_`UijnaW@lZHAv@sqSls(w%mS|w@Sv#?#Nt4leo+t#Ar=e=@3#Yzz zaPdSeszj@@+a}VPw`OgciO^Sp0|p-^w*n>ftGt@*3CrSnnjHjVDxRlVrF;Vq+5jhC zE+3)q428nUa12jD<%cH8&LHRVi3nSiZ6^pDR`0PmO^(@#JD0tX^HIu`M{Ny66|j?6 zq}XW9wyFLktOhNd{F3MT!i9%Q4I*=S#LB2;St~J@24}`t=jt=1}u>FDY4u12JzqWVo=qdRp{;o&jv5R)&u`Ql9 zo3jm1Z?XGUI@~KAwsyDlNb+ZW z3)k%yoX5k-te2b$8a>USkAi7VbNch3ENEO}#cEI1LqMbASqXHY!HrFz_h}W-Yzs%H z{Iq`$p0ltg7LmOh!!6AWc4Xben2dT)z$#B7p1fb42n=7|FD4MWhXdgVJ)nT@EvMM> z)RlD+tF@}vYIG3vSY4p@n*uT^Syfu3X>rb~^F8|LT-bvI1wfXXP4TAkfU_SQVD9}x~5SgP$cUHqcu%L;lD zg-_((WgcnZ$BJxM-hNl8I)2-e52priEyw`m4T_OozUF=W!EAjV3irg z;E}lfS7$fO5VY}R8laY&awC_bE$>JwQIg?tiayPvY$rjRiWA0o2yP{`;iML5=pDO~ zW!F*&XAaR$j_i7ZGZnilbPr$5s!`+GnG&4IewT?arqn6fbp%~Q>V1n;*zr973G_tF zO6XD`1?jPg$^BK?wFFuxs@F)A(85z{Kp%`1JjeV~F&d1Fg*ZING!o|GvSUn~N}gJt zT|+RYVn(Pc7=xbEPzhrS4y$qdg%onipIuF`run>s9nY)*t*5Diwf@-1&OmgpB6MDE zvAB@r|5=@-&wMM3n4sy7hNBEY{T+3zjPAWUmcob5HVq#-Zs>VSl@1*~e5DZalE*Jz z@Jq8>iLnyuzugwN)H$wWc7hh0_r=EIK`y`25ME{^7EN&N+XC^u zToTVS3(6j1^hozZYxXo^DpfNEmUR)arQFyZ1+~<4xKf02$>S619a0OjTZp+-JkCsb zI+sC~-Db6eh7U^CSlz$29e>32g)f#LR6g<1wKUtK`tz^y3)zL88d*TqePASv^U|HN zx3zh^LW(7|FncO7)m}|rRhZef)_BTKmsjK^?D2?I%L}ucRd1#$h{D|)vLH&C>&YVq zP3LDf5i_ZHFkqGI372?)dYENyC;lZPdnud8Ek4bcXS<1oG#R|X)7D|>P%FLn6rAJj zaSL!F{{LK^agSLnA$&C8xa_V5Mjzq5C8qht7y!}nLD?~77krh}(rhoWlZtab zRWOAQJJ3K{MNGwd2NmH$@k;eKi?iE_sbcECmr6n?`SctfV*~n*D4U+cb;f}>?*Es) z(#9)R+MwnEolTe4H6LxczwzqE9#d1pw;Ey%EyhQUhmB6d^ZJNkQ~f{cKUaUY>Girt z>XH5%rZ3bT#Qgr})!7)2?%Bo{bZPTe%a~aK=W z_Yqiix5BEs1yh6bCcOR^}dtlYw4Xf^3uLy^-<*?IxE!DgMf563Yp^(4L!P%W{u3&&`8wdnLZCH7_K!>8+8>oWMmnfK>VOZLJ%nHO~L zUWMEu4$&3SdR61`^7_S`&YbK8BzCq^E&nET?Y7$P^RyBl^f~|#Kw^+HcGIlW+Y6M1W76d$`=#U-oK4>UXK@99C1CzNyB&R!VNBh|&lzm`kJdeo zKc4a@)so#y%*d(PjK!`a-))Tv+(5?FUUZvH#_wl_s5spjp4hjFZ#)A$@%ErqDL3~c}F?xJ>WEfKD!u!o0|=iO{C)B=+rk0Zbqzm?xsI_K!GL?pG|1U!4H=KVj9gqT)-pP6>kKp zcV^j#i%oDaQzvEv@ALlK*`5`h}hvlK*$=EE~)ZvGpk0 z_!$#2cGiEi?n#|wr)@74<8y#7w^%p7TD{jUv@zG?6fB3}M_QGs}z)Z!oQG=tU`=5W5LXWt> zkJ8d*`2M#1X9-1UqkKN$U6Ja}#tG6oG>xf4HA8&tMC^Z)ngFSt@4WTmG11wOu?Al>XtR?SO_+9p{~G4AQ(<6 zFm2lB6`m!BW@lr0Zyq|Nk4LD!#kr|IiTJN88zsA|Rr4pf&FYdXd{;QxH{qIPT&BFY zk4q-Q(IMFn36F3QV+VhjoZl*T@TWdx@cCVXQo)k*`<+>(tb^|n1E(vq5dxp%wGJP@ z)dig;uJrzyRG%yVt4Abj%=7>0{;jjP%^yK;GX17c8UJnAss9rGXy*S(b!0CmTen^l zTUX2=3h@kl+(VamO