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
5 changes: 4 additions & 1 deletion packages/cli/src/core/resources/entity/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ export const EntitySchema = z.looseObject({
name: z
.string()
.min(1)
.regex(/^[a-zA-Z0-9]+$/, "Entity name must be alphanumeric only"),
.regex(
/^[a-zA-Z0-9_]+$/,
"Entity name can only contain letters, numbers, and underscores",
),
title: z.string().optional(),
description: z.string().optional(),
properties: z.record(z.string(), PropertyDefinitionSchema).default({}),
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/tests/cli/entities_push.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ describe("entities push command", () => {
t.expectResult(result).toContain("Updated: Product");
});

it("pushes entities with snake_case names", async () => {
await t.givenLoggedInWithProject(fixture("with-snake-case-entities"));
t.api.mockEntitiesPush({
created: ["line_items"],
updated: [],
deleted: [],
});

const result = await t.run("entities", "push");

t.expectResult(result).toSucceed();
t.expectResult(result).toContain("Created: line_items");
});

it("fails with helpful error when entity is missing required fields", async () => {
await t.givenLoggedInWithProject(fixture("invalid-entity"));

Expand Down
49 changes: 49 additions & 0 deletions packages/cli/tests/core/entity-schema.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { EntitySchema } from "@/core/resources/entity/schema.js";

describe("Entity name validation", () => {
it("accepts alphanumeric names", () => {
const result = EntitySchema.safeParse({ name: "Customer" });

expect(result.success).toBe(true);
});

it("accepts snake_case names", () => {
const result = EntitySchema.safeParse({ name: "line_items" });

expect(result.success).toBe(true);
});

it("accepts names with a leading underscore", () => {
const result = EntitySchema.safeParse({ name: "_internal" });

expect(result.success).toBe(true);
});

it("rejects names with dashes", () => {
const result = EntitySchema.safeParse({ name: "line-items" });

expect(result.success).toBe(false);
expect(result.error?.issues[0]?.message).toBe(
"Entity name can only contain letters, numbers, and underscores",
);
});

it("rejects names with spaces", () => {
const result = EntitySchema.safeParse({ name: "line items" });

expect(result.success).toBe(false);
});

it("rejects names with dots", () => {
const result = EntitySchema.safeParse({ name: "line.items" });

expect(result.success).toBe(false);
});

it("rejects empty names", () => {
const result = EntitySchema.safeParse({ name: "" });

expect(result.success).toBe(false);
});
});
9 changes: 9 additions & 0 deletions packages/cli/tests/core/project.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ describe("readProjectConfig", () => {
expect(result.agents).toEqual([]);
});

it("reads project with snake_case entity names", async () => {
const result = await readProjectConfig(
resolve(FIXTURES_DIR, "with-snake-case-entities"),
);

expect(result.entities).toHaveLength(1);
expect(result.entities[0].name).toBe("line_items");
});

it("reads project with functions and entities", async () => {
const result = await readProjectConfig(
resolve(FIXTURES_DIR, "with-functions-and-entities"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Base44 App Configuration
{
"id": "test-app-id"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "Snake Case Entities Test Project"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "line_items",
"type": "object",
"properties": {
"sku": {
"type": "string",
"description": "Stock keeping unit"
},
"quantity": {
"type": "number",
"description": "Quantity ordered"
}
},
"required": ["sku"]
}