feat: add checkout service make targets (#1311)
This commit is contained in:
@@ -30,6 +30,15 @@ YELLOW := \033[33m
|
||||
MAGENTA := \033[35m
|
||||
|
||||
VERBOSE ?= 0
|
||||
NODE ?= $(shell command -v node 2>/dev/null || echo node)
|
||||
SERVICE_CLI := $(CURDIR)/dist/kandev/cli/bin/cli.js
|
||||
SERVICE_BUNDLE_DIR := $(CURDIR)/dist/kandev
|
||||
SERVICE_VERSION := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev)
|
||||
SERVICE_ENV := KANDEV_BUNDLE_DIR="$(SERVICE_BUNDLE_DIR)" KANDEV_VERSION="$(SERVICE_VERSION)"
|
||||
SERVICE_PORT_FLAG := $(if $(PORT),--port $(PORT),)
|
||||
SERVICE_HOME_DIR_FLAG := $(if $(HOME_DIR),--home-dir "$(HOME_DIR)",)
|
||||
SERVICE_NO_BOOT_START_FLAG := $(if $(filter 1 true yes,$(NO_BOOT_START)),--no-boot-start,)
|
||||
SERVICE_INSTALL_FLAGS := $(SERVICE_PORT_FLAG) $(SERVICE_HOME_DIR_FLAG) $(SERVICE_NO_BOOT_START_FLAG)
|
||||
|
||||
# Phase headers
|
||||
define phase
|
||||
@@ -65,6 +74,20 @@ help:
|
||||
@echo " start-verbose Start in production mode with info logs from backend + web"
|
||||
@echo " start VERBOSE=1 Same as start-verbose"
|
||||
@echo ""
|
||||
@echo "Service Commands:"
|
||||
@echo " service-install Install deps, build current checkout, install user service"
|
||||
@echo " service-install-system Install deps, build current checkout, install system service"
|
||||
@echo " service-status Show current user service status"
|
||||
@echo " service-logs Show current user service logs"
|
||||
@echo " service-logs-follow Follow current user service logs"
|
||||
@echo " service-start Start current user service"
|
||||
@echo " service-stop Stop current user service"
|
||||
@echo " service-restart Restart current user service"
|
||||
@echo " service-uninstall Uninstall current user service"
|
||||
@echo " service-config Show service launcher/config paths"
|
||||
@echo " service-install PORT=3000 HOME_DIR=/path Optional install overrides"
|
||||
@echo " service-install NO_BOOT_START=1 Skip Linux user-service boot hint"
|
||||
@echo ""
|
||||
@echo "Build Commands:"
|
||||
@echo " build Build backend and web app"
|
||||
@echo " build-backend Build backend binary"
|
||||
@@ -189,6 +212,61 @@ start-verbose:
|
||||
start-debug:
|
||||
@$(MAKE) start DEBUG=1
|
||||
|
||||
#
|
||||
# Service
|
||||
#
|
||||
|
||||
.PHONY: service-bundle
|
||||
service-bundle: install build
|
||||
$(call phase,Packaging Service Bundle)
|
||||
@scripts/release/package-web.sh
|
||||
@scripts/release/package-cli.sh
|
||||
@test -n "$(SERVICE_BUNDLE_DIR)" || { echo "SERVICE_BUNDLE_DIR is empty; aborting."; exit 1; }
|
||||
@test "$(SERVICE_BUNDLE_DIR)" != "/" || { echo "SERVICE_BUNDLE_DIR must not be /; aborting."; exit 1; }
|
||||
@test -f "$(BACKEND_DIR)/bin/agentctl-linux-amd64" || $(MAKE) -C $(BACKEND_DIR) build-agentctl-linux
|
||||
@$(RMDIR) "$(SERVICE_BUNDLE_DIR)/bin"
|
||||
@mkdir -p "$(SERVICE_BUNDLE_DIR)/bin"
|
||||
@cp "$(BACKEND_DIR)/bin/kandev" "$(BACKEND_DIR)/bin/agentctl" "$(BACKEND_DIR)/bin/agentctl-linux-amd64" "$(SERVICE_BUNDLE_DIR)/bin/"
|
||||
@scripts/release/package-bundle.sh
|
||||
$(call success,Service bundle packaged at $(SERVICE_BUNDLE_DIR))
|
||||
|
||||
.PHONY: service-cli-check
|
||||
service-cli-check:
|
||||
@test -f "$(SERVICE_CLI)" || { echo "Missing $(SERVICE_CLI). Run 'make service-install' first."; exit 1; }
|
||||
|
||||
.PHONY: service-install
|
||||
service-install: service-bundle
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service install $(SERVICE_INSTALL_FLAGS)
|
||||
|
||||
.PHONY: service-install-system
|
||||
service-install-system: service-bundle
|
||||
@sudo env $(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service install --system $(SERVICE_INSTALL_FLAGS)
|
||||
|
||||
.PHONY: service-uninstall service-start service-stop service-restart service-status service-logs service-logs-follow service-config
|
||||
service-uninstall: service-cli-check
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service uninstall
|
||||
|
||||
service-start: service-cli-check
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service start
|
||||
|
||||
service-stop: service-cli-check
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service stop
|
||||
|
||||
service-restart: service-cli-check
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service restart
|
||||
|
||||
service-status: service-cli-check
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service status
|
||||
|
||||
service-logs: service-cli-check
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service logs
|
||||
|
||||
service-logs-follow: service-cli-check
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service logs -f
|
||||
|
||||
service-config: service-cli-check
|
||||
@$(SERVICE_ENV) "$(NODE)" "$(SERVICE_CLI)" service config
|
||||
|
||||
.PHONY: build-backend
|
||||
build-backend:
|
||||
@printf "$(CYAN)Building backend...$(RESET)\n"
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
installKindHomebrew = "homebrew"
|
||||
installKindNPM = "npm"
|
||||
installKindNPX = "npx"
|
||||
installKindLocal = "local"
|
||||
manualServiceInstall = "kandev service install"
|
||||
manualServiceRestart = "kandev service restart"
|
||||
)
|
||||
|
||||
@@ -114,6 +114,47 @@ func TestService_GetForeignServiceDisablesApply(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_GetLocalBundleServiceDisablesApply(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
metadataPath, _ := writeServiceInstallForTest(t, homeDir, serviceInstallMetadata{
|
||||
Manager: "systemd",
|
||||
Mode: "user",
|
||||
Kind: installKindLocal,
|
||||
HomeDir: homeDir,
|
||||
LogDir: filepath.Join(homeDir, "logs"),
|
||||
ServicePath: filepath.Join(homeDir, "kandev.service"),
|
||||
NodePath: "/usr/local/bin/node",
|
||||
CLIEntry: "/Users/alice/src/kandev/dist/kandev/cli/bin/cli.js",
|
||||
BundleDir: "/Users/alice/src/kandev/dist/kandev",
|
||||
})
|
||||
t.Setenv(envRunningAsService, "true")
|
||||
t.Setenv(envServiceMode, "user")
|
||||
t.Setenv(envServiceManager, "systemd")
|
||||
t.Setenv(envInstallKind, installKindLocal)
|
||||
t.Setenv(envServiceMetadata, metadataPath)
|
||||
|
||||
svc := NewService(newTestPool(t), "v1.0.0", nil, logger.Default(), WithHomeDir(homeDir))
|
||||
resp, err := svc.Get()
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
if !resp.Install.ManagedService {
|
||||
t.Fatalf("expected service to be recognised as managed")
|
||||
}
|
||||
if resp.ApplySupported {
|
||||
t.Fatalf("ApplySupported=true for local bundle service")
|
||||
}
|
||||
if resp.ApplyUnsupportedReason == "" {
|
||||
t.Fatalf("expected unsupported reason for local bundle service")
|
||||
}
|
||||
if !hasString(resp.ManualCommands, "kandev service install") {
|
||||
t.Fatalf("manual commands = %v, want service install command", resp.ManualCommands)
|
||||
}
|
||||
if !hasString(resp.ManualCommands, "kandev service restart") {
|
||||
t.Fatalf("manual commands = %v, want service restart command", resp.ManualCommands)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManualCommandsNPXHasNoDuplicateBinaryName(t *testing.T) {
|
||||
cmds := manualCommands(InstallStateResponse{Kind: installKindNPX, Mode: installModeUser}, "v1.2.3")
|
||||
if !hasString(cmds, "npx -y kandev@1.2.3 service install") {
|
||||
|
||||
@@ -187,4 +187,17 @@ describe("captureLauncher shim resolution", () => {
|
||||
expect(info.kind).toBe("homebrew");
|
||||
expect(info.shimPath).toBeUndefined();
|
||||
});
|
||||
|
||||
it("classifies a non-Cellar bundle as a local checkout install", () => {
|
||||
const cliEntry = "/Users/alice/src/kandev/dist/kandev/cli/bin/cli.js";
|
||||
process.argv = ["/path/to/node", cliEntry];
|
||||
vi.stubEnv("KANDEV_BUNDLE_DIR", "/Users/alice/src/kandev/dist/kandev");
|
||||
vi.spyOn(fs, "existsSync").mockImplementation((p) => p === cliEntry);
|
||||
vi.spyOn(fs, "realpathSync").mockReturnValue(cliEntry);
|
||||
|
||||
const info = captureLauncher();
|
||||
expect(info.kind).toBe("local");
|
||||
expect(info.bundleDir).toBe("/Users/alice/src/kandev/dist/kandev");
|
||||
expect(info.shimPath).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ export function macosSystemPlistPath(): string {
|
||||
return path.join(MACOS_SYSTEM_DAEMON_DIR, `${LAUNCHD_LABEL}.plist`);
|
||||
}
|
||||
|
||||
export type LauncherKind = "homebrew" | "npm" | "npx" | "unknown";
|
||||
export type LauncherKind = "homebrew" | "npm" | "npx" | "local" | "unknown";
|
||||
|
||||
export type LauncherInfo = {
|
||||
/** Absolute path to node executable (process.execPath at install time). */
|
||||
@@ -43,7 +43,7 @@ export type LauncherInfo = {
|
||||
cliEntry: string;
|
||||
/** Best-guess of how kandev was installed. Used to seed env vars. */
|
||||
kind: LauncherKind;
|
||||
/** KANDEV_BUNDLE_DIR if set (Homebrew sets this). */
|
||||
/** KANDEV_BUNDLE_DIR if set (Homebrew and local checkout installs set this). */
|
||||
bundleDir?: string;
|
||||
/** KANDEV_VERSION if set (Homebrew sets this). */
|
||||
version?: string;
|
||||
@@ -96,7 +96,9 @@ export function captureLauncher(): LauncherInfo {
|
||||
const bundleDir = process.env.KANDEV_BUNDLE_DIR;
|
||||
const version = process.env.KANDEV_VERSION;
|
||||
const kind: LauncherKind = bundleDir
|
||||
? "homebrew"
|
||||
? homebrewShimPath(cliEntry)
|
||||
? "homebrew"
|
||||
: "local"
|
||||
: looksLikeNpxEntry(cliEntry)
|
||||
? "npx"
|
||||
: cliEntry.includes(`${path.sep}node_modules${path.sep}`)
|
||||
|
||||
@@ -18,6 +18,35 @@ sudo kandev service install --system
|
||||
|
||||
After install, kandev is reachable at `http://localhost:38429` (or `--port <N>` if you passed it).
|
||||
|
||||
## Run the Current Checkout as a Service
|
||||
|
||||
When developing from a cloned repo, use the root Make targets instead of the globally installed `kandev` binary. They install dependencies, build the currently checked-out branch, assemble a local release-style bundle under `dist/kandev`, and install the service with `KANDEV_BUNDLE_DIR` pointing at that bundle.
|
||||
|
||||
```bash
|
||||
git checkout my-branch
|
||||
make service-install
|
||||
```
|
||||
|
||||
Useful targets:
|
||||
|
||||
```bash
|
||||
make service-install # user service from the current checkout
|
||||
make service-install PORT=3000
|
||||
make service-install HOME_DIR=/path/to/kandev-home
|
||||
make service-install NO_BOOT_START=1
|
||||
make service-install-system # system service install; other targets below use the user service
|
||||
make service-status
|
||||
make service-logs
|
||||
make service-logs-follow
|
||||
make service-start
|
||||
make service-stop
|
||||
make service-restart
|
||||
make service-uninstall
|
||||
make service-config
|
||||
```
|
||||
|
||||
The service runs the built snapshot in `dist/kandev`, not live source files. After switching branches or changing code, rerun `make service-install` to rebuild and refresh the service unit. Checkout-based services are marked as a local install kind, so the System → Updates page will not offer one-click npm/Homebrew self-update; update by rebuilding from the desired branch.
|
||||
|
||||
## User Mode vs `--system` Mode
|
||||
|
||||
| | **User mode** (default) | **`--system` mode** |
|
||||
|
||||
Reference in New Issue
Block a user