Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions __tests__/port-resolution.test.ts
Original file line number Diff line number Diff line change
@@ -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, unknown>): 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);
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
17 changes: 11 additions & 6 deletions packages/cli/src/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -117,15 +118,19 @@ export async function runDoctor(cwd: string): Promise<void> {
);
}

// 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`),
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/web/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Loading