Skip to content

Commit d2a6787

Browse files
veksenclaude
andcommitted
feat(typeorm,mikroorm): add project root detection via tsconfig.json
In deployed environments, process.cwd() may not be the project root (e.g., when the start script does `cd .amplify-hosting/compute/default/ && node app.js`). This adds findProjectRoot() which walks up from cwd looking for tsconfig.json to find the real project root. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 274a1a7 commit d2a6787

4 files changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { existsSync } from "node:fs";
2+
import { dirname, join } from "node:path";
3+
4+
let cachedProjectRoot: string | undefined;
5+
6+
/**
7+
* Finds the project root by walking up from `process.cwd()` looking for `tsconfig.json`.
8+
*
9+
* In deployed environments, `process.cwd()` may not be the project root
10+
* (e.g., `cd .amplify-hosting/compute/default/ && node app.js`).
11+
* Walking up to find `tsconfig.json` — which is never copied to deployment directories —
12+
* gives us the real project root.
13+
*
14+
* The result is cached since the project root doesn't change during a process's lifetime.
15+
*/
16+
export function findProjectRoot(): string {
17+
if (cachedProjectRoot !== undefined) {
18+
return cachedProjectRoot;
19+
}
20+
let projectRoot = process.cwd();
21+
for (let d = projectRoot; d !== dirname(d); d = dirname(d)) {
22+
if (existsSync(join(d, "tsconfig.json"))) {
23+
projectRoot = d;
24+
break;
25+
}
26+
}
27+
cachedProjectRoot = projectRoot;
28+
return projectRoot;
29+
}
30+
31+
/** @internal Exposed for testing only */
32+
export function _resetProjectRootCache() {
33+
cachedProjectRoot = undefined;
34+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { test } from "node:test";
2+
import assert from "node:assert";
3+
import { existsSync } from "node:fs";
4+
import { join } from "node:path";
5+
import { findProjectRoot, _resetProjectRootCache } from "../src/path.js";
6+
7+
test("findProjectRoot", async (t) => {
8+
t.afterEach(() => {
9+
_resetProjectRootCache();
10+
});
11+
12+
await t.test("returns a directory containing tsconfig.json", () => {
13+
const root = findProjectRoot();
14+
assert.ok(
15+
existsSync(join(root, "tsconfig.json")),
16+
`Expected ${root} to contain tsconfig.json`,
17+
);
18+
});
19+
20+
await t.test("caches the result across calls", () => {
21+
const first = findProjectRoot();
22+
const second = findProjectRoot();
23+
assert.strictEqual(first, second);
24+
});
25+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { existsSync } from "node:fs";
2+
import { dirname, join } from "node:path";
3+
4+
let cachedProjectRoot: string | undefined;
5+
6+
/**
7+
* Finds the project root by walking up from `process.cwd()` looking for `tsconfig.json`.
8+
*
9+
* In deployed environments, `process.cwd()` may not be the project root
10+
* (e.g., `cd .amplify-hosting/compute/default/ && node app.js`).
11+
* Walking up to find `tsconfig.json` — which is never copied to deployment directories —
12+
* gives us the real project root.
13+
*
14+
* The result is cached since the project root doesn't change during a process's lifetime.
15+
*/
16+
export function findProjectRoot(): string {
17+
if (cachedProjectRoot !== undefined) {
18+
return cachedProjectRoot;
19+
}
20+
let projectRoot = process.cwd();
21+
for (let d = projectRoot; d !== dirname(d); d = dirname(d)) {
22+
if (existsSync(join(d, "tsconfig.json"))) {
23+
projectRoot = d;
24+
break;
25+
}
26+
}
27+
cachedProjectRoot = projectRoot;
28+
return projectRoot;
29+
}
30+
31+
/** @internal Exposed for testing only */
32+
export function _resetProjectRootCache() {
33+
cachedProjectRoot = undefined;
34+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { test } from "node:test";
2+
import assert from "node:assert";
3+
import { existsSync } from "node:fs";
4+
import { join } from "node:path";
5+
import { findProjectRoot, _resetProjectRootCache } from "../src/path.js";
6+
7+
test("findProjectRoot", async (t) => {
8+
t.afterEach(() => {
9+
_resetProjectRootCache();
10+
});
11+
12+
await t.test("returns a directory containing tsconfig.json", () => {
13+
const root = findProjectRoot();
14+
assert.ok(
15+
existsSync(join(root, "tsconfig.json")),
16+
`Expected ${root} to contain tsconfig.json`,
17+
);
18+
});
19+
20+
await t.test("caches the result across calls", () => {
21+
const first = findProjectRoot();
22+
const second = findProjectRoot();
23+
assert.strictEqual(first, second);
24+
});
25+
});

0 commit comments

Comments
 (0)