Compare commits

...
Author SHA1 Message Date
l0ng-ai 2900712698 fix(http-client): take the server port from the active Spring profiles' files
Generated requests read server.port and the context path only from
application.properties / application.yml, so a port set in
application-dev.yml under spring.profiles.active=dev was ignored. The
active profiles' files are now read over the base ones, the last profile
winning.
2026-11-13 13:55:15 +08:00
2 changed files with 34 additions and 10 deletions

No files matched your search

+21 -10
View File
@@ -193,25 +193,36 @@ const MAX_SUPERCLASSES = 5;
const dirname = (p: string) => p.replace(/[\\/][^\\/]*$/, "");
/** The Spring Boot configuration files for `sourcePath` (its module's `src/main/resources`), most specific first. */
export function configCandidates(sourcePath: string, projectRoot: string | null): string[] {
/** The Spring Boot configuration files (of `profile`) for `sourcePath` (its module's `src/main/resources`), most specific first. */
export function configCandidates(sourcePath: string, projectRoot: string | null, profile?: string): string[] {
const norm = sourcePath.replace(/\\/g, "/");
const at = norm.lastIndexOf("/src/main/");
const roots = at >= 0 ? [norm.slice(0, at)] : [];
if (projectRoot && !roots.includes(projectRoot)) roots.push(projectRoot.replace(/\\/g, "/").replace(/\/$/, ""));
return roots.flatMap((r) =>
["application.properties", "application.yml", "application.yaml"].map((f) => `${r}/src/main/resources/${f}`),
["properties", "yml", "yaml"].map((x) => `${r}/src/main/resources/application${profile ? `-${profile}` : ""}.${x}`),
);
}
export async function findHost(sourcePath: string, projectRoot: string | null, fs: SourceReader): Promise<string> {
const configs: Map<string, string>[] = [];
for (const path of configCandidates(sourcePath, projectRoot)) {
const text = await fs.read(path).catch(() => null);
if (text === null) continue;
configs.push(path.endsWith(".properties") ? parseProperties(text) : parseYaml(text));
}
return hostFromConfig(configs);
const read = async (paths: string[]) => {
const configs: Map<string, string>[] = [];
for (const path of paths) {
const text = await fs.read(path).catch(() => null);
if (text !== null) configs.push(path.endsWith(".properties") ? parseProperties(text) : parseYaml(text));
}
return configs;
};
const base = await read(configCandidates(sourcePath, projectRoot));
// `spring.profiles.active=dev`: `application-dev.*` overrides the base files (the last profile wins).
const active = base.map((c) => resolved(c.get("spring.profiles.active"))).find(Boolean);
const profiles = (active ?? "")
.split(",")
.map((p) => p.trim())
.filter((p) => /^[\w.-]+$/.test(p))
.reverse();
const profiled = await read(profiles.flatMap((p) => configCandidates(sourcePath, projectRoot, p)));
return hostFromConfig([...profiled, ...base]);
}
/**
@@ -252,6 +252,19 @@ describe("server address", () => {
expect(await findHost("/ws/api/src/main/java/a/A.java", "/ws", fs)).toBe("http://localhost:8001");
expect(await findHost("/ws/other/A.java", "/ws", fs)).toBe("http://localhost:9000");
});
it("reads the active profiles' files over the base ones", async () => {
const files: Record<string, string> = {
"/ws/api/src/main/resources/application.yml":
"server:\n port: 8001\n servlet:\n context-path: /api\nspring:\n profiles:\n active: ${PROFILE:local}, dev\n",
"/ws/api/src/main/resources/application-local.properties": "server.port=7000\n",
"/ws/api/src/main/resources/application-dev.yml": "server:\n port: 9100\n",
};
const fs: SourceReader = { read: async (p) => files[p] ?? null };
expect(await findHost("/ws/api/src/main/java/a/A.java", "/ws", fs)).toBe("http://localhost:9100/api");
delete files["/ws/api/src/main/resources/application-dev.yml"];
expect(await findHost("/ws/api/src/main/java/a/A.java", "/ws", fs)).toBe("http://localhost:7000/api");
});
});
describe("body types from other files", () => {