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
12 changes: 10 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ cli/
│ │ │ ├── api.ts # uploadSite() - reads archive, sends to API
│ │ │ ├── deploy.ts # deploySite() - validates, creates tar.gz, uploads
│ │ │ └── index.ts
│ │ ├── types/ # TypeScript type generation
│ │ │ ├── generator.ts # generateTypesFile() - creates types.d.ts
│ │ │ ├── update-project.ts # updateProjectConfig() - updates tsconfig.json
│ │ │ └── index.ts
│ │ ├── utils/
│ │ │ ├── fs.ts # File system utilities
│ │ │ └── index.ts
Expand Down Expand Up @@ -116,8 +120,11 @@ cli/
│ │ │ └── push.ts
│ │ ├── functions/
│ │ │ └── deploy.ts
│ │ └── site/
│ │ └── deploy.ts
│ │ ├── site/
│ │ │ └── deploy.ts
│ │ └── types/
│ │ ├── index.ts # getTypesCommand(context) - parent command
│ │ └── generate.ts # getTypesGenerateCommand(context) factory
│ ├── telemetry/ # Error reporting and telemetry
│ │ ├── consts.ts # PostHog API key, env var names
│ │ ├── posthog.ts # PostHog client singleton
Expand Down Expand Up @@ -584,6 +591,7 @@ When an error is thrown, the CLI displays:
| `FILE_NOT_FOUND` | `FileNotFoundError` | File doesn't exist |
| `FILE_READ_ERROR` | `FileReadError` | Can't read/write file |
| `INTERNAL_ERROR` | `InternalError` | Unexpected error |
| `TYPE_GENERATION_ERROR` | `TypeGenerationError` | Type generation failed for entity |

### CLIExitError (Special Case)

Expand Down
30 changes: 30 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,21 @@
"@biomejs/biome": "^2.0.0",
"@clack/prompts": "^0.11.0",
"@types/bun": "^1.2.15",
"@types/common-tags": "^1.8.4",
"@types/ejs": "^3.1.5",
"@types/json-schema": "^7.0.15",
"@types/lodash.kebabcase": "^4.1.9",
"@types/node": "^22.10.5",
"@types/tar": "^6.1.13",
"@vercel/detect-agent": "^1.1.0",
"chalk": "^5.6.2",
"commander": "^12.1.0",
"common-tags": "^1.8.2",
"ejs": "^3.1.10",
"execa": "^9.6.1",
"front-matter": "^4.0.2",
"globby": "^16.1.0",
"json-schema-to-typescript": "^15.0.4",
"json5": "^2.2.3",
"ky": "^1.14.2",
"lodash.kebabcase": "^4.1.1",
Expand Down
38 changes: 38 additions & 0 deletions src/cli/commands/types/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Command } from "commander";
import type { CLIContext } from "@/cli/types.js";
import { runCommand, runTask } from "@/cli/utils/index.js";
import type { RunCommandResult } from "@/cli/utils/runCommand.js";
import { readProjectConfig } from "@/core/index.js";
import { generateTypesFile, updateProjectConfig } from "@/core/types/index.js";

const TYPES_FILE_PATH = "base44/.types/types.d.ts";

async function generateTypesAction(): Promise<RunCommandResult> {
const { entities, functions, agents, project } = await readProjectConfig();

await runTask("Generating types", async () => {
await generateTypesFile({ entities, functions, agents });
});

const tsconfigUpdated = await updateProjectConfig(project.root);

return {
outroMessage: tsconfigUpdated
? `Generated ${TYPES_FILE_PATH} and updated tsconfig.json`
: `Generated ${TYPES_FILE_PATH}`,
};
}

export function getTypesGenerateCommand(context: CLIContext): Command {
return new Command("generate")
.description(
"Generate TypeScript declaration file (types.d.ts) from project resources"
)
.action(async () => {
await runCommand(
() => generateTypesAction(),
{ requireAuth: false },
context
);
});
}
9 changes: 9 additions & 0 deletions src/cli/commands/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Command } from "commander";
import type { CLIContext } from "@/cli/types.js";
import { getTypesGenerateCommand } from "./generate.js";

export function getTypesCommand(context: CLIContext): Command {
return new Command("types")
.description("Manage TypeScript type generation")
.addCommand(getTypesGenerateCommand(context));
}
4 changes: 4 additions & 0 deletions src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getCreateCommand } from "@/cli/commands/project/create.js";
import { getDeployCommand } from "@/cli/commands/project/deploy.js";
import { getLinkCommand } from "@/cli/commands/project/link.js";
import { getSiteCommand } from "@/cli/commands/site/index.js";
import { getTypesCommand } from "@/cli/commands/types/index.js";
import packageJson from "../../package.json";
import type { CLIContext } from "./types.js";

Expand Down Expand Up @@ -50,5 +51,8 @@ export function createProgram(context: CLIContext): Command {
// Register site commands
program.addCommand(getSiteCommand(context));

// Register types command
program.addCommand(getTypesCommand(context), { hidden: true });

return program;
}
2 changes: 1 addition & 1 deletion src/cli/utils/version-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function checkForUpgrade(): Promise<UpgradeInfo | null> {

try {
const { stdout } = await execa("npm", ["view", "base44", "version"], {
timeout: 500,
timeout: 1000,
shell: true,
env: { CI: "1" },
});
Expand Down
10 changes: 9 additions & 1 deletion src/core/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { PROJECT_SUBDIR } from "@/core/consts.js";
import {
PROJECT_SUBDIR,
TYPES_FILENAME,
TYPES_OUTPUT_SUBDIR,
} from "@/core/consts.js";
import {
type TestOverrides,
TestOverridesSchema,
Expand Down Expand Up @@ -31,6 +35,10 @@ export function getAppConfigPath(projectRoot: string): string {
return join(projectRoot, PROJECT_SUBDIR, ".app.jsonc");
}

export function getTypesOutputPath(projectRoot: string): string {
return join(projectRoot, PROJECT_SUBDIR, TYPES_OUTPUT_SUBDIR, TYPES_FILENAME);
}

export function getBase44ApiUrl(): string {
return process.env.BASE44_API_URL || "https://app.base44.com";
}
Expand Down
4 changes: 4 additions & 0 deletions src/core/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@ export const PROJECT_CONFIG_PATTERNS = [
`config.${CONFIG_FILE_EXTENSION_GLOB}`,
];

// Types generation
export const TYPES_OUTPUT_SUBDIR = ".types";
export const TYPES_FILENAME = "types.d.ts";

// Auth
export const AUTH_CLIENT_ID = "base44_cli";
22 changes: 22 additions & 0 deletions src/core/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,28 @@ export class InternalError extends SystemError {
}
}

/**
* Thrown when type generation fails for an entity.
*/
export class TypeGenerationError extends SystemError {
readonly code = "TYPE_GENERATION_ERROR";
readonly entityName?: string;

constructor(message: string, entityName?: string, cause?: unknown) {
super(message, {
hints: [
{
message: entityName
? `Check the schema for entity "${entityName}"`
: "Check your entity schemas for errors",
},
],
cause: cause instanceof Error ? cause : undefined,
});
this.entityName = entityName;
}
}

// ============================================================================
// Type Guards
// ============================================================================
Expand Down
3 changes: 0 additions & 3 deletions src/core/resources/entity/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ const PropertyTypeSchema = z.enum([
"boolean",
"array",
"object",
"binary",
]);

const StringFormatSchema = z.enum([
Expand All @@ -120,8 +119,6 @@ const StringFormatSchema = z.enum([
"ipv4",
"ipv6",
"uuid",
"file",
"regex",
]);

const PropertyDefinitionSchema = z.object({
Expand Down
Loading
Loading