diff --git a/__tests__/port-resolution.test.ts b/__tests__/port-resolution.test.ts new file mode 100644 index 0000000..f8269ed --- /dev/null +++ b/__tests__/port-resolution.test.ts @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +import { describe, expect, test } from 'bun:test'; +import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { stablePort } from '../packages/cli/src/init.ts'; +import { resolvePort, resolveProjectName } from '../packages/web/src/index.ts'; + +const PORT_BASE = 7474; +const PORT_SPAN = 64; + +const withConfig = (cfg: Record): string => { + const root = mkdtempSync(join(tmpdir(), 'rig-port-')); + mkdirSync(join(root, '.rig'), { recursive: true }); + writeFileSync(join(root, '.rig', 'config.json'), JSON.stringify(cfg)); + return root; +}; + +describe('#17 — doctor/serve read the resolved per-project port', () => { + test('resolvePort: explicit flag wins over config and default', () => { + const root = withConfig({ port: 7510 }); + expect(resolvePort(root, 9999)).toBe(9999); + }); + + test('resolvePort: reads port from .rig/config.json', () => { + const root = withConfig({ port: 7510 }); + expect(resolvePort(root)).toBe(7510); + }); + + test('resolvePort: falls back to 7474 with no config', () => { + const root = mkdtempSync(join(tmpdir(), 'rig-port-')); + expect(resolvePort(root)).toBe(PORT_BASE); + }); + + test('resolveProjectName: reads name from config, else basename', () => { + expect(resolveProjectName(withConfig({ name: 'alpha-svc' }))).toBe('alpha-svc'); + const noName = mkdtempSync(join(tmpdir(), 'rig-named-')); + expect(resolveProjectName(noName).length).toBeGreaterThan(0); + }); + + test('stablePort: deterministic and inside the 7474–7537 window', () => { + const root = mkdtempSync(join(tmpdir(), 'rig-stable-')); + const p = stablePort(root); + expect(p).toBe(stablePort(root)); + expect(p).toBeGreaterThanOrEqual(PORT_BASE); + expect(p).toBeLessThan(PORT_BASE + PORT_SPAN); + }); +}); diff --git a/package.json b/package.json index 1b76c47..e18b1a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rig-constellation", - "version": "0.1.2", + "version": "0.1.3", "private": false, "type": "module", "description": "Local-first semantic knowledge graph", diff --git a/packages/cli/src/doctor.ts b/packages/cli/src/doctor.ts index ef8ff1e..4fbb342 100644 --- a/packages/cli/src/doctor.ts +++ b/packages/cli/src/doctor.ts @@ -14,6 +14,7 @@ const dirSizeBytes = (dir: string): number => { return total; }; import { Rig, RIG_DIR_NAME } from '@rig/core'; +import { resolvePort } from '@rig/web'; interface Check { name: string; @@ -117,15 +118,19 @@ export async function runDoctor(cwd: string): Promise { ); } - // 4. port 7474 - const portFree = await checkPort(7474); + // 4. map server port — resolved per project (.rig/config.json → 7474), not + // hardcoded. On a conflict, `rig serve` walks forward up to +64, so a busy + // port is a note, not a blocker. + const port = resolvePort(root); + const portLabel = `port ${port}`; + const portFree = await checkPort(port); if (portFree) { - checks.push(ok('port 7474', 'available for `rig serve --web`')); - } else if (await isRigServer(7474)) { - checks.push(ok('port 7474', 'rig map server already running here')); + checks.push(ok(portLabel, 'available for `rig serve --web`')); + } else if (await isRigServer(port)) { + checks.push(ok(portLabel, 'rig map server already running here')); } else { checks.push( - warn('port 7474', 'in use by another process', '`lsof -i :7474` to find the owner, or pass --port'), + warn(portLabel, 'in use', `\`lsof -i :${port}\` to find the owner — rig will fall forward to the next free port`), ); } diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index 875669a..cb6ac9c 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -1,3 +1,4 @@ // SPDX-License-Identifier: Apache-2.0 export { serveWeb } from './server.ts'; export type { ServeWebOptions, WebHandle } from './server.ts'; +export { resolvePort, resolveProjectName } from './listen.ts';