diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f871aff..e0eaada1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - App visibility: `base44 visibility ` sets it on the server directly (accepts `--app-id` to target any app). Also configurable via `"visibility"` in `config.jsonc`, which `base44 deploy` applies. New projects scaffold `"visibility": "public"`. +- `base44 build` runs the site's `buildCommand` with `VITE_BASE44_APP_ID` injected, so built bundles always carry the linked app's id. ### Fixed diff --git a/packages/cli/src/cli/commands/project/build.ts b/packages/cli/src/cli/commands/project/build.ts new file mode 100644 index 00000000..747329ce --- /dev/null +++ b/packages/cli/src/cli/commands/project/build.ts @@ -0,0 +1,32 @@ +import type { Command } from "commander"; +import { runSiteBuild } from "@/cli/commands/project/site-build.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; +import { Base44Command, theme } from "@/cli/utils/index.js"; +import { ConfigInvalidError } from "@/core/errors.js"; +import { readProjectConfig } from "@/core/project/index.js"; + +async function buildAction(ctx: CLIContext): Promise { + const { app } = ctx; + if (!app?.projectRoot) { + throw new ConfigInvalidError( + "base44 build requires a linked local project. Run it from a project with base44/.app.jsonc.", + ); + } + + const { project } = await readProjectConfig(app.projectRoot); + await runSiteBuild(ctx, { + root: project.root, + buildCommand: project.site?.buildCommand, + appId: app.id, + }); + + return { + outroMessage: `Site built with app id ${theme.styles.bold(app.id)}`, + }; +} + +export function getBuildCommand(): Command { + return new Base44Command("build") + .description("Build the site with the Base44 app id injected") + .action(buildAction); +} diff --git a/packages/cli/src/cli/commands/project/site-build.ts b/packages/cli/src/cli/commands/project/site-build.ts new file mode 100644 index 00000000..9a51a6cc --- /dev/null +++ b/packages/cli/src/cli/commands/project/site-build.ts @@ -0,0 +1,39 @@ +import { execa } from "execa"; +import type { CLIContext } from "@/cli/types.js"; +import { ConfigNotFoundError } from "@/core/errors.js"; + +interface SiteBuildTarget { + root: string; + buildCommand?: string; + appId: string; +} + +export async function runSiteBuild( + { runTask }: Pick, + { root, buildCommand, appId }: SiteBuildTarget, +): Promise { + if (!buildCommand) { + throw new ConfigNotFoundError("No site build command found.", { + hints: [ + { + message: + 'Add \'site.buildCommand\' to your config.jsonc (e.g., "site": { "buildCommand": "npm run build" })', + }, + ], + }); + } + + await runTask( + "Building site...", + () => + execa({ + cwd: root, + shell: true, + env: { VITE_BASE44_APP_ID: appId }, + })`${buildCommand}`, + { + successMessage: "Site built successfully", + errorMessage: "Build failed", + }, + ); +} diff --git a/packages/cli/src/cli/program.ts b/packages/cli/src/cli/program.ts index b3a7a87f..04e1c33e 100644 --- a/packages/cli/src/cli/program.ts +++ b/packages/cli/src/cli/program.ts @@ -9,6 +9,7 @@ import { getConnectorsCommand } from "@/cli/commands/connectors/index.js"; import { getDashboardCommand } from "@/cli/commands/dashboard/index.js"; import { getEntitiesPushCommand } from "@/cli/commands/entities/push.js"; import { getFunctionsCommand } from "@/cli/commands/functions/index.js"; +import { getBuildCommand } from "@/cli/commands/project/build.js"; import { getCreateCommand } from "@/cli/commands/project/create.js"; import { getDeployCommand } from "@/cli/commands/project/deploy.js"; import { getLinkCommand } from "@/cli/commands/project/link.js"; @@ -69,6 +70,7 @@ export function createProgram(context: CLIContext): Command { program.addCommand(getCreateCommand()); program.addCommand(getScaffoldCommand()); program.addCommand(getDashboardCommand()); + program.addCommand(getBuildCommand()); program.addCommand(getDeployCommand()); program.addCommand(getVisibilityCommand()); program.addCommand(getLinkCommand()); diff --git a/packages/cli/tests/cli/build.spec.ts b/packages/cli/tests/cli/build.spec.ts new file mode 100644 index 00000000..d8d390db --- /dev/null +++ b/packages/cli/tests/cli/build.spec.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import { fixture, setupCLITests } from "./testkit/index.js"; + +describe("build command", () => { + const t = setupCLITests(); + + it("runs the site buildCommand with the app id injected", async () => { + await t.givenLoggedInWithProject(fixture("with-buildable-site")); + + const result = await t.run("build"); + + t.expectResult(result).toSucceed(); + expect(await t.readProjectFile("build-env.txt")).toBe( + `BUILD_APP=${t.api.appId}`, + ); + }); + + it("fails when the project has no site.buildCommand", async () => { + await t.givenLoggedInWithProject(fixture("with-site")); + + const result = await t.run("build"); + + t.expectResult(result).toFail(); + t.expectResult(result).toContain("No site build command found"); + }); + + it("fails when the buildCommand fails", async () => { + await t.givenLoggedInWithProject(fixture("with-failing-build")); + + const result = await t.run("build"); + + t.expectResult(result).toFail(); + t.expectResult(result).toContain("Build failed"); + }); + + it("fails when not in a project directory", async () => { + await t.givenLoggedIn({ email: "test@example.com", name: "Test User" }); + + const result = await t.run("build"); + + t.expectResult(result).toFail(); + }); +}); diff --git a/packages/cli/tests/fixtures/with-buildable-site/base44/config.jsonc b/packages/cli/tests/fixtures/with-buildable-site/base44/config.jsonc new file mode 100644 index 00000000..05abc950 --- /dev/null +++ b/packages/cli/tests/fixtures/with-buildable-site/base44/config.jsonc @@ -0,0 +1,6 @@ +{ + "name": "Buildable Site Project", + "site": { + "buildCommand": "node -e \"require('fs').writeFileSync('build-env.txt', 'BUILD_APP=' + process.env.VITE_BASE44_APP_ID)\"" + } +} diff --git a/packages/cli/tests/fixtures/with-failing-build/base44/config.jsonc b/packages/cli/tests/fixtures/with-failing-build/base44/config.jsonc new file mode 100644 index 00000000..fb3844ea --- /dev/null +++ b/packages/cli/tests/fixtures/with-failing-build/base44/config.jsonc @@ -0,0 +1,6 @@ +{ + "name": "Failing Build Project", + "site": { + "buildCommand": "node -e \"process.exit(1)\"" + } +}