Skip to content

Commit 6bba5ad

Browse files
authored
Merge pull request #44 from masseater/fix/ops-harbor-publish
fix(ops-harbor): fix publish config
2 parents 1ffedea + 5483c07 commit 6bba5ad

22 files changed

Lines changed: 118 additions & 42 deletions

File tree

AGENTS.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ Claude Codeプラグインのマーケットプレイスリポジトリ。全プ
4545
| -------------------------------- | ------------------------------------------------------------------------------------------------------------ |
4646
| **@r_masseater/cc-plugin-lib** | Common library for Claude Code plugins - logging and error handling utilities |
4747
| **@r_masseater/doc-engine** | Documentation generation and validation engine — JSDoc extraction, Markdown generation, and plugin list sync |
48-
| **@r_masseater/ops-harbor-core** | Core library for Ops Harbor — alerts, work-item filtering, SQLite helpers, and shared type definitions |
4948
| **@r_masseater/repository-lint** | Repository linting rules — hook structure, workspace dependency constraints, and version-bump enforcement |
49+
| **@repo/ops-harbor-core** | Core library for Ops Harbor — alerts, work-item filtering, SQLite helpers, and shared type definitions |
5050
| **@repo/ts-config** | Shared TypeScript configuration presets — base, app, plugin, and library |
5151
| **@repo/vitest-config** | Shared Vitest configuration with standardized coverage thresholds for the monorepo |
5252

@@ -56,11 +56,11 @@ Claude Codeプラグインのマーケットプレイスリポジトリ。全プ
5656

5757
<!-- BEGIN:app-list (auto-generated, do not edit) -->
5858

59-
| アプリ | 説明 |
60-
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------- |
61-
| **@r_masseater/ops-harbor** | Ops Harbor local dashboard, daemon, and read-only MCP bridge |
62-
| **@r_masseater/ops-harbor-control-plane** | Local GitHub App control plane — webhook ingress, tunnel management, and configuration API for Ops Harbor |
63-
| **@r_masseater/sdd-webapp** | Web dashboard for SDD (Spec Driven Development) plugin |
59+
| アプリ | 説明 |
60+
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- |
61+
| **@r_masseater/ops-harbor** | Ops Harbor local dashboard, daemon, and read-only MCP bridge |
62+
| **@r_masseater/sdd-webapp** | Web dashboard for SDD (Spec Driven Development) plugin |
63+
| **@repo/ops-harbor-control-plane** | Local GitHub App control plane — webhook ingress, tunnel management, and configuration API for Ops Harbor |
6464

6565
<!-- END:app-list -->
6666

apps/ops-harbor-control-plane/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "@r_masseater/ops-harbor-control-plane",
2+
"name": "@repo/ops-harbor-control-plane",
33
"private": true,
44
"description": "Local GitHub App control plane — webhook ingress, tunnel management, and configuration API for Ops Harbor",
55
"type": "module",
@@ -12,7 +12,7 @@
1212
"test": "vitest run --passWithNoTests"
1313
},
1414
"dependencies": {
15-
"@r_masseater/ops-harbor-core": "workspace:*",
15+
"@repo/ops-harbor-core": "workspace:*",
1616
"better-sqlite3": "^11.9.1",
1717
"hono": "^4.10.2",
1818
"localtunnel": "^2.0.2",

apps/ops-harbor-control-plane/src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { ensureStoredConfigSecrets, readConfig } from "./lib/config";
44
import { ensureGitHubAppWebhookUrl } from "./lib/github";
5-
import { findAvailablePort } from "@r_masseater/ops-harbor-core";
5+
import { findAvailablePort } from "@repo/ops-harbor-core";
66
import { openTunnel } from "./lib/tunnel";
77
import { openControlPlaneApps, type RuntimeStatus } from "./server";
88

apps/ops-harbor-control-plane/src/lib/db.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import type {
66
WorkItem,
77
WorkItemAlert,
88
WorkItemFilter,
9-
} from "@r_masseater/ops-harbor-core";
9+
} from "@repo/ops-harbor-core";
1010
import {
1111
buildActivityWhereClause,
1212
buildAutomationPrompt,
1313
filterWorkItems,
1414
mapWorkItemsToAlertSummaries,
1515
openSqliteDatabase,
1616
parseJson,
17-
} from "@r_masseater/ops-harbor-core";
17+
} from "@repo/ops-harbor-core";
1818
import { mkdirSync } from "node:fs";
1919
import { dirname } from "node:path";
2020

apps/ops-harbor-control-plane/src/lib/github.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { WorkItem, WorkItemCheck, WorkItemReview } from "@r_masseater/ops-harbor-core";
2-
import { summarizeStatus } from "@r_masseater/ops-harbor-core";
1+
import type { WorkItem, WorkItemCheck, WorkItemReview } from "@repo/ops-harbor-core";
2+
import { summarizeStatus } from "@repo/ops-harbor-core";
33
import { createHmac, createSign } from "node:crypto";
44
import type { OpsHarborControlPlaneConfig } from "./config";
55

apps/ops-harbor-control-plane/src/lib/sync.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, test } from "vitest";
2-
import type { WorkItem } from "@r_masseater/ops-harbor-core";
2+
import type { WorkItem } from "@repo/ops-harbor-core";
33
import { hasWorkItemChanged } from "./sync.js";
44

55
function makeWorkItem(overrides: Partial<WorkItem> = {}): WorkItem {

apps/ops-harbor-control-plane/src/lib/sync.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import type {
44
GitHubReviewEventState,
55
SqliteDatabase,
66
WorkItem,
7-
} from "@r_masseater/ops-harbor-core";
8-
import { deriveAlerts } from "@r_masseater/ops-harbor-core";
7+
} from "@repo/ops-harbor-core";
8+
import { deriveAlerts } from "@repo/ops-harbor-core";
99
import { randomUUID } from "node:crypto";
1010
import type { OpsHarborControlPlaneConfig } from "./config";
1111
import {

apps/ops-harbor-control-plane/src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { SqliteDatabase } from "@r_masseater/ops-harbor-core";
2-
import { parseWorkItemFilterFromQuery } from "@r_masseater/ops-harbor-core";
1+
import type { SqliteDatabase } from "@repo/ops-harbor-core";
2+
import { parseWorkItemFilterFromQuery } from "@repo/ops-harbor-core";
33
import { Hono } from "hono";
44
import { cors } from "hono/cors";
55
import type { OpsHarborControlPlaneConfig, StoredOpsHarborControlPlaneConfig } from "./lib/config";

apps/ops-harbor/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"dev:client": "vite --host 127.0.0.1 --strictPort",
3333
"build": "bun run build:client && bun run build:server",
3434
"build:client": "vite build",
35-
"build:server": "bun build src/cli.ts --outdir dist --target bun && bun build src/mcp-server.ts --outdir dist --target bun",
35+
"build:server": "bun build src/cli.ts --outdir dist --target bun && bun build src/mcp-server.ts --outdir dist --target bun && bun build ../ops-harbor-control-plane/src/cli.ts --outfile dist/control-plane.js --target bun --external localtunnel",
3636
"check": "oxlint",
3737
"check:fix": "oxlint --fix",
3838
"typecheck": "tsgo --noEmit",
@@ -43,14 +43,16 @@
4343
},
4444
"dependencies": {
4545
"@modelcontextprotocol/sdk": "^1.25.1",
46-
"@r_masseater/ops-harbor-core": "workspace:*",
4746
"better-sqlite3": "^11.9.1",
4847
"hono": "^4.10.2",
48+
"localtunnel": "^2.0.2",
4949
"react": "^19.0.0",
5050
"react-dom": "^19.0.0",
5151
"valibot": "^1.2.0"
5252
},
5353
"devDependencies": {
54+
"@repo/ops-harbor-control-plane": "workspace:*",
55+
"@repo/ops-harbor-core": "workspace:*",
5456
"@repo/ts-config": "workspace:*",
5557
"@repo/vitest-config": "workspace:*",
5658
"@types/better-sqlite3": "^7.6.13",

apps/ops-harbor/src/cli.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,47 @@
11
#!/usr/bin/env bun
22

3+
import { existsSync } from "node:fs";
4+
import { readFile } from "node:fs/promises";
5+
import { homedir } from "node:os";
6+
import { join } from "node:path";
37
import { parseArgs } from "node:util";
4-
import { findAvailablePort } from "@r_masseater/ops-harbor-core";
8+
import { findAvailablePort } from "@repo/ops-harbor-core";
59
import { createServedApp } from "./server";
610

11+
type ChildProcessHandle = {
12+
kill(signal?: number): void;
13+
exited: Promise<number>;
14+
};
15+
16+
const CONTROL_PLANE_CONFIG_PATH = join(homedir(), ".config", "ops-harbor", "control-plane.json");
17+
18+
async function readConfiguredControlPlanePort(): Promise<number> {
19+
if (!existsSync(CONTROL_PLANE_CONFIG_PATH)) return 4130;
20+
try {
21+
const raw = await readFile(CONTROL_PLANE_CONFIG_PATH, "utf-8");
22+
const parsed = JSON.parse(raw) as { port?: unknown };
23+
if (typeof parsed.port === "number" && Number.isInteger(parsed.port)) return parsed.port;
24+
} catch {
25+
// Fall through to the default.
26+
}
27+
return 4130;
28+
}
29+
30+
async function isControlPlaneReachable(port: number): Promise<boolean> {
31+
const controller = new AbortController();
32+
const timeout = setTimeout(() => controller.abort(), 700);
33+
try {
34+
const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
35+
signal: controller.signal,
36+
});
37+
return response.ok;
38+
} catch {
39+
return false;
40+
} finally {
41+
clearTimeout(timeout);
42+
}
43+
}
44+
745
const subcommand = Bun.argv[2];
846

947
if (subcommand === "mcp") {
@@ -42,6 +80,24 @@ Options:
4280
process.exit(0);
4381
}
4482

83+
let managedControlPlane: ChildProcessHandle | null = null;
84+
const controlPlanePort = await readConfiguredControlPlanePort();
85+
if (!(await isControlPlaneReachable(controlPlanePort))) {
86+
const controlPlanePath = join(import.meta.dir, "control-plane.js");
87+
if (existsSync(controlPlanePath)) {
88+
const child = Bun.spawn({
89+
cmd: ["bun", controlPlanePath],
90+
stdout: "inherit",
91+
stderr: "inherit",
92+
});
93+
managedControlPlane = child;
94+
child.exited.then((exitCode) => {
95+
if (exitCode !== 0) console.error(`control-plane exited with code ${exitCode}`);
96+
if (managedControlPlane === child) managedControlPlane = null;
97+
});
98+
}
99+
}
100+
45101
const port = values.port ? Number(values.port) : await findAvailablePort(4131);
46102
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
47103
console.error(`Invalid port: ${values.port ?? String(port)} (expected 1-65535)`);
@@ -62,4 +118,14 @@ Options:
62118
hostname: "127.0.0.1",
63119
fetch: app.fetch,
64120
});
121+
122+
const shutdown = () => {
123+
if (managedControlPlane) {
124+
managedControlPlane.kill(15);
125+
managedControlPlane = null;
126+
}
127+
process.exit(0);
128+
};
129+
process.on("SIGINT", shutdown);
130+
process.on("SIGTERM", shutdown);
65131
}

0 commit comments

Comments
 (0)