Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/db/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { pool, closePool } from "./pool.js";
import { logger } from "../observability/logger.js";
import { isMainModule } from "../utils/is-main.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, "../..");
Expand Down Expand Up @@ -51,7 +52,7 @@ export async function migrate(): Promise<void> {
}
}

if (import.meta.url === `file://${process.argv[1]}`) {
if (isMainModule(import.meta.url)) {
migrate()
.then(async () => closePool())
.catch(async (error: unknown) => {
Expand Down
3 changes: 2 additions & 1 deletion src/db/seed.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { pool, closePool } from "./pool.js";
import { logger } from "../observability/logger.js";
import { isMainModule } from "../utils/is-main.js";

export const defaultEntityTypes = [
{
Expand Down Expand Up @@ -124,7 +125,7 @@ export async function seed(): Promise<void> {
logger.info({ count: defaultEntityTypes.length }, "seed complete");
}

if (import.meta.url === `file://${process.argv[1]}`) {
if (isMainModule(import.meta.url)) {
seed()
.then(async () => closePool())
.catch(async (error: unknown) => {
Expand Down
3 changes: 2 additions & 1 deletion src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { graphService } from "../services/graph-service.js";
import { logger } from "../observability/logger.js";
import { subscribeModelCallLogs, type ModelCallLogRecord } from "../observability/model-call-log.js";
import type { SearchProgressEvent } from "../types.js";
import { isMainModule } from "../utils/is-main.js";

export function buildMcpServer(): McpServer {
const server = new McpServer({
Expand Down Expand Up @@ -192,7 +193,7 @@ export async function startMcpServer(): Promise<void> {
logger.info("SAG MCP stdio server started");
}

if (import.meta.url === `file://${process.argv[1]}`) {
if (isMainModule(import.meta.url)) {
startMcpServer().catch((error: unknown) => {
logger.error({ error }, "mcp server failed");
process.exit(1);
Expand Down
24 changes: 24 additions & 0 deletions src/utils/is-main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { fileURLToPath } from 'node:url';
import path from 'node:path';

export function isMainModule(metaUrl: string): boolean {
if (!process.argv[1]) return false;

try {
const modulePath = fileURLToPath(metaUrl);
let entryPath = process.argv[1];

const stripExt = (p: string) => {
const ext = path.extname(p);
return ext ? p.slice(0, -ext.length) : p;
};

// Normalize path separators and lowercase on Windows for case-insensitivity
const normalize = (p: string) =>
process.platform === 'win32' ? path.resolve(p).toLowerCase() : path.resolve(p);

return stripExt(normalize(modulePath)) === stripExt(normalize(entryPath));
} catch {
return false;
}
}
59 changes: 59 additions & 0 deletions test/is-main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, it, expect, afterEach } from "vitest";
import { isMainModule } from "../src/utils/is-main.js";
import { pathToFileURL } from "node:url";

describe("isMainModule", () => {
const originalArgv = process.argv;
const originalPlatform = process.platform;

afterEach(() => {
Object.defineProperty(process, 'argv', { value: originalArgv });
Object.defineProperty(process, 'platform', { value: originalPlatform });
});

it("should return true when module matches argv[1] on Linux", () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
Object.defineProperty(process, 'argv', {
value: ['node', '/app/src/db/migrate.ts']
});

const metaUrl = pathToFileURL('/app/src/db/migrate.ts').href;
expect(isMainModule(metaUrl)).toBe(true);
});

it("should return false when module does not match argv[1]", () => {
Object.defineProperty(process, 'argv', {
value: ['node', '/app/src/db/seed.ts']
});

const metaUrl = pathToFileURL('/app/src/db/migrate.ts').href;
expect(isMainModule(metaUrl)).toBe(false);
});

it("should return true when module matches argv[1] on Windows (different case)", () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
Object.defineProperty(process, 'argv', {
value: ['node', 'c:\\app\\src\\db\\migrate.ts']
});

// simulate import.meta.url which has uppercase drive letter
const metaUrl = pathToFileURL('C:\\app\\src\\db\\migrate.ts').href;
expect(isMainModule(metaUrl)).toBe(true);
});

it("should return true even when argv[1] lacks file extension", () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
Object.defineProperty(process, 'argv', {
value: ['node', '/app/src/db/migrate']
});

const metaUrl = pathToFileURL('/app/src/db/migrate.ts').href;
expect(isMainModule(metaUrl)).toBe(true);
});

it("should return false if process.argv[1] is undefined", () => {
Object.defineProperty(process, 'argv', { value: ['node'] });
const metaUrl = pathToFileURL('/app/src/db/migrate.ts').href;
expect(isMainModule(metaUrl)).toBe(false);
});
});