diff --git a/docs/api-patterns.md b/docs/api-patterns.md index 75f0e3b7..5a786d83 100644 --- a/docs/api-patterns.md +++ b/docs/api-patterns.md @@ -43,6 +43,33 @@ The `base44Client` automatically handles token refresh: 2. If expired, refreshes token and saves new tokens 3. On 401 response, attempts refresh and retries once +## Environment-Supplied Credentials (Stripe Projects Handoff) + +When `BASE44_ACCESS_TOKEN` is set in the environment, `base44Client` uses it +verbatim as the bearer token, taking precedence over `~/.base44/auth/auth.json` +and **bypassing the OAuth refresh logic** (these tokens are provider-issued, not +Base44 OAuth tokens, so they cannot be refreshed via `/oauth/token`; on a 401 the +error simply propagates). `isLoggedIn()` also returns `true` when this var is set, +so `requireAuth` commands run without an interactive login. See +`getEnvAccessToken()` in `core/auth/config.js`. + +This supports the Stripe Projects CLI handoff: it provisions a Base44 app and +writes the credentials to `.env` via `stripe projects env --pull`. The CLI loads +`.env`/`.env.local` from the working directory at startup (`loadProjectEnvFiles()` +in `core/utils/env.js`, wired through `cli/bootstrap-env.js` as the first import so +it runs before the HTTP clients capture `getBase44ApiUrl()`). Precedence: ambient +`process.env` > `.env.local` > `.env`; pre-set values are never overridden. + +**Var name normalization.** `stripe projects env --pull` namespaces each var by +resource name, so the credentials arrive as e.g. `BASE44_PROJECTS_BASE44_APP_ID` +rather than bare `BASE44_APP_ID`. After loading, `loadProjectEnvFiles()` normalizes +the four credential keys (`BASE44_APP_ID`, `BASE44_ACCESS_TOKEN`, +`BASE44_REFRESH_TOKEN`, `BASE44_API_URL`): for each, if the bare name is unset and +exactly one `_` variable exists, its value is copied to the bare name. +This is prefix-agnostic (survives any resource name) and leaves the bare key unset +when ambiguous. The `base44 init` command then consumes `BASE44_APP_ID` to scaffold +a local project for that existing app. + ## API Response Transformation (snake_case to camelCase) The Base44 API returns snake_case keys, but the CLI uses camelCase. Use Zod's `.transform()` to convert: diff --git a/docs/commands.md b/docs/commands.md index 5504f158..b0f5fbb9 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -143,7 +143,9 @@ export function getMyCommand(): Command { } ``` -Commands that only use `logger.*` (display-only, no input) don't need this guard. See `project/create.ts`, `project/link.ts`, and `project/eject.ts` for real examples. +Commands that only use `logger.*` (display-only, no input) don't need this guard. See `project/create.ts`, `project/init.ts`, `project/link.ts`, and `project/eject.ts` for real examples. + +`project/create.ts` and `project/init.ts` share their post-scaffold logic (push entities, deploy site, install skills, print summary) via `project/scaffold-shared.ts` — `create` mints a new app, `init` reuses an existing app id (from `--app-id` or `BASE44_APP_ID`). ## runTask (Async Operations with Spinners) diff --git a/packages/cli/src/cli/bootstrap-env.ts b/packages/cli/src/cli/bootstrap-env.ts new file mode 100644 index 00000000..829d4d15 --- /dev/null +++ b/packages/cli/src/cli/bootstrap-env.ts @@ -0,0 +1,9 @@ +import { loadProjectEnvFiles } from "@/core/utils/env.js"; + +// Side-effect module: loads project-local .env / .env.local into process.env at +// the very start of the CLI process. This MUST run before any module that reads +// env-derived config at import time — notably the HTTP clients, which capture +// the API base URL (getBase44ApiUrl) when ky.create() runs at module load. +// +// Imported first in cli/index.ts so it initializes ahead of the program graph. +loadProjectEnvFiles(); diff --git a/packages/cli/src/cli/commands/project/create.ts b/packages/cli/src/cli/commands/project/create.ts index f77f2406..6158a9d8 100644 --- a/packages/cli/src/cli/commands/project/create.ts +++ b/packages/cli/src/cli/commands/project/create.ts @@ -1,27 +1,23 @@ -import { basename, join, resolve } from "node:path"; +import { basename, resolve } from "node:path"; import type { Option } from "@clack/prompts"; -import { confirm, group, isCancel, select, text } from "@clack/prompts"; +import { group, select, text } from "@clack/prompts"; import { Argument, type Command } from "commander"; -import { execa } from "execa"; import kebabCase from "lodash/kebabCase"; import type { CLIContext, RunCommandResult } from "@/cli/types.js"; -import { - Base44Command, - getDashboardUrl, - onPromptCancel, - theme, -} from "@/cli/utils/index.js"; +import { Base44Command, onPromptCancel, theme } from "@/cli/utils/index.js"; import { InvalidInputError } from "@/core/errors.js"; -import { deploySite, isDirEmpty, pushEntities } from "@/core/index.js"; +import { isDirEmpty } from "@/core/index.js"; import type { Template } from "@/core/project/index.js"; import { createProjectFiles, listTemplates, - readProjectConfig, setAppConfig, } from "@/core/project/index.js"; - -const DEFAULT_TEMPLATE_ID = "backend-only"; +import { + completeProjectSetup, + DEFAULT_TEMPLATE_ID, + getTemplateById, +} from "./scaffold-shared.js"; interface CreateOptions { name?: string; @@ -31,18 +27,6 @@ interface CreateOptions { skills?: boolean; } -async function getTemplateById(templateId: string): Promise