fix: respect compose project name

This commit is contained in:
2026-08-30 19:11:50 +07:00 Unverified
parent 7f1d445981
commit e7efa9b44a
8 changed files with 58 additions and 7 deletions
+4 -1
View File
@@ -143,7 +143,7 @@ export default {
Operational defaults:
- `project`: current working directory name
- `project`: Compose top-level `name`, falling back to the current working directory name
- `composeFile`: the first recognized Compose filename in the working directory
- `registry`: `registry.neko-piranha.ts.net`
- `builders.amd64`: `kuber@astral-th`
@@ -151,6 +151,9 @@ Operational defaults:
- `builders.remoteRoot`: `kuber-build`
- `rolloutTimeoutMs`: `300000`
Project-name precedence is `.kuberrc.ts project`, Compose top-level `name`, then
the current working directory name.
Configuration hooks can be synchronous or asynchronous and receive mutable
values:
+1 -1
View File
@@ -15,7 +15,7 @@ import { up } from "./up";
export const main = defineCommand({
meta: {
name: "kuber",
version: "1.2.0",
version: "1.2.1",
description: "Docker Compose -> K8s translation layer",
},
args: {
+2
View File
@@ -20,6 +20,7 @@ type Hooks = Pick<
export type ResolvedKuberConfig = Hooks & {
project: string;
projectConfigured: boolean;
composeFile?: string;
registry: string;
builders: {
@@ -157,6 +158,7 @@ export async function loadConfig(
return {
project: raw.project?.trim() ?? basename(cwd),
projectConfigured: raw.project !== undefined,
composeFile: composeFile
? isAbsolute(composeFile)
? composeFile
+22 -1
View File
@@ -15,6 +15,14 @@ type AppContext = {
const appContext = new AsyncLocalStorage<AppContext>();
export function resolveProjectName(
config: Pick<ResolvedKuberConfig, "project" | "projectConfigured">,
compose: ComposeSpecification,
): string {
if (config.projectConfigured) return config.project;
return compose.name?.trim() || config.project;
}
export async function provideContext<T>(
fn: () => T,
configPath?: string,
@@ -26,6 +34,19 @@ export async function provideContext<T>(
? Promise.resolve(config.composeFile)
: resolveComposeFile(cwd),
);
const parsedCompose = createCachedGetter("parsedCompose", async () => {
const composeFile = await path();
if (!composeFile) throw new Error("Compose file cannot be found");
return readCompose(composeFile);
});
if (!config.projectConfigured) {
const composeFile = await path();
if (composeFile) {
config.project = resolveProjectName(config, await parsedCompose());
}
}
const getHookContext = createCachedGetter("hookContext", async () => {
const composeFile = await path();
if (!composeFile) throw new Error("Compose file cannot be found");
@@ -33,7 +54,7 @@ export async function provideContext<T>(
});
const compose = createCachedGetter("compose", async () => {
const context = await getHookContext();
const value = await readCompose(context.composeFile);
const value = await parsedCompose();
await config.compose?.(value, context);
return value;
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@dmgnr/kuber",
"version": "1.2.0",
"version": "1.2.1",
"description": "Docker Compose to Kubernetes translation layer",
"bin": {
"kuber": "dist/index.js"
+22 -1
View File
@@ -7,7 +7,7 @@ import {
resolveComposeArch,
withComposeArch,
} from "../../lib/arch";
import { createCachedGetter, ctx } from "../../lib/context";
import { createCachedGetter, ctx, resolveProjectName } from "../../lib/context";
import { toEnvVars, toTable } from "../../lib/format";
import { deepMerge, getPodContainerName, throwWhen } from "../../lib/shared";
@@ -131,6 +131,27 @@ describe("shared helpers", () => {
});
describe("context helpers", () => {
test("resolves project names using config, Compose, then directory precedence", () => {
expect(
resolveProjectName(
{ project: "directory", projectConfigured: false },
{ name: "compose" },
),
).toBe("compose");
expect(
resolveProjectName(
{ project: "config", projectConfigured: true },
{ name: "compose" },
),
).toBe("config");
expect(
resolveProjectName(
{ project: "directory", projectConfigured: false },
{},
),
).toBe("directory");
});
test("rejects access outside an application context", () => {
expect(() => ctx()).toThrow("Not in context");
});
+5 -1
View File
@@ -50,8 +50,12 @@ describe("Compose parsing", () => {
test("parses and validates a minimal Compose project", async () => {
const directory = await temporaryDirectory();
const path = join(directory, "compose.yml");
await writeFile(path, "services:\n app:\n image: nginx:latest\n");
await writeFile(
path,
"name: compose-project\nservices:\n app:\n image: nginx:latest\n",
);
expect(await readCompose(path)).toMatchObject({
name: "compose-project",
services: { app: { image: "nginx:latest" } },
});
});
Vendored
+1 -1
View File
@@ -35,7 +35,7 @@ export interface KuberBuildersConfig {
}
export interface KuberConfig {
/** Kubernetes namespace/project name. Defaults to the working directory name. */
/** Kubernetes namespace/project name. Overrides the Compose name and working directory. */
project?: string;
/** Compose file path, resolved relative to the working directory. */
composeFile?: string;