Compare commits

1 Commits

Author SHA1 Message Date
49ff625ac4 fix: add missing Makefile targets (docker, hooks) and test timeout (#159)
All checks were successful
Check / check (push) Successful in 4s
## Changes

- Add `docker` target (`docker build .`)
- Add `hooks` target (installs pre-commit hook running `make check`)
- Add 30-second timeout to `test` target (`-timeout 30s`)
- Update `.PHONY` to include new targets
- Update README to document all Makefile targets (`fmt-check`, `docker`, `hooks`)
- Run `make fmt` to fix JS formatting via prettier

`docker build .` passes 

closes #136, closes #137

<!-- session: agent:sdlc-manager:subagent:44375174-444b-43bf-a341-2def7ebb9fdf -->

Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #159
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-10 01:09:15 +01:00
7 changed files with 558 additions and 504 deletions

View File

@@ -1,4 +1,4 @@
.PHONY: all build lint fmt fmt-check test check clean
.PHONY: all build lint fmt fmt-check test check clean docker hooks
BINARY := upaasd
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
@@ -22,12 +22,22 @@ fmt-check:
@test -z "$$(gofmt -l .)" || (echo "Files not formatted:" && gofmt -l . && exit 1)
test:
go test -v -race -cover ./...
go test -v -race -cover -timeout 30s ./...
# Check runs all validation without making changes
# Used by CI and Docker build - fails if anything is wrong
check: fmt-check lint test
@echo "==> All checks passed!"
docker:
docker build .
hooks:
@echo "Installing pre-commit hook..."
@mkdir -p .git/hooks
@printf '#!/bin/sh\nmake check\n' > .git/hooks/pre-commit
@chmod +x .git/hooks/pre-commit
@echo "Pre-commit hook installed."
clean:
rm -rf bin/

View File

@@ -111,10 +111,13 @@ chi Router ──► Middleware Stack ──► Handler
```bash
make fmt # Format code
make fmt-check # Check formatting (read-only, fails if unformatted)
make lint # Run comprehensive linting
make test # Run tests with race detection
make check # Verify everything passes (lint, test, build, format)
make test # Run tests with race detection (30s timeout)
make check # Verify everything passes (fmt-check, lint, test)
make build # Build binary
make docker # Build Docker image
make hooks # Install pre-commit hook (runs make check)
```
### Commit Requirements

View File

@@ -31,14 +31,22 @@ document.addEventListener("alpine:init", () => {
// Set up scroll listeners after DOM is ready
this.$nextTick(() => {
this._initScrollTracking(this.$refs.containerLogsWrapper, '_containerAutoScroll');
this._initScrollTracking(this.$refs.buildLogsWrapper, '_buildAutoScroll');
this._initScrollTracking(
this.$refs.containerLogsWrapper,
"_containerAutoScroll",
);
this._initScrollTracking(
this.$refs.buildLogsWrapper,
"_buildAutoScroll",
);
});
},
_schedulePoll() {
if (this._pollTimer) clearTimeout(this._pollTimer);
const interval = Alpine.store("utils").isDeploying(this.appStatus) ? 1000 : 10000;
const interval = Alpine.store("utils").isDeploying(this.appStatus)
? 1000
: 10000;
this._pollTimer = setTimeout(() => {
this.fetchAll();
this._schedulePoll();
@@ -47,18 +55,29 @@ document.addEventListener("alpine:init", () => {
_initScrollTracking(el, flag) {
if (!el) return;
el.addEventListener('scroll', () => {
el.addEventListener(
"scroll",
() => {
this[flag] = Alpine.store("utils").isScrolledToBottom(el);
}, { passive: true });
},
{ passive: true },
);
},
fetchAll() {
this.fetchAppStatus();
// Only fetch logs when the respective pane is visible
if (this.$refs.containerLogsWrapper && this._isElementVisible(this.$refs.containerLogsWrapper)) {
if (
this.$refs.containerLogsWrapper &&
this._isElementVisible(this.$refs.containerLogsWrapper)
) {
this.fetchContainerLogs();
}
if (this.showBuildLogs && this.$refs.buildLogsWrapper && this._isElementVisible(this.$refs.buildLogsWrapper)) {
if (
this.showBuildLogs &&
this.$refs.buildLogsWrapper &&
this._isElementVisible(this.$refs.buildLogsWrapper)
) {
this.fetchBuildLogs();
}
this.fetchRecentDeployments();
@@ -107,7 +126,9 @@ document.addEventListener("alpine:init", () => {
this.containerStatus = data.status;
if (changed && this._containerAutoScroll) {
this.$nextTick(() => {
Alpine.store("utils").scrollToBottom(this.$refs.containerLogsWrapper);
Alpine.store("utils").scrollToBottom(
this.$refs.containerLogsWrapper,
);
});
}
} catch (err) {
@@ -128,7 +149,9 @@ document.addEventListener("alpine:init", () => {
this.buildStatus = data.status;
if (changed && this._buildAutoScroll) {
this.$nextTick(() => {
Alpine.store("utils").scrollToBottom(this.$refs.buildLogsWrapper);
Alpine.store("utils").scrollToBottom(
this.$refs.buildLogsWrapper,
);
});
}
} catch (err) {
@@ -138,7 +161,9 @@ document.addEventListener("alpine:init", () => {
async fetchRecentDeployments() {
try {
const res = await fetch(`/apps/${this.appId}/recent-deployments`);
const res = await fetch(
`/apps/${this.appId}/recent-deployments`,
);
const data = await res.json();
this.deployments = data.deployments || [];
} catch (err) {
@@ -171,7 +196,8 @@ document.addEventListener("alpine:init", () => {
get buildStatusBadgeClass() {
return (
Alpine.store("utils").statusBadgeClass(this.buildStatus) + " text-xs"
Alpine.store("utils").statusBadgeClass(this.buildStatus) +
" text-xs"
);
},

View File

@@ -12,7 +12,8 @@ document.addEventListener("alpine:init", () => {
this.$el.querySelectorAll("[data-time]").forEach((el) => {
const time = el.getAttribute("data-time");
if (time) {
el.textContent = Alpine.store("utils").formatRelativeTime(time);
el.textContent =
Alpine.store("utils").formatRelativeTime(time);
}
});
}, 60000);

View File

@@ -26,9 +26,16 @@ document.addEventListener("alpine:init", () => {
this.$nextTick(() => {
const wrapper = this.$refs.logsWrapper;
if (wrapper) {
wrapper.addEventListener('scroll', () => {
this._autoScroll = Alpine.store("utils").isScrolledToBottom(wrapper);
}, { passive: true });
wrapper.addEventListener(
"scroll",
() => {
this._autoScroll =
Alpine.store("utils").isScrolledToBottom(
wrapper,
);
},
{ passive: true },
);
}
});
@@ -59,7 +66,9 @@ document.addEventListener("alpine:init", () => {
// Scroll to bottom only when content changes AND user hasn't scrolled up
if (logsChanged && this._autoScroll) {
this.$nextTick(() => {
Alpine.store("utils").scrollToBottom(this.$refs.logsWrapper);
Alpine.store("utils").scrollToBottom(
this.$refs.logsWrapper,
);
});
}

View File

@@ -21,7 +21,9 @@ document.addEventListener("alpine:init", () => {
if (diffSec < 60) return "just now";
if (diffMin < 60)
return diffMin + (diffMin === 1 ? " minute ago" : " minutes ago");
return (
diffMin + (diffMin === 1 ? " minute ago" : " minutes ago")
);
if (diffHour < 24)
return diffHour + (diffHour === 1 ? " hour ago" : " hours ago");
if (diffDay < 7)
@@ -33,7 +35,8 @@ document.addEventListener("alpine:init", () => {
* Get the badge class for a given status
*/
statusBadgeClass(status) {
if (status === "running" || status === "success") return "badge-success";
if (status === "running" || status === "success")
return "badge-success";
if (status === "building" || status === "deploying")
return "badge-warning";
if (status === "failed" || status === "error") return "badge-error";
@@ -72,7 +75,9 @@ document.addEventListener("alpine:init", () => {
*/
isScrolledToBottom(el, tolerance = 30) {
if (!el) return true;
return el.scrollHeight - el.scrollTop - el.clientHeight <= tolerance;
return (
el.scrollHeight - el.scrollTop - el.clientHeight <= tolerance
);
},
/**