feat: release 2.7.0-rc2
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { afterEach, expect, test } from "bun:test";
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { YAML } from "bun";
|
||||
import {
|
||||
addAppToCompose,
|
||||
createCompose,
|
||||
type AppOptions,
|
||||
} from "../../lib/scaffold";
|
||||
import { resolveAppInput } from "../../command/add";
|
||||
import { readCompose } from "../../lib/yaml";
|
||||
|
||||
const roots: string[] = [];
|
||||
afterEach(async () => {
|
||||
for (const path of roots.splice(0))
|
||||
await rm(path, { recursive: true, force: true });
|
||||
});
|
||||
async function root() {
|
||||
const path = await mkdtemp(join(tmpdir(), "kuber-add-test-"));
|
||||
roots.push(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
test("add preserves compose metadata, existing services and comments while adding claims/resources", async () => {
|
||||
const cwd = await root();
|
||||
const original =
|
||||
"name: shop\nx-owner: team\nservices:\n api:\n image: example/api:1 # keep this comment\nx-hook: untouched\n";
|
||||
await writeFile(join(cwd, "compose.yml"), original);
|
||||
await addAppToCompose(cwd, {
|
||||
name: "worker",
|
||||
path: ".",
|
||||
source: { kind: "image", image: "example/worker:2" },
|
||||
postgres: "user/db",
|
||||
s3: "key/bucket",
|
||||
cpu: "0.5",
|
||||
memory: "512m",
|
||||
replicas: 2,
|
||||
});
|
||||
const updated = await readFile(join(cwd, "compose.yml"), "utf8");
|
||||
expect(updated).toContain("image: example/api:1 # keep this comment");
|
||||
expect(updated).toContain("x-hook: untouched");
|
||||
const compose = await readCompose(join(cwd, "compose.yml"));
|
||||
expect(compose.services?.worker).toMatchObject({
|
||||
image: "example/worker:2",
|
||||
volumes: ["postgresql:user/db", "s3:key/bucket"],
|
||||
deploy: {
|
||||
replicas: 2,
|
||||
resources: { limits: { cpus: "0.5", memory: "512m" } },
|
||||
},
|
||||
});
|
||||
expect(compose.services?.api?.image).toBe("example/api:1");
|
||||
await expect(
|
||||
addAppToCompose(cwd, {
|
||||
name: "api",
|
||||
path: ".",
|
||||
source: { kind: "image", image: "x" },
|
||||
}),
|
||||
).rejects.toThrow("already exists");
|
||||
});
|
||||
|
||||
test("add inserts into an inline empty services mapping and preserves surrounding content", async () => {
|
||||
const cwd = await root();
|
||||
const original =
|
||||
"# before marker\nname: demo\nservices: {}\n# after marker\nx-hook: untouched\n";
|
||||
await writeFile(join(cwd, "compose.yml"), original);
|
||||
|
||||
await addAppToCompose(cwd, {
|
||||
name: "web",
|
||||
path: ".",
|
||||
source: { kind: "image", image: "example/web:1" },
|
||||
});
|
||||
|
||||
const updated = await readFile(join(cwd, "compose.yml"), "utf8");
|
||||
expect(updated).toContain("# before marker\nname: demo\nservices:\n");
|
||||
expect(updated).toContain("# after marker\nx-hook: untouched\n");
|
||||
expect(
|
||||
(await readCompose(join(cwd, "compose.yml"))).services?.web?.image,
|
||||
).toBe("example/web:1");
|
||||
});
|
||||
|
||||
test("unsupported inline services mappings fail without changing the compose file", async () => {
|
||||
const cwd = await root();
|
||||
const original =
|
||||
"# before marker\nname: demo\nservices: {existing: {image: busybox}}\n# after marker\n";
|
||||
await writeFile(join(cwd, "compose.yml"), original);
|
||||
|
||||
await expect(
|
||||
addAppToCompose(cwd, {
|
||||
name: "web",
|
||||
path: ".",
|
||||
source: { kind: "image", image: "example/web:1" },
|
||||
}),
|
||||
).rejects.toThrow("Cannot safely insert a service");
|
||||
|
||||
expect(await readFile(join(cwd, "compose.yml"), "utf8")).toBe(original);
|
||||
});
|
||||
|
||||
test("template creates paired files exclusively; collision rolls back and preserves compose", async () => {
|
||||
const cwd = await root();
|
||||
await mkdir(join(cwd, "app"));
|
||||
await createCompose(cwd, "sample", {
|
||||
name: "starter",
|
||||
path: ".",
|
||||
source: { kind: "image", image: "nginx:stable" },
|
||||
});
|
||||
const original = await readFile(join(cwd, "compose.yml"), "utf8");
|
||||
const app: AppOptions = {
|
||||
name: "app",
|
||||
path: "app",
|
||||
source: { kind: "template", template: "bun-service" },
|
||||
};
|
||||
await writeFile(join(cwd, "app", ".dockerignore"), "custom\n");
|
||||
await expect(addAppToCompose(cwd, app)).rejects.toThrow();
|
||||
expect(await readFile(join(cwd, "compose.yml"), "utf8")).toBe(original);
|
||||
expect(await Bun.file(join(cwd, "app", "Dockerfile")).exists()).toBe(false);
|
||||
expect(await readFile(join(cwd, "app", ".dockerignore"), "utf8")).toBe(
|
||||
"custom\n",
|
||||
);
|
||||
await rm(join(cwd, "app", ".dockerignore"));
|
||||
await addAppToCompose(cwd, app);
|
||||
expect(await Bun.file(join(cwd, "app", "Dockerfile")).exists()).toBe(true);
|
||||
expect(await Bun.file(join(cwd, "app", ".dockerignore")).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test("auto selects subproject and emits Buildpacks mode with path", async () => {
|
||||
const cwd = await root();
|
||||
await mkdir(join(cwd, "web"));
|
||||
await writeFile(join(cwd, "web", "package.json"), "{}");
|
||||
const app = await resolveAppInput(cwd, { nonInteractive: true });
|
||||
expect(app).toMatchObject({ path: "web", source: { kind: "auto" } });
|
||||
await createCompose(cwd, "sample", app);
|
||||
const compose = YAML.parse(
|
||||
await readFile(join(cwd, "compose.yml"), "utf8"),
|
||||
) as { services: Record<string, Record<string, unknown>> };
|
||||
expect(compose.services[app.name]!.build).toBe("auto");
|
||||
expect(compose.services[app.name]!["x-kuber-build-context"]).toBe("web");
|
||||
expect(
|
||||
(await readCompose(join(cwd, "compose.yml"))).services?.[app.name]?.build,
|
||||
).toBe("auto");
|
||||
expect(await Bun.file(join(cwd, "web", "Dockerfile")).exists()).toBe(false);
|
||||
});
|
||||
|
||||
test("rejects path traversal and symlink outside the project", async () => {
|
||||
const cwd = await root();
|
||||
await createCompose(cwd, "sample", {
|
||||
name: "main",
|
||||
path: ".",
|
||||
source: { kind: "image", image: "nginx" },
|
||||
});
|
||||
await expect(
|
||||
addAppToCompose(cwd, {
|
||||
name: "bad",
|
||||
path: "../elsewhere",
|
||||
source: { kind: "auto" },
|
||||
}),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
Reference in New Issue
Block a user