From 21da03fa3d7502af70046b97f8627026f099e47f Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Wed, 7 Jan 2026 17:43:44 +0200 Subject: [PATCH 01/13] fix issue with error throwing, add refresh mechanism --- src/core/auth/schema.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/auth/schema.ts b/src/core/auth/schema.ts index abe39a2d8..dbfb41360 100644 --- a/src/core/auth/schema.ts +++ b/src/core/auth/schema.ts @@ -4,13 +4,20 @@ import { z } from "zod"; export const AuthDataSchema = z.object({ accessToken: z.string().min(1, "Token cannot be empty"), refreshToken: z.string().min(1, "Refresh token cannot be empty"), +<<<<<<< HEAD expiresAt: z.number().int().positive("Expires at must be a positive integer"), +======= +>>>>>>> 827a40c (fix issue with error throwing, add refresh mechanism) email: z.email(), name: z.string().min(1, "Name cannot be empty"), }); export type AuthData = z.infer; +<<<<<<< HEAD +======= +// API response schemas +>>>>>>> 827a40c (fix issue with error throwing, add refresh mechanism) export const DeviceCodeResponseSchema = z .object({ device_code: z.string().min(1, "Device code cannot be empty"), From f4fe358c3dba47bb125f6c1e91e3f069cac00be2 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Thu, 8 Jan 2026 11:27:16 +0200 Subject: [PATCH 02/13] added refresh logic fixes --- src/core/auth/schema.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/core/auth/schema.ts b/src/core/auth/schema.ts index dbfb41360..abe39a2d8 100644 --- a/src/core/auth/schema.ts +++ b/src/core/auth/schema.ts @@ -4,20 +4,13 @@ import { z } from "zod"; export const AuthDataSchema = z.object({ accessToken: z.string().min(1, "Token cannot be empty"), refreshToken: z.string().min(1, "Refresh token cannot be empty"), -<<<<<<< HEAD expiresAt: z.number().int().positive("Expires at must be a positive integer"), -======= ->>>>>>> 827a40c (fix issue with error throwing, add refresh mechanism) email: z.email(), name: z.string().min(1, "Name cannot be empty"), }); export type AuthData = z.infer; -<<<<<<< HEAD -======= -// API response schemas ->>>>>>> 827a40c (fix issue with error throwing, add refresh mechanism) export const DeviceCodeResponseSchema = z .object({ device_code: z.string().min(1, "Device code cannot be empty"), From cabb0b1489bfc1d3107be302ed2767970b14dc28 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Thu, 8 Jan 2026 14:45:59 +0200 Subject: [PATCH 03/13] fix package version issue and add a userInfo method --- src/core/auth/api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/auth/api.ts b/src/core/auth/api.ts index 7255a7542..8d85130ab 100644 --- a/src/core/auth/api.ts +++ b/src/core/auth/api.ts @@ -12,6 +12,7 @@ import type { } from "./schema.js"; import { AUTH_CLIENT_ID } from "../consts.js"; import authClient from "./authClient.js"; +import httpClient from "../utils/httpClient.js"; export async function generateDeviceCode(): Promise { const response = await authClient.post("oauth/device/code", { From 14a58f2f16c6c9f1304fe992f121a09d604badbe Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Thu, 8 Jan 2026 15:10:28 +0200 Subject: [PATCH 04/13] actual call the getUserInfo service --- src/core/auth/api.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/auth/api.ts b/src/core/auth/api.ts index 8d85130ab..24fbc2d53 100644 --- a/src/core/auth/api.ts +++ b/src/core/auth/api.ts @@ -132,12 +132,17 @@ export async function renewAccessToken( return result.data; } +<<<<<<< HEAD export async function getUserInfo( accessToken: string ): Promise { const response = await authClient.get("oauth/userinfo", { headers: { Authorization: `Bearer ${accessToken}` }, }); +======= +export async function getUserInfo(): Promise { + const response = await httpClient.get("oauth/userinfo"); +>>>>>>> 85f1e63 (actual call the getUserInfo service) if (!response.ok) { throw new AuthApiError(`Failed to fetch user info: ${response.status}`); From e00f079d87f49f906dc47626ab090daca26df2b0 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 09:48:41 +0200 Subject: [PATCH 05/13] added userInfo call and log --- src/cli/commands/auth/login.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/commands/auth/login.ts b/src/cli/commands/auth/login.ts index 29710c765..f4c836153 100644 --- a/src/cli/commands/auth/login.ts +++ b/src/cli/commands/auth/login.ts @@ -14,6 +14,7 @@ import type { UserInfoResponse, } from "@core/auth/index.js"; import { runCommand, runTask } from "../../utils/index.js"; +import { UserInfo } from "node:os"; async function generateAndDisplayDeviceCode(): Promise { const deviceCodeResponse = await runTask( @@ -84,6 +85,10 @@ async function saveAuthData( response: TokenResponse, userInfo: UserInfoResponse ): Promise { +<<<<<<< HEAD +======= + // For now, we store placeholder values until a /userinfo endpoint is available +>>>>>>> 248994d (added userInfo call and log) const expiresAt = Date.now() + response.expiresIn * 1000; await writeAuth({ From b95400a87179419ad7f8dd388addcabed781ffe2 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 10:39:40 +0200 Subject: [PATCH 06/13] small ui changes --- src/cli/commands/auth/login.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/commands/auth/login.ts b/src/cli/commands/auth/login.ts index f4c836153..87d85ce63 100644 --- a/src/cli/commands/auth/login.ts +++ b/src/cli/commands/auth/login.ts @@ -14,7 +14,6 @@ import type { UserInfoResponse, } from "@core/auth/index.js"; import { runCommand, runTask } from "../../utils/index.js"; -import { UserInfo } from "node:os"; async function generateAndDisplayDeviceCode(): Promise { const deviceCodeResponse = await runTask( From 6c0d4ba47d9e0e408772ee4911ce0c37253d2b8d Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 11:46:48 +0200 Subject: [PATCH 07/13] get user info with access token --- src/core/auth/api.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/core/auth/api.ts b/src/core/auth/api.ts index 24fbc2d53..8d85130ab 100644 --- a/src/core/auth/api.ts +++ b/src/core/auth/api.ts @@ -132,17 +132,12 @@ export async function renewAccessToken( return result.data; } -<<<<<<< HEAD export async function getUserInfo( accessToken: string ): Promise { const response = await authClient.get("oauth/userinfo", { headers: { Authorization: `Bearer ${accessToken}` }, }); -======= -export async function getUserInfo(): Promise { - const response = await httpClient.get("oauth/userinfo"); ->>>>>>> 85f1e63 (actual call the getUserInfo service) if (!response.ok) { throw new AuthApiError(`Failed to fetch user info: ${response.status}`); From a75dd2d66fcabc2a51a2fc6803f9da111586d22a Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 11:52:16 +0200 Subject: [PATCH 08/13] tiny changes --- src/cli/commands/auth/login.ts | 4 ---- src/core/auth/api.ts | 1 - 2 files changed, 5 deletions(-) diff --git a/src/cli/commands/auth/login.ts b/src/cli/commands/auth/login.ts index 87d85ce63..29710c765 100644 --- a/src/cli/commands/auth/login.ts +++ b/src/cli/commands/auth/login.ts @@ -84,10 +84,6 @@ async function saveAuthData( response: TokenResponse, userInfo: UserInfoResponse ): Promise { -<<<<<<< HEAD -======= - // For now, we store placeholder values until a /userinfo endpoint is available ->>>>>>> 248994d (added userInfo call and log) const expiresAt = Date.now() + response.expiresIn * 1000; await writeAuth({ diff --git a/src/core/auth/api.ts b/src/core/auth/api.ts index 8d85130ab..7255a7542 100644 --- a/src/core/auth/api.ts +++ b/src/core/auth/api.ts @@ -12,7 +12,6 @@ import type { } from "./schema.js"; import { AUTH_CLIENT_ID } from "../consts.js"; import authClient from "./authClient.js"; -import httpClient from "../utils/httpClient.js"; export async function generateDeviceCode(): Promise { const response = await authClient.post("oauth/device/code", { From a278b61e65c4d4995466e9ce2b478fdb0bc2c711 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 16:02:30 +0200 Subject: [PATCH 09/13] added create app step --- package-lock.json | 83 ++++++++++++++++++- package.json | 3 + src/cli/commands/project/init.ts | 75 +++++++++++++++++ src/cli/commands/project/show-project.ts | 2 +- src/cli/index.ts | 2 + src/cli/utils/banner.ts | 18 ++++ src/cli/utils/index.ts | 2 + src/cli/utils/prompts.ts | 22 +++++ src/cli/utils/runCommand.ts | 5 ++ src/core/auth/api.ts | 2 +- src/core/auth/authClient.ts | 2 +- src/core/auth/config.ts | 2 +- src/core/config.ts | 63 ++++++++++++++ src/core/index.ts | 3 +- src/core/project/api.ts | 18 ++++ src/core/{config => project}/baseResource.ts | 0 .../{config/project.ts => project/config.ts} | 2 +- src/core/project/index.ts | 5 ++ src/core/project/init.ts | 63 ++++++++++++++ src/core/project/resource.ts | 4 + src/core/{config/app.ts => project/schema.ts} | 6 ++ src/core/project/templates/config.jsonc.ejs | 17 ++++ src/core/project/templates/env.local.ejs | 6 ++ src/core/project/templates/index.ts | 30 +++++++ src/core/resources/function/config.ts | 2 +- src/core/utils/fs.ts | 62 +++++++------- src/core/utils/httpClient.ts | 2 +- templates/config.jsonc.ejs | 17 ++++ templates/env.local.ejs | 6 ++ templates/index.ts | 30 +++++++ tsdown.config.mjs | 1 + 31 files changed, 515 insertions(+), 40 deletions(-) create mode 100644 src/cli/commands/project/init.ts create mode 100644 src/cli/utils/banner.ts create mode 100644 src/cli/utils/prompts.ts create mode 100644 src/core/config.ts create mode 100644 src/core/project/api.ts rename src/core/{config => project}/baseResource.ts (100%) rename src/core/{config/project.ts => project/config.ts} (99%) create mode 100644 src/core/project/index.ts create mode 100644 src/core/project/init.ts create mode 100644 src/core/project/resource.ts rename src/core/{config/app.ts => project/schema.ts} (77%) create mode 100644 src/core/project/templates/config.jsonc.ejs create mode 100644 src/core/project/templates/env.local.ejs create mode 100644 src/core/project/templates/index.ts create mode 100644 templates/config.jsonc.ejs create mode 100644 templates/env.local.ejs create mode 100644 templates/index.ts diff --git a/package-lock.json b/package-lock.json index 307f58c16..57498fac8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@clack/prompts": "^0.11.0", "chalk": "^5.6.2", "commander": "^12.1.0", + "dotenv": "^17.2.3", + "ejs": "^3.1.10", "globby": "^16.1.0", "jsonc-parser": "^3.3.1", "ky": "^1.14.2", @@ -23,6 +25,7 @@ }, "devDependencies": { "@stylistic/eslint-plugin": "^5.6.1", + "@types/ejs": "^3.1.5", "@types/node": "^22.10.5", "@typescript-eslint/eslint-plugin": "^8.51.0", "@typescript-eslint/parser": "^8.51.0", @@ -1625,6 +1628,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2209,6 +2219,12 @@ "url": "https://github.com/sponsors/sxzz" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -2239,7 +2255,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -2266,7 +2281,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2716,6 +2730,18 @@ "node": ">=0.10.0" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dts-resolver": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-2.1.3.tgz", @@ -2752,6 +2778,21 @@ "node": ">= 0.4" } }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -3481,6 +3522,27 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4368,6 +4430,23 @@ "dev": true, "license": "ISC" }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", diff --git a/package.json b/package.json index e8c261ad9..a57fe2ec2 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "@clack/prompts": "^0.11.0", "chalk": "^5.6.2", "commander": "^12.1.0", + "dotenv": "^17.2.3", + "ejs": "^3.1.10", "globby": "^16.1.0", "jsonc-parser": "^3.3.1", "ky": "^1.14.2", @@ -44,6 +46,7 @@ }, "devDependencies": { "@stylistic/eslint-plugin": "^5.6.1", + "@types/ejs": "^3.1.5", "@types/node": "^22.10.5", "@typescript-eslint/eslint-plugin": "^8.51.0", "@typescript-eslint/parser": "^8.51.0", diff --git a/src/cli/commands/project/init.ts b/src/cli/commands/project/init.ts new file mode 100644 index 000000000..94b7d551b --- /dev/null +++ b/src/cli/commands/project/init.ts @@ -0,0 +1,75 @@ +import { resolve } from "node:path"; +import { Command } from "commander"; +import { log } from "@clack/prompts"; +import chalk from "chalk"; +import { loadProjectEnv } from "@core/config.js"; +import { initProject } from "@core/project/index.js"; +import { runTask, textPrompt, printBanner } from "../../utils/index.js"; + +async function init(): Promise { + printBanner(); + + // Load .env.local from project root (if in a project) + await loadProjectEnv(); + + const name = await textPrompt({ + message: "What is the name of your project?", + placeholder: "my-app-backend", + validate: (value) => { + if (!value || value.trim().length === 0) { + return "Project name is required"; + } + }, + }); + + // Ask for description (optional) + const description: string | undefined = await textPrompt({ + message: "Project description (optional)", + placeholder: "A brief description of your project", + }); + + // Ask for project path with default + const defaultPath = "./"; + const projectPath = await textPrompt({ + message: "Where should we create the base44 folder?", + placeholder: defaultPath, + initialValue: defaultPath, + }); + + const resolvedPath = resolve(projectPath || defaultPath); + + // Create the project + const result = await runTask( + "Creating project...", + async () => { + return await initProject({ + name: name.trim(), + description: description ? description.trim() : undefined, + path: resolvedPath, + }); + }, + { + successMessage: "Project created successfully", + errorMessage: "Failed to create project", + } + ); + + // Display success message with details + log.success(`Project ${chalk.bold(name)} has been initialized!`); +} + +export const initCommand = new Command("init") + .alias("create") + .description("Initialize a new Base44 project") + .action(async () => { + try { + await init(); + } catch (e) { + if (e instanceof Error) { + log.error(e.stack ?? e.message); + } else { + log.error(String(e)); + } + process.exit(1); + } + }); diff --git a/src/cli/commands/project/show-project.ts b/src/cli/commands/project/show-project.ts index 8f82a3111..6c5a56ec5 100644 --- a/src/cli/commands/project/show-project.ts +++ b/src/cli/commands/project/show-project.ts @@ -1,6 +1,6 @@ import { Command } from "commander"; import { log } from "@clack/prompts"; -import { readProjectConfig } from "@core/config/project.js"; +import { readProjectConfig } from "@core/project/config.js"; import { runCommand, runTask } from "../../utils/index.js"; async function showProject(): Promise { diff --git a/src/cli/index.ts b/src/cli/index.ts index b510f2830..1c8e985d5 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -6,6 +6,7 @@ import { whoamiCommand } from "./commands/auth/whoami.js"; import { logoutCommand } from "./commands/auth/logout.js"; import { showProjectCommand } from "./commands/project/show-project.js"; import { entitiesPushCommand } from "./commands/entities/push.js"; +import { initCommand } from "./commands/project/init.js"; import packageJson from "../../package.json"; const program = new Command(); @@ -23,6 +24,7 @@ program.addCommand(whoamiCommand); program.addCommand(logoutCommand); // Register project commands +program.addCommand(initCommand); program.addCommand(showProjectCommand); // Register entities commands diff --git a/src/cli/utils/banner.ts b/src/cli/utils/banner.ts new file mode 100644 index 000000000..a35c5be37 --- /dev/null +++ b/src/cli/utils/banner.ts @@ -0,0 +1,18 @@ +import chalk from "chalk"; + +const orange = chalk.hex("#E86B3C"); +const dim = chalk.dim; + +// ASCII art banner for Base44 +const BANNER = ` +${orange("██████╗ █████╗ ███████╗███████╗ ██╗ ██╗██╗ ██╗")} +${orange("██╔══██╗██╔══██╗██╔════╝██╔════╝ ██║ ██║██║ ██║")} +${orange("██████╔╝███████║███████╗█████╗ ███████║███████║")} +${orange("██╔══██╗██╔══██║╚════██║██╔══╝ ╚════██║╚════██║")} +${orange("██████╔╝██║ ██║███████║███████╗ ██║ ██║")} +${orange("╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝")} +`; + +export function printBanner(): void { + console.log(BANNER); +} diff --git a/src/cli/utils/index.ts b/src/cli/utils/index.ts index f28dc0d64..c306b01c6 100644 --- a/src/cli/utils/index.ts +++ b/src/cli/utils/index.ts @@ -1,2 +1,4 @@ export * from "./runCommand.js"; export * from "./runTask.js"; +export * from "./prompts.js"; +export * from "./banner.js"; \ No newline at end of file diff --git a/src/cli/utils/prompts.ts b/src/cli/utils/prompts.ts new file mode 100644 index 000000000..f21ec8151 --- /dev/null +++ b/src/cli/utils/prompts.ts @@ -0,0 +1,22 @@ +import { text, isCancel, cancel } from "@clack/prompts"; +import type { TextOptions } from "@clack/prompts"; + +/** + * Handles prompt cancellation by exiting gracefully. + */ +function handleCancel(value: T | symbol): asserts value is T { + if (isCancel(value)) { + cancel("Operation cancelled."); + process.exit(0); + } +} + +/** + * Wrapper around @clack/prompts text() that handles cancellation automatically. + * Returns the string value directly, exits process if cancelled. + */ +export async function textPrompt(options: TextOptions): Promise { + const value = await text(options); + handleCancel(value); + return value; +} diff --git a/src/cli/utils/runCommand.ts b/src/cli/utils/runCommand.ts index 431a0e20c..571a06aab 100644 --- a/src/cli/utils/runCommand.ts +++ b/src/cli/utils/runCommand.ts @@ -1,11 +1,13 @@ import { intro, log } from "@clack/prompts"; import chalk from "chalk"; +import { loadProjectEnv } from "@core/config.js"; const base44Color = chalk.bgHex("#E86B3C"); /** * Wraps a command function with the Base44 intro banner. * All CLI commands should use this utility to ensure consistent branding. + * Also loads .env.local from the project root if available. * * @param commandFn - The async function to execute as the command */ @@ -14,6 +16,9 @@ export async function runCommand( ): Promise { intro(base44Color(" Base 44 ")); + // Load .env.local from project root (if in a project) + await loadProjectEnv(); + try { await commandFn(); } catch (e) { diff --git a/src/core/auth/api.ts b/src/core/auth/api.ts index 7255a7542..df7016577 100644 --- a/src/core/auth/api.ts +++ b/src/core/auth/api.ts @@ -10,7 +10,7 @@ import type { TokenResponse, UserInfoResponse, } from "./schema.js"; -import { AUTH_CLIENT_ID } from "../consts.js"; +import { AUTH_CLIENT_ID } from "../config.js"; import authClient from "./authClient.js"; export async function generateDeviceCode(): Promise { diff --git a/src/core/auth/authClient.ts b/src/core/auth/authClient.ts index 18a158bec..1a1a4d192 100644 --- a/src/core/auth/authClient.ts +++ b/src/core/auth/authClient.ts @@ -1,5 +1,5 @@ import ky from "ky"; -import { getBase44ApiUrl } from "../consts.js"; +import { getBase44ApiUrl } from "../config.js"; /** * Separate ky instance for OAuth endpoints. diff --git a/src/core/auth/config.ts b/src/core/auth/config.ts index 393fb2caa..78902f202 100644 --- a/src/core/auth/config.ts +++ b/src/core/auth/config.ts @@ -1,4 +1,4 @@ -import { getAuthFilePath } from "../consts.js"; +import { getAuthFilePath } from "../config.js"; import { readJsonFile, writeJsonFile, deleteFile } from "../utils/fs.js"; import { renewAccessToken } from "./api.js"; import { AuthDataSchema } from "./schema.js"; diff --git a/src/core/config.ts b/src/core/config.ts new file mode 100644 index 000000000..efbcb10fb --- /dev/null +++ b/src/core/config.ts @@ -0,0 +1,63 @@ +import { join } from "node:path"; +import { homedir } from "node:os"; +import { config } from "dotenv"; + +// Static constants +export const PROJECT_SUBDIR = "base44"; +export const FUNCTION_CONFIG_FILE = "function.jsonc"; +export const AUTH_CLIENT_ID = "base44_cli"; + +const DEFAULT_API_URL = "https://app.base44.com"; + +// Path helpers +export function getBase44Dir() { + return join(homedir(), ".base44"); +} + +export function getAuthFilePath() { + return join(getBase44Dir(), "auth", "auth.json"); +} + +export function getProjectConfigPatterns() { + return [ + `${PROJECT_SUBDIR}/config.jsonc`, + `${PROJECT_SUBDIR}/config.json`, + "config.jsonc", + "config.json", + ]; +} + +/** + * Load .env.local from the project root if it exists. + * Values won't override existing process.env variables. + * Safe to call multiple times - only loads once. + */ +export async function loadProjectEnv(projectRoot?: string): Promise { + // Avoid circular dependency - inline the project root finding + const { findProjectRoot } = await import("./project/config.js"); + const found = projectRoot ? { root: projectRoot } : await findProjectRoot(); + + if (!found) { + return; + } + + const envPath = join(found.root, ".env.local"); + config({ path: envPath, override: false }); +} + +/** + * Get the Base44 API URL. + * Priority: process.env.BASE44_API_URL > .env.local > default + */ +export function getBase44ApiUrl(): string { + return process.env.BASE44_API_URL || DEFAULT_API_URL; +} + +/** + * Get the Base44 Client ID (app ID). + * Priority: process.env.BASE44_CLIENT_ID > .env.local + * Returns undefined if not set. + */ +export function getBase44ClientId(): string | undefined { + return process.env.BASE44_CLIENT_ID; +} diff --git a/src/core/index.ts b/src/core/index.ts index b5f005dfe..992fe1908 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,5 +1,6 @@ export * from "./auth/index.js"; export * from "./resources/index.js"; -export * from "./config/index.js"; +export * from "./project/index.js"; export * from "./utils/index.js"; export * from "./errors.js"; +export * from "./config.js"; \ No newline at end of file diff --git a/src/core/project/api.ts b/src/core/project/api.ts new file mode 100644 index 000000000..634328936 --- /dev/null +++ b/src/core/project/api.ts @@ -0,0 +1,18 @@ +import httpClient from "@core/utils/httpClient"; +import { CreateProjectResponseSchema } from "./schema.js"; + +export async function createProject(projectName: string, description?: string) { + const response = await httpClient.post("api/apps", { + json: { + name: projectName, + user_description: description ?? `Backend for '${projectName}'`, + app_type: "baas", + }, + }); + + const result = CreateProjectResponseSchema.parse(await response.json()); + + return { + projectId: result.id, + }; +} diff --git a/src/core/config/baseResource.ts b/src/core/project/baseResource.ts similarity index 100% rename from src/core/config/baseResource.ts rename to src/core/project/baseResource.ts diff --git a/src/core/config/project.ts b/src/core/project/config.ts similarity index 99% rename from src/core/config/project.ts rename to src/core/project/config.ts index 0902e7336..7cbc194f0 100644 --- a/src/core/config/project.ts +++ b/src/core/project/config.ts @@ -1,7 +1,7 @@ import { join, dirname } from "node:path"; import { z } from "zod"; import { globby } from "globby"; -import { getProjectConfigPatterns, PROJECT_SUBDIR } from "../consts.js"; +import { getProjectConfigPatterns, PROJECT_SUBDIR } from "../config.js"; import { readJsonFile } from "../utils/fs.js"; import { entityResource } from "../resources/entity/index.js"; import type { Entity } from "../resources/entity/index.js"; diff --git a/src/core/project/index.ts b/src/core/project/index.ts new file mode 100644 index 000000000..29cc90840 --- /dev/null +++ b/src/core/project/index.ts @@ -0,0 +1,5 @@ +export type * from "./resource.js"; +export * from "./config.js"; +export * from "./schema.js"; +export * from "./api.js"; +export * from "./init.js"; \ No newline at end of file diff --git a/src/core/project/init.ts b/src/core/project/init.ts new file mode 100644 index 000000000..0a8873546 --- /dev/null +++ b/src/core/project/init.ts @@ -0,0 +1,63 @@ +import { join } from "node:path"; +import { globby } from "globby"; +import { getProjectConfigPatterns, PROJECT_SUBDIR } from "../config.js"; +import { writeFile } from "../utils/fs.js"; +import { createProject } from "./api.js"; +import { renderConfigTemplate, renderEnvTemplate } from "./templates/index.js"; + +export interface InitProjectOptions { + name: string; + description?: string; + path: string; +} + +export interface InitProjectResult { + projectId: string; + projectDir: string; + configPath: string; + envPath: string; +} + +/** + * Initialize a new Base44 project. + * Creates the base44 directory, config.jsonc, and .env.local files. + */ +export async function initProject( + options: InitProjectOptions +): Promise { + const { name, description, path: basePath } = options; + + const projectDir = join(basePath, PROJECT_SUBDIR); + const configPath = join(projectDir, "config.jsonc"); + const envPath = join(projectDir, ".env.local"); + + // Check if project already exists + const existingConfigs = await globby(getProjectConfigPatterns(), { + cwd: basePath, + absolute: true, + }); + + if (existingConfigs.length > 0) { + throw new Error( + `A Base44 project already exists at ${existingConfigs[0]}. Please choose a different location.` + ); + } + + // Create the project via API to get the app ID + const { projectId } = await createProject(name, description); + + // Create config.jsonc from template + const configContent = await renderConfigTemplate({ name, description }); + await writeFile(configPath, configContent); + + // Create .env.local from template + const envContent = await renderEnvTemplate({ projectId }); + await writeFile(envPath, envContent); + + return { + projectId, + projectDir, + configPath, + envPath, + }; +} diff --git a/src/core/project/resource.ts b/src/core/project/resource.ts new file mode 100644 index 000000000..139ce2922 --- /dev/null +++ b/src/core/project/resource.ts @@ -0,0 +1,4 @@ +export interface Resource { + readAll: (dir: string) => Promise; + push?: (items: T[]) => Promise; +} diff --git a/src/core/config/app.ts b/src/core/project/schema.ts similarity index 77% rename from src/core/config/app.ts rename to src/core/project/schema.ts index ff47302b0..02d6bac35 100644 --- a/src/core/config/app.ts +++ b/src/core/project/schema.ts @@ -16,3 +16,9 @@ export const AppConfigSchema = z.object({ export type SiteConfig = z.infer; export type AppConfig = z.infer; + +export const CreateProjectResponseSchema = z.looseObject({ + id: z.string(), +}); + +export type CreateProjectResponse = z.infer; diff --git a/src/core/project/templates/config.jsonc.ejs b/src/core/project/templates/config.jsonc.ejs new file mode 100644 index 000000000..6e73acfee --- /dev/null +++ b/src/core/project/templates/config.jsonc.ejs @@ -0,0 +1,17 @@ +// Base44 Project Configuration +// JSONC enables inline documentation and discoverability directly in config files. +// Commented-out properties show available options you can enable. + +{ + "name": "<%= name %>"<% if (description) { %>, + "description": "<%= description %>"<% } %> + + // Site/hosting configuration + // Docs: https://docs.base44.com/configuration/hosting + // "site": { + // "buildCommand": "npm run build", + // "serveCommand": "npm run dev", + // "outputDirectory": "./dist", + // "installCommand": "npm ci" + // } +} diff --git a/src/core/project/templates/env.local.ejs b/src/core/project/templates/env.local.ejs new file mode 100644 index 000000000..31f694492 --- /dev/null +++ b/src/core/project/templates/env.local.ejs @@ -0,0 +1,6 @@ +# Base44 Project Environment Variables +# This file contains environment-specific configuration for your Base44 project. +# Do not commit this file to version control if it contains sensitive data. + +# Your Base44 Application ID +BASE44_CLIENT_ID=<%= projectId %> diff --git a/src/core/project/templates/index.ts b/src/core/project/templates/index.ts new file mode 100644 index 000000000..4a22e8c86 --- /dev/null +++ b/src/core/project/templates/index.ts @@ -0,0 +1,30 @@ +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import ejs from "ejs"; + +// After bundling, import.meta.url points to dist/cli/index.js +// Templates are copied to dist/cli/templates/ +const __dirname = dirname(fileURLToPath(import.meta.url)); +const TEMPLATES_DIR = join(__dirname, "templates"); + +const CONFIG_TEMPLATE_PATH = join(TEMPLATES_DIR, "config.jsonc.ejs"); +const ENV_TEMPLATE_PATH = join(TEMPLATES_DIR, "env.local.ejs"); + +interface ConfigTemplateData { + name: string; + description?: string; +} + +interface EnvTemplateData { + projectId: string; +} + +export async function renderConfigTemplate( + data: ConfigTemplateData +): Promise { + return ejs.renderFile(CONFIG_TEMPLATE_PATH, data); +} + +export async function renderEnvTemplate(data: EnvTemplateData): Promise { + return ejs.renderFile(ENV_TEMPLATE_PATH, data); +} diff --git a/src/core/resources/function/config.ts b/src/core/resources/function/config.ts index 243576e7a..bc9024b09 100644 --- a/src/core/resources/function/config.ts +++ b/src/core/resources/function/config.ts @@ -1,5 +1,5 @@ import { globby } from "globby"; -import { FUNCTION_CONFIG_FILE } from "../../consts.js"; +import { FUNCTION_CONFIG_FILE } from "../../config.js"; import { readJsonFile, pathExists } from "../../utils/fs.js"; import { FunctionConfigSchema } from "./schema.js"; import type { FunctionConfig } from "./schema.js"; diff --git a/src/core/utils/fs.ts b/src/core/utils/fs.ts index 3765c0822..2a9f837ec 100644 --- a/src/core/utils/fs.ts +++ b/src/core/utils/fs.ts @@ -1,12 +1,32 @@ -import { readFile, writeFile, mkdir, unlink, access } from "node:fs/promises"; +import { + readFile as fsReadFile, + writeFile as fsWriteFile, + mkdir, + unlink, + access, +} from "node:fs/promises"; import { dirname } from "node:path"; import type { ParseError } from "jsonc-parser"; import { parse, printParseErrorCode } from "jsonc-parser"; -export function pathExists(path: string) { - return access(path) - .then(() => true) - .catch(() => false); +export async function pathExists(path: string): Promise { + try { + await access(path); + return true; + } catch { + return false; + } +} + +export async function writeFile( + filePath: string, + content: string +): Promise { + const dir = dirname(filePath); + if (!(await pathExists(dir))) { + await mkdir(dir, { recursive: true }); + } + await fsWriteFile(filePath, content, "utf-8"); } export async function readJsonFile(filePath: string): Promise { @@ -15,7 +35,7 @@ export async function readJsonFile(filePath: string): Promise { } try { - const fileContent = await readFile(filePath, "utf-8"); + const fileContent = await fsReadFile(filePath, "utf-8"); const errors: ParseError[] = []; const result = parse(fileContent, errors, { allowTrailingComma: true }); @@ -45,35 +65,17 @@ export async function writeJsonFile( filePath: string, data: unknown ): Promise { - try { - const dir = dirname(filePath); - if (!(await pathExists(dir))) { - await mkdir(dir, { recursive: true }); - } - - const jsonContent = JSON.stringify(data, null, 2); - await writeFile(filePath, jsonContent, "utf-8"); - } catch (error) { - throw new Error( - `Failed to write file ${filePath}: ${ - error instanceof Error ? error.message : "Unknown error" - }` - ); + const dir = dirname(filePath); + if (!(await pathExists(dir))) { + await mkdir(dir, { recursive: true }); } + const jsonContent = JSON.stringify(data, null, 2); + await fsWriteFile(filePath, jsonContent, "utf-8"); } export async function deleteFile(filePath: string): Promise { if (!(await pathExists(filePath))) { return; } - - try { - await unlink(filePath); - } catch (error) { - throw new Error( - `Failed to delete file ${filePath}: ${ - error instanceof Error ? error.message : "Unknown error" - }` - ); - } + await unlink(filePath); } diff --git a/src/core/utils/httpClient.ts b/src/core/utils/httpClient.ts index bddd8376a..ad9955371 100644 --- a/src/core/utils/httpClient.ts +++ b/src/core/utils/httpClient.ts @@ -1,6 +1,6 @@ import ky from "ky"; import type { KyRequest, KyResponse, NormalizedOptions } from "ky"; -import { getAppId, getBase44ApiUrl } from "../consts.js"; +import { getBase44ApiUrl } from "../config.js"; import { readAuth, refreshAndSaveTokens, diff --git a/templates/config.jsonc.ejs b/templates/config.jsonc.ejs new file mode 100644 index 000000000..6e73acfee --- /dev/null +++ b/templates/config.jsonc.ejs @@ -0,0 +1,17 @@ +// Base44 Project Configuration +// JSONC enables inline documentation and discoverability directly in config files. +// Commented-out properties show available options you can enable. + +{ + "name": "<%= name %>"<% if (description) { %>, + "description": "<%= description %>"<% } %> + + // Site/hosting configuration + // Docs: https://docs.base44.com/configuration/hosting + // "site": { + // "buildCommand": "npm run build", + // "serveCommand": "npm run dev", + // "outputDirectory": "./dist", + // "installCommand": "npm ci" + // } +} diff --git a/templates/env.local.ejs b/templates/env.local.ejs new file mode 100644 index 000000000..31f694492 --- /dev/null +++ b/templates/env.local.ejs @@ -0,0 +1,6 @@ +# Base44 Project Environment Variables +# This file contains environment-specific configuration for your Base44 project. +# Do not commit this file to version control if it contains sensitive data. + +# Your Base44 Application ID +BASE44_CLIENT_ID=<%= projectId %> diff --git a/templates/index.ts b/templates/index.ts new file mode 100644 index 000000000..4a22e8c86 --- /dev/null +++ b/templates/index.ts @@ -0,0 +1,30 @@ +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import ejs from "ejs"; + +// After bundling, import.meta.url points to dist/cli/index.js +// Templates are copied to dist/cli/templates/ +const __dirname = dirname(fileURLToPath(import.meta.url)); +const TEMPLATES_DIR = join(__dirname, "templates"); + +const CONFIG_TEMPLATE_PATH = join(TEMPLATES_DIR, "config.jsonc.ejs"); +const ENV_TEMPLATE_PATH = join(TEMPLATES_DIR, "env.local.ejs"); + +interface ConfigTemplateData { + name: string; + description?: string; +} + +interface EnvTemplateData { + projectId: string; +} + +export async function renderConfigTemplate( + data: ConfigTemplateData +): Promise { + return ejs.renderFile(CONFIG_TEMPLATE_PATH, data); +} + +export async function renderEnvTemplate(data: EnvTemplateData): Promise { + return ejs.renderFile(ENV_TEMPLATE_PATH, data); +} diff --git a/tsdown.config.mjs b/tsdown.config.mjs index ad855d60e..85863d1ca 100644 --- a/tsdown.config.mjs +++ b/tsdown.config.mjs @@ -7,4 +7,5 @@ export default defineConfig({ outDir: "dist/cli", clean: true, tsconfig: "tsconfig.json", + copy: ["src/core/project/templates"], }); From 5daa26d96a777c3da3baa4b911bad7a77636348b Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 16:46:34 +0200 Subject: [PATCH 10/13] fix imports after rebasing --- src/cli/commands/project/init.ts | 2 +- src/cli/utils/banner.ts | 1 - src/core/config/index.ts | 3 -- src/core/consts.ts | 38 ------------------------- src/core/project/api.ts | 4 +-- src/core/resources/entity/resource.ts | 2 +- src/core/resources/function/resource.ts | 2 +- src/core/utils/httpClient.ts | 5 ++-- 8 files changed, 8 insertions(+), 49 deletions(-) delete mode 100644 src/core/config/index.ts delete mode 100644 src/core/consts.ts diff --git a/src/cli/commands/project/init.ts b/src/cli/commands/project/init.ts index 94b7d551b..9e63b357f 100644 --- a/src/cli/commands/project/init.ts +++ b/src/cli/commands/project/init.ts @@ -39,7 +39,7 @@ async function init(): Promise { const resolvedPath = resolve(projectPath || defaultPath); // Create the project - const result = await runTask( + await runTask( "Creating project...", async () => { return await initProject({ diff --git a/src/cli/utils/banner.ts b/src/cli/utils/banner.ts index a35c5be37..80275548f 100644 --- a/src/cli/utils/banner.ts +++ b/src/cli/utils/banner.ts @@ -1,7 +1,6 @@ import chalk from "chalk"; const orange = chalk.hex("#E86B3C"); -const dim = chalk.dim; // ASCII art banner for Base44 const BANNER = ` diff --git a/src/core/config/index.ts b/src/core/config/index.ts deleted file mode 100644 index 78ea40db2..000000000 --- a/src/core/config/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type * from "./baseResource.js"; -export * from "./project.js"; -export * from "./app.js"; diff --git a/src/core/consts.ts b/src/core/consts.ts deleted file mode 100644 index b1be0ee59..000000000 --- a/src/core/consts.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { join } from "node:path"; -import { homedir } from "node:os"; - -export const PROJECT_SUBDIR = "base44"; -export const FUNCTION_CONFIG_FILE = "function.jsonc"; - -export function getBase44Dir() { - return join(homedir(), ".base44"); -} - -export function getAuthFilePath() { - return join(getBase44Dir(), "auth", "auth.json"); -} - -export function getProjectConfigPatterns() { - return [ - `${PROJECT_SUBDIR}/config.jsonc`, - `${PROJECT_SUBDIR}/config.json`, - "config.jsonc", - "config.json", - ]; -} - -export const AUTH_CLIENT_ID = "base44_cli"; - -const DEFAULT_API_URL = "https://app.base44.com"; - -export function getBase44ApiUrl(): string { - return process.env.BASE44_API_URL || DEFAULT_API_URL; -} - -export function getAppId(): string { - const appId = process.env.BASE44_CLIENT_ID; - if (!appId) { - throw new Error("BASE44_CLIENT_ID environment variable is not set"); - } - return appId; -} diff --git a/src/core/project/api.ts b/src/core/project/api.ts index 634328936..0a9efc3b3 100644 --- a/src/core/project/api.ts +++ b/src/core/project/api.ts @@ -1,8 +1,8 @@ -import httpClient from "@core/utils/httpClient"; +import { base44Client } from "@core/utils/httpClient.js"; import { CreateProjectResponseSchema } from "./schema.js"; export async function createProject(projectName: string, description?: string) { - const response = await httpClient.post("api/apps", { + const response = await base44Client.post("api/apps", { json: { name: projectName, user_description: description ?? `Backend for '${projectName}'`, diff --git a/src/core/resources/entity/resource.ts b/src/core/resources/entity/resource.ts index 80b010f97..f27b0cef5 100644 --- a/src/core/resources/entity/resource.ts +++ b/src/core/resources/entity/resource.ts @@ -1,4 +1,4 @@ -import type { Resource } from "@core/config/baseResource.js"; +import type { Resource } from "@core/project/baseResource.js"; import type { Entity } from "./schema.js"; import { readAllEntities } from "./config.js"; import { pushEntities } from "./api.js"; diff --git a/src/core/resources/function/resource.ts b/src/core/resources/function/resource.ts index 144dcee84..ec0866b40 100644 --- a/src/core/resources/function/resource.ts +++ b/src/core/resources/function/resource.ts @@ -1,4 +1,4 @@ -import type { Resource } from "@core/config/baseResource.js"; +import type { Resource } from "@core/project/baseResource.js"; import type { FunctionConfig } from "./schema.js"; import { readAllFunctions } from "./config.js"; diff --git a/src/core/utils/httpClient.ts b/src/core/utils/httpClient.ts index ad9955371..61255cc9d 100644 --- a/src/core/utils/httpClient.ts +++ b/src/core/utils/httpClient.ts @@ -1,6 +1,6 @@ import ky from "ky"; import type { KyRequest, KyResponse, NormalizedOptions } from "ky"; -import { getBase44ApiUrl } from "../config.js"; +import { getBase44ApiUrl, getBase44ClientId } from "../config.js"; import { readAuth, refreshAndSaveTokens, @@ -77,7 +77,8 @@ const base44Client = ky.create({ */ function getAppClient() { return base44Client.extend({ - prefixUrl: new URL(`/api/apps/${getAppId()}/`, getBase44ApiUrl()).href, + prefixUrl: new URL(`/api/apps/${getBase44ClientId()}/`, getBase44ApiUrl()) + .href, }); } From 996c659c96c102098bfecc10de2ba7a2487db5fd Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 16:48:52 +0200 Subject: [PATCH 11/13] fix broken test import --- tests/core/project.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/project.test.ts b/tests/core/project.test.ts index 58ce55c97..e32be2921 100644 --- a/tests/core/project.test.ts +++ b/tests/core/project.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { readProjectConfig } from "../../src/core/config/project.js"; +import { readProjectConfig } from "../../src/core/project/index.js"; import { resolve } from "path"; const FIXTURES_DIR = resolve(__dirname, "../fixtures"); From 9cee4a900682a82328c32682c1e164aa199238a8 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 16:51:09 +0200 Subject: [PATCH 12/13] remove unused files --- src/cli/utils/index.ts | 2 +- src/core/project/index.ts | 2 +- templates/config.jsonc.ejs | 17 ----------------- templates/env.local.ejs | 6 ------ templates/index.ts | 30 ------------------------------ 5 files changed, 2 insertions(+), 55 deletions(-) delete mode 100644 templates/config.jsonc.ejs delete mode 100644 templates/env.local.ejs delete mode 100644 templates/index.ts diff --git a/src/cli/utils/index.ts b/src/cli/utils/index.ts index c306b01c6..858d20588 100644 --- a/src/cli/utils/index.ts +++ b/src/cli/utils/index.ts @@ -1,4 +1,4 @@ export * from "./runCommand.js"; export * from "./runTask.js"; export * from "./prompts.js"; -export * from "./banner.js"; \ No newline at end of file +export * from "./banner.js"; diff --git a/src/core/project/index.ts b/src/core/project/index.ts index 29cc90840..f26fe18b7 100644 --- a/src/core/project/index.ts +++ b/src/core/project/index.ts @@ -2,4 +2,4 @@ export type * from "./resource.js"; export * from "./config.js"; export * from "./schema.js"; export * from "./api.js"; -export * from "./init.js"; \ No newline at end of file +export * from "./init.js"; diff --git a/templates/config.jsonc.ejs b/templates/config.jsonc.ejs deleted file mode 100644 index 6e73acfee..000000000 --- a/templates/config.jsonc.ejs +++ /dev/null @@ -1,17 +0,0 @@ -// Base44 Project Configuration -// JSONC enables inline documentation and discoverability directly in config files. -// Commented-out properties show available options you can enable. - -{ - "name": "<%= name %>"<% if (description) { %>, - "description": "<%= description %>"<% } %> - - // Site/hosting configuration - // Docs: https://docs.base44.com/configuration/hosting - // "site": { - // "buildCommand": "npm run build", - // "serveCommand": "npm run dev", - // "outputDirectory": "./dist", - // "installCommand": "npm ci" - // } -} diff --git a/templates/env.local.ejs b/templates/env.local.ejs deleted file mode 100644 index 31f694492..000000000 --- a/templates/env.local.ejs +++ /dev/null @@ -1,6 +0,0 @@ -# Base44 Project Environment Variables -# This file contains environment-specific configuration for your Base44 project. -# Do not commit this file to version control if it contains sensitive data. - -# Your Base44 Application ID -BASE44_CLIENT_ID=<%= projectId %> diff --git a/templates/index.ts b/templates/index.ts deleted file mode 100644 index 4a22e8c86..000000000 --- a/templates/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import ejs from "ejs"; - -// After bundling, import.meta.url points to dist/cli/index.js -// Templates are copied to dist/cli/templates/ -const __dirname = dirname(fileURLToPath(import.meta.url)); -const TEMPLATES_DIR = join(__dirname, "templates"); - -const CONFIG_TEMPLATE_PATH = join(TEMPLATES_DIR, "config.jsonc.ejs"); -const ENV_TEMPLATE_PATH = join(TEMPLATES_DIR, "env.local.ejs"); - -interface ConfigTemplateData { - name: string; - description?: string; -} - -interface EnvTemplateData { - projectId: string; -} - -export async function renderConfigTemplate( - data: ConfigTemplateData -): Promise { - return ejs.renderFile(CONFIG_TEMPLATE_PATH, data); -} - -export async function renderEnvTemplate(data: EnvTemplateData): Promise { - return ejs.renderFile(ENV_TEMPLATE_PATH, data); -} From 5106f1b5c03659690ba31d1437976b2ef13be2cf Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 11 Jan 2026 17:42:55 +0200 Subject: [PATCH 13/13] fix small issue with dotenv --- src/core/config.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/core/config.ts b/src/core/config.ts index efbcb10fb..c384b9043 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -1,14 +1,13 @@ import { join } from "node:path"; import { homedir } from "node:os"; import { config } from "dotenv"; +import { findProjectRoot } from "./project/index.js"; // Static constants export const PROJECT_SUBDIR = "base44"; export const FUNCTION_CONFIG_FILE = "function.jsonc"; export const AUTH_CLIENT_ID = "base44_cli"; -const DEFAULT_API_URL = "https://app.base44.com"; - // Path helpers export function getBase44Dir() { return join(homedir(), ".base44"); @@ -33,16 +32,14 @@ export function getProjectConfigPatterns() { * Safe to call multiple times - only loads once. */ export async function loadProjectEnv(projectRoot?: string): Promise { - // Avoid circular dependency - inline the project root finding - const { findProjectRoot } = await import("./project/config.js"); const found = projectRoot ? { root: projectRoot } : await findProjectRoot(); if (!found) { return; } - const envPath = join(found.root, ".env.local"); - config({ path: envPath, override: false }); + const envPath = join(found.root, PROJECT_SUBDIR, ".env.local"); + config({ path: envPath, override: false, quiet: true }); } /** @@ -50,7 +47,7 @@ export async function loadProjectEnv(projectRoot?: string): Promise { * Priority: process.env.BASE44_API_URL > .env.local > default */ export function getBase44ApiUrl(): string { - return process.env.BASE44_API_URL || DEFAULT_API_URL; + return process.env.BASE44_API_URL || "https://app.base44.com"; } /**