@fluid-app/fluid-cli-portal 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["FLUID_API_BASE"],"sources":["../src/types.ts","../src/utils/prompts.ts","../src/utils/result.ts","../src/utils/file-system.ts","../src/utils/package-manager.ts","../src/commands/create.ts","../src/commands/dev.ts","../src/commands/build.ts","../src/utils/turso.ts","../src/utils/cloud-run.ts","../src/utils/fluid-api.ts","../src/utils/project.ts","../src/utils/extract-navigation.ts","../src/utils/navigation-sync.ts","../src/commands/deploy.ts","../src/commands/destroy.ts","../src/index.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// Template types - derived from const object\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Available project templates\n */\nexport const TEMPLATES = {\n starter: \"starter\",\n fullstack: \"fullstack\",\n} as const;\n\n/**\n * Union type of valid template names\n */\nexport type TemplateName = (typeof TEMPLATES)[keyof typeof TEMPLATES];\n\n/**\n * Type guard to check if a string is a valid template name\n */\nexport function isTemplateName(value: string): value is TemplateName {\n return Object.values(TEMPLATES).includes(value as TemplateName);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Project configuration types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Selected page template info\n */\nexport interface SelectedPageTemplate {\n readonly id: string;\n readonly slug: string;\n readonly name: string;\n}\n\n/**\n * Configuration options collected during project scaffolding\n */\nexport interface ProjectConfig {\n /** Project name (used for directory and package.json name) */\n readonly name: string;\n /** Template to scaffold from */\n readonly template: TemplateName;\n /** Whether to install dependencies after scaffolding */\n readonly installDeps: boolean;\n /** Selected optional page templates to include */\n readonly selectedPages: readonly SelectedPageTemplate[];\n}\n\n/**\n * Options for the create command (from CLI arguments)\n * Uses string types since CLI input needs validation before narrowing\n */\nexport interface CreateOptions {\n /** Template name (validated against TemplateName type at runtime) */\n readonly template?: string;\n /** Skip dependency installation */\n readonly skipInstall?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Command option types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Options for the dev command\n */\nexport interface DevOptions {\n readonly port?: number;\n readonly host?: boolean;\n}\n\n/**\n * Options for the build command\n */\nexport interface BuildOptions {\n readonly outDir?: string;\n}\n\n/**\n * Options for the deploy command\n */\nexport interface DeployOptions {\n /** Cloud Run region (default: us-central1) */\n readonly region?: string;\n /** GCP project ID (default: from gcloud config) */\n readonly gcpProject?: string;\n /** Service name override (default: from package.json) */\n readonly project?: string;\n /** Turso database group location (default: aws-us-east-1) */\n readonly dbRegion?: string;\n /** Require IAM authentication for the Cloud Run service (default: public) */\n readonly requireAuth?: boolean;\n /** Run database migrations (db:push) after successful deploy */\n readonly migrate?: boolean;\n /** Skip the local Docker build check before deploying */\n readonly skipLocalBuild?: boolean;\n /** Turso organization slug (skips interactive org selection) */\n readonly tursoOrg?: string;\n /** Fluid company API key (skips env var lookup and interactive prompt) */\n readonly fluidCompanyApiKey?: string;\n /** Skip navigation sync from portal.config.ts */\n readonly skipNavSync?: boolean;\n}\n\n/**\n * Options for the destroy command\n */\nexport interface DestroyOptions {\n /** Cloud Run region (default: us-central1) */\n readonly region?: string;\n /** GCP project ID (default: from gcloud config) */\n readonly gcpProject?: string;\n /** Service name override (default: from package.json) */\n readonly project?: string;\n /** Turso organization slug (skips interactive org selection) */\n readonly tursoOrg?: string;\n /** Fluid company API key (skips env var lookup and interactive prompt) */\n readonly fluidCompanyApiKey?: string;\n /** Skip confirmation prompt */\n readonly yes?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Template processing types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Template variables for Handlebars processing\n */\nexport interface TemplateVariables {\n readonly projectName: string;\n readonly sdkVersion: string;\n /** Selected page templates for the project */\n readonly selectedPages: readonly SelectedPageTemplate[];\n /** Whether any optional pages were selected */\n readonly hasSelectedPages: boolean;\n}\n","import prompts from \"prompts\";\nimport {\n type ProjectConfig,\n type CreateOptions,\n type SelectedPageTemplate,\n type TemplateName,\n TEMPLATES,\n isTemplateName,\n} from \"../types.js\";\n\n/**\n * Optional page template shape\n */\ninterface OptionalPageTemplate {\n readonly id: string;\n readonly slug: string;\n readonly name: string;\n readonly description: string;\n}\n\n/**\n * Available optional page templates that can be selected during project creation.\n * Core pages (Messaging, Contacts, CRM) are always included automatically.\n */\nconst OPTIONAL_PAGE_TEMPLATES: readonly OptionalPageTemplate[] = [\n // Currently no optional pages - all pages are core\n // Future optional pages can be added here:\n // { id: 'orders', slug: 'orders', name: 'Orders', description: 'Order management page' },\n // { id: 'products', slug: 'products', name: 'Products', description: 'Product catalog page' },\n];\n\n/**\n * Prompts the user for project configuration\n * Pre-fills values from CLI options when provided\n */\nexport async function promptProjectConfig(\n projectName: string,\n options: CreateOptions,\n): Promise<ProjectConfig | null> {\n // Build questions based on what options are missing\n const questions: prompts.PromptObject[] = [];\n\n // Template selection (if not provided via CLI)\n if (!options.template) {\n questions.push({\n type: \"select\",\n name: \"template\",\n message: \"Select a project template\",\n choices: [\n {\n title: \"Starter\",\n value: TEMPLATES.starter,\n description: \"Frontend-only (React + Vite + Tailwind + portal-sdk)\",\n },\n {\n title: \"Fullstack\",\n value: TEMPLATES.fullstack,\n description: \"Frontend + API server (Hono + Drizzle + SQLite)\",\n },\n ],\n });\n }\n\n // Page template selection (only if there are optional templates)\n if (OPTIONAL_PAGE_TEMPLATES.length > 0) {\n questions.push({\n type: \"multiselect\",\n name: \"selectedPages\",\n message: \"Select additional page templates to include\",\n instructions:\n \"\\n Space to select, Enter to confirm. Core pages (Messaging, Contacts, CRM) are always included.\",\n choices: OPTIONAL_PAGE_TEMPLATES.map((page) => ({\n title: page.name,\n value: { id: page.id, slug: page.slug, name: page.name },\n description: page.description,\n })),\n });\n }\n\n // Install dependencies\n if (!options.skipInstall) {\n questions.push({\n type: \"confirm\",\n name: \"installDeps\",\n message: \"Install dependencies?\",\n initial: true,\n });\n }\n\n // Fast-path: all options provided via CLI flags, no prompts needed\n if (questions.length === 0) {\n const templateRaw = options.template;\n const template: TemplateName = isTemplateName(templateRaw ?? \"\")\n ? (templateRaw as TemplateName)\n : TEMPLATES.starter;\n\n return {\n name: projectName,\n template,\n installDeps: options.skipInstall ? false : true,\n selectedPages: [],\n } satisfies ProjectConfig;\n }\n\n // Handle Ctrl+C gracefully\n let cancelled = false;\n const response = await prompts(questions, {\n onCancel: () => {\n cancelled = true;\n return false;\n },\n });\n\n if (cancelled) {\n return null;\n }\n\n // Merge CLI options with prompted values\n const templateRaw = options.template ?? response.template;\n const template: TemplateName = isTemplateName(templateRaw)\n ? templateRaw\n : TEMPLATES.starter;\n\n // Parse selected pages\n const selectedPages: readonly SelectedPageTemplate[] =\n response.selectedPages ?? [];\n\n return {\n name: projectName,\n template,\n installDeps: options.skipInstall ? false : (response.installDeps ?? true),\n selectedPages,\n } satisfies ProjectConfig;\n}\n","/**\n * Result type utilities for type-safe error handling\n *\n * The Result<T, E> pattern provides a discriminated union for fallible operations,\n * enabling exhaustive handling without try/catch blocks.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result type - discriminated union for success/failure\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Successful result containing a value of type T\n */\nexport interface Success<T> {\n readonly success: true;\n readonly value: T;\n}\n\n/**\n * Failed result containing an error of type E\n */\nexport interface Failure<E> {\n readonly success: false;\n readonly error: E;\n}\n\n/**\n * Result type - discriminated union of Success and Failure\n * The `success` field serves as the discriminant for type narrowing\n */\nexport type Result<T, E = Error> = Success<T> | Failure<E>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constructor functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create a successful result\n */\nexport function success<T>(value: T): Success<T> {\n return { success: true, value };\n}\n\n/**\n * Create a failed result\n */\nexport function failure<E>(error: E): Failure<E> {\n return { success: false, error };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Type guards\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Type guard for successful results\n */\nexport function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {\n return result.success === true;\n}\n\n/**\n * Type guard for failed results\n */\nexport function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {\n return result.success === false;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Utility functions for working with Results\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Wrap a function that may throw into a Result-returning function\n */\nexport function tryCatch<T>(fn: () => T): Result<T, Error> {\n try {\n return success(fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/**\n * Wrap an async function that may throw into a Result-returning function\n */\nexport async function tryCatchAsync<T>(\n fn: () => Promise<T>,\n): Promise<Result<T, Error>> {\n try {\n return success(await fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/**\n * Extract value from Result or throw error\n * Use sparingly - prefer pattern matching with isSuccess/isFailure\n */\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (isSuccess(result)) {\n return result.value;\n }\n throw result.error;\n}\n\n/**\n * Extract value from Result or return a default value\n */\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (isSuccess(result)) {\n return result.value;\n }\n return defaultValue;\n}\n\n/**\n * Map over a successful Result value\n */\nexport function mapResult<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => U,\n): Result<U, E> {\n if (isSuccess(result)) {\n return success(fn(result.value));\n }\n return result;\n}\n\n/**\n * Map over a failed Result error\n */\nexport function mapError<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => F,\n): Result<T, F> {\n if (isFailure(result)) {\n return failure(fn(result.error));\n }\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error narrowing utilities - prefer these over `(error as Error).message`\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Type guard to check if a value is an Error instance\n *\n * @example\n * ```ts\n * catch (error) {\n * if (isError(error)) {\n * console.log(error.message); // TypeScript knows this is an Error\n * }\n * }\n * ```\n */\nexport function isError(value: unknown): value is Error {\n return value instanceof Error;\n}\n\n/**\n * Type guard for Node.js system errors (with `code` property)\n *\n * @example\n * ```ts\n * catch (error) {\n * if (isNodeError(error) && error.code === \"ENOENT\") {\n * console.log(\"File not found\");\n * }\n * }\n * ```\n */\nexport function isNodeError(value: unknown): value is NodeJS.ErrnoException {\n return value instanceof Error && \"code\" in value;\n}\n\n/**\n * Extract error message safely from unknown catch parameter.\n * Prefer type guards (isError, isNodeError) when you need the full error object.\n *\n * @example\n * ```ts\n * catch (error) {\n * console.log(\"Error: \" + getErrorMessage(error));\n * }\n * ```\n */\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error === \"string\") {\n return error;\n }\n return String(error);\n}\n","import { readdir, readFile, stat, mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport Handlebars from \"handlebars\";\nimport type { TemplateVariables } from \"../types.js\";\nimport { type Result, success, failure } from \"./result.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Find the package root by walking up from __dirname to the nearest package.json.\n * Works whether running from dist/ (bundled) or src/utils/ (tsx dev mode).\n */\nfunction findPackageRoot(): string {\n let dir = __dirname;\n while (!existsSync(join(dir, \"package.json\"))) {\n const parent = dirname(dir);\n if (parent === dir) throw new Error(\"Could not find package root\");\n dir = parent;\n }\n return dir;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// File system operation error types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Error types for file system operations\n */\nexport const FILE_SYSTEM_ERRORS = {\n directoryNotFound: \"DIRECTORY_NOT_FOUND\",\n fileNotFound: \"FILE_NOT_FOUND\",\n readError: \"READ_ERROR\",\n writeError: \"WRITE_ERROR\",\n templateError: \"TEMPLATE_ERROR\",\n} as const;\n\n/**\n * Union type for file system error codes\n */\nexport type FileSystemErrorCode =\n (typeof FILE_SYSTEM_ERRORS)[keyof typeof FILE_SYSTEM_ERRORS];\n\n/**\n * Structured file system error with code for pattern matching\n */\nexport interface FileSystemError {\n readonly code: FileSystemErrorCode;\n readonly message: string;\n readonly path?: string;\n readonly cause?: Error;\n}\n\n/**\n * Create a file system error\n */\nfunction createFsError(\n code: FileSystemErrorCode,\n message: string,\n path?: string,\n cause?: Error,\n): FileSystemError {\n return { code, message, path, cause };\n}\n\n/**\n * Paths for the base + overlay template system\n */\nexport interface TemplatePaths {\n /** Path to shared frontend files used by all templates */\n readonly base: string;\n /** Path to template-specific overlay files */\n readonly overlay: string;\n}\n\n/**\n * Gets paths for the base + overlay template system.\n *\n * The create command copies `base` first, then the `overlay` on top.\n * Any overlay file with the same relative path overwrites the base version.\n */\nexport function getTemplatePaths(templateName: string): TemplatePaths {\n const packageRoot = findPackageRoot();\n const templatesDir = join(packageRoot, \"templates\");\n return {\n base: join(templatesDir, \"base\"),\n overlay: join(templatesDir, templateName),\n };\n}\n\n/**\n * Gets all files in a directory recursively\n */\nasync function getFiles(dir: string, baseDir: string = dir): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await getFiles(fullPath, baseDir)));\n } else {\n // Return relative path from baseDir\n files.push(fullPath.slice(baseDir.length + 1));\n }\n }\n\n return files;\n}\n\n/**\n * Processes a template file with Handlebars\n * Files ending in .template have the extension removed and content processed\n * Other files are copied as-is\n */\nfunction processTemplate(\n content: string,\n variables: TemplateVariables,\n isTemplate: boolean,\n filePath?: string,\n): string {\n if (!isTemplate) {\n return content;\n }\n\n try {\n const template = Handlebars.compile(content);\n return template(variables);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Template processing failed${filePath ? ` for ${filePath}` : \"\"}: ${message}`,\n );\n }\n}\n\n/**\n * Gets the output filename for a template file\n * Removes .template extension if present\n */\nfunction getOutputFilename(filename: string): string {\n if (filename.endsWith(\".template\")) {\n return filename.slice(0, -\".template\".length);\n }\n return filename;\n}\n\n/**\n * Copies a template directory to the target directory\n * Processes .template files with Handlebars\n */\nexport async function copyTemplate(\n templatePath: string,\n targetPath: string,\n variables: TemplateVariables,\n): Promise<void> {\n const files = await getFiles(templatePath);\n\n for (const file of files) {\n const sourcePath = join(templatePath, file);\n const isTemplate = file.endsWith(\".template\");\n const outputFile = getOutputFilename(file);\n const destPath = join(targetPath, outputFile);\n\n // Create directory if needed\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n\n // Read source file\n const content = await readFile(sourcePath, \"utf-8\");\n\n // Process and write\n const processed = processTemplate(\n content,\n variables,\n isTemplate,\n sourcePath,\n );\n await writeFile(destPath, processed, \"utf-8\");\n }\n}\n\n/**\n * Checks if a directory exists\n */\nexport async function directoryExists(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Checks if a file exists\n */\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Checks if a path exists (file or directory)\n */\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Creates a directory\n */\nexport async function createDirectory(path: string): Promise<void> {\n await mkdir(path, { recursive: true });\n}\n\n/**\n * Reads the SDK version from the workspace package.json\n * Falls back to ^0.1.0 if not found\n */\nexport async function getSdkVersion(): Promise<string> {\n try {\n // Try to read from workspace\n // CLI lives at packages/cli/portal/, SDK at packages/portal/sdk/\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPackagePath = join(packagesRoot, \"portal\", \"sdk\", \"package.json\");\n\n const content = await readFile(sdkPackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n return `^${pkg.version ?? \"0.1.0\"}`;\n } catch {\n // Fallback for when running outside the workspace\n return \"^0.1.0\";\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result-based variants for type-safe error handling\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Read a file's content with Result-based error handling\n */\nexport async function readFileSafe(\n path: string,\n): Promise<Result<string, FileSystemError>> {\n try {\n const content = await readFile(path, \"utf-8\");\n return success(content);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.readError,\n `Failed to read file: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Write content to a file with Result-based error handling\n */\nexport async function writeFileSafe(\n path: string,\n content: string,\n): Promise<Result<void, FileSystemError>> {\n try {\n await writeFile(path, content, \"utf-8\");\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.writeError,\n `Failed to write file: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Create a directory with Result-based error handling\n */\nexport async function createDirectorySafe(\n path: string,\n): Promise<Result<void, FileSystemError>> {\n try {\n await mkdir(path, { recursive: true });\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.writeError,\n `Failed to create directory: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Copy a template directory with Result-based error handling\n */\nexport async function copyTemplateSafe(\n templatePath: string,\n targetPath: string,\n variables: Readonly<TemplateVariables>,\n): Promise<Result<void, FileSystemError>> {\n try {\n const files = await getFiles(templatePath);\n\n for (const file of files) {\n const sourcePath = join(templatePath, file);\n const isTemplateFile = file.endsWith(\".template\");\n const outputFile = getOutputFilename(file);\n const destPath = join(targetPath, outputFile);\n\n // Create directory if needed\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n\n // Read source file\n const content = await readFile(sourcePath, \"utf-8\");\n\n // Process and write\n const processed = processTemplate(\n content,\n variables,\n isTemplateFile,\n sourcePath,\n );\n await writeFile(destPath, processed, \"utf-8\");\n }\n\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.templateError,\n `Failed to copy template from ${templatePath} to ${targetPath}`,\n templatePath,\n error,\n ),\n );\n }\n}\n\n/**\n * Get SDK version with Result-based error handling\n * Unlike getSdkVersion, this returns an error instead of a fallback\n */\nexport async function getSdkVersionSafe(): Promise<\n Result<string, FileSystemError>\n> {\n try {\n // CLI lives at packages/cli/portal/, SDK at packages/portal/sdk/\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPackagePath = join(packagesRoot, \"portal\", \"sdk\", \"package.json\");\n\n const content = await readFile(sdkPackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n const version = pkg.version;\n\n if (version === undefined) {\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.readError,\n \"SDK package.json does not contain a version field\",\n sdkPackagePath,\n ),\n );\n }\n\n return success(`^${version}`);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.fileNotFound,\n \"Could not find SDK package.json\",\n undefined,\n error,\n ),\n );\n }\n}\n","import { execa } from \"execa\";\n\n/**\n * Returns the install command for pnpm\n */\nexport function getInstallCommand(): string {\n return \"pnpm install\";\n}\n\n/**\n * Returns the run command for pnpm\n */\nexport function getRunCommand(script: string): string {\n return `pnpm run ${script}`;\n}\n\n/**\n * Runs a pnpm command in the specified directory\n */\nexport async function runPackageManager(\n args: string[],\n cwd: string,\n): Promise<void> {\n await execa(\"pnpm\", args, {\n cwd,\n stdio: \"inherit\",\n });\n}\n\n/**\n * Installs dependencies using pnpm\n */\nexport async function installDependencies(cwd: string): Promise<void> {\n await runPackageManager([\"install\"], cwd);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { join } from \"node:path\";\nimport { copyFile } from \"node:fs/promises\";\nimport { type CreateOptions, TEMPLATES } from \"../types.js\";\nimport { promptProjectConfig } from \"../utils/prompts.js\";\nimport {\n getTemplatePaths,\n copyTemplate,\n directoryExists,\n createDirectory,\n getSdkVersion,\n fileExists,\n} from \"../utils/file-system.js\";\nimport {\n installDependencies,\n getRunCommand,\n} from \"../utils/package-manager.js\";\n\nexport const createCommand: Command = new Command(\"create\")\n .description(\"Create a new Fluid portal application\")\n .argument(\"<app-name>\", \"Name of the application to create\")\n .option(\"-t, --template <template>\", \"Project template (starter, fullstack)\")\n .option(\"--skip-install\", \"Skip dependency installation\")\n .action(async (appName: string, options: CreateOptions) => {\n try {\n console.log();\n console.log(chalk.bold(\"Creating a new Fluid portal application\"));\n console.log();\n\n // Validate app name\n if (!/^[a-z0-9-]+$/.test(appName)) {\n console.error(\n chalk.red(\n \"Error: App name must contain only lowercase letters, numbers, and hyphens\",\n ),\n );\n process.exit(1);\n }\n\n // Check if directory already exists\n const targetPath = join(process.cwd(), appName);\n if (await directoryExists(targetPath)) {\n console.error(\n chalk.red(`Error: Directory \"${appName}\" already exists`),\n );\n process.exit(1);\n }\n\n // Prompt for configuration\n const config = await promptProjectConfig(appName, options);\n if (!config) {\n console.log();\n console.log(chalk.yellow(\"Cancelled\"));\n process.exit(0);\n }\n\n console.log();\n\n // Get template paths (base + overlay)\n const templatePaths = getTemplatePaths(config.template);\n if (!(await directoryExists(templatePaths.base))) {\n console.error(chalk.red(\"Error: Base template not found\"));\n process.exit(1);\n }\n if (!(await directoryExists(templatePaths.overlay))) {\n console.error(\n chalk.red(`Error: Template \"${config.template}\" not found`),\n );\n process.exit(1);\n }\n\n // Get SDK version\n const sdkVersion = await getSdkVersion();\n\n // Create project directory\n const spinner = ora(\"Creating project directory...\").start();\n try {\n await createDirectory(targetPath);\n spinner.succeed(\"Created project directory\");\n } catch (error) {\n spinner.fail(\"Failed to create project directory\");\n throw error;\n }\n\n // Copy base template first, then overlay template-specific files on top\n const templateVariables = {\n projectName: config.name,\n sdkVersion,\n selectedPages: config.selectedPages,\n hasSelectedPages: config.selectedPages.length > 0,\n };\n\n spinner.start(\"Copying template files...\");\n try {\n await copyTemplate(templatePaths.base, targetPath, templateVariables);\n await copyTemplate(\n templatePaths.overlay,\n targetPath,\n templateVariables,\n );\n\n // Copy .env.example → .env so dotenv works out of the box\n const envExamplePath = join(targetPath, \".env.example\");\n if (await fileExists(envExamplePath)) {\n await copyFile(envExamplePath, join(targetPath, \".env\"));\n }\n\n spinner.succeed(\"Copied template files\");\n } catch (error) {\n spinner.fail(\"Failed to copy template files\");\n throw error;\n }\n\n // Install dependencies\n if (config.installDeps) {\n spinner.start(\"Installing dependencies with pnpm...\");\n try {\n await installDependencies(targetPath);\n spinner.succeed(\"Installed dependencies\");\n } catch {\n spinner.fail(\"Failed to install dependencies\");\n console.log();\n console.log(\n chalk.yellow(\"You can try installing dependencies manually:\"),\n );\n console.log(chalk.cyan(` cd ${appName}`));\n console.log(chalk.cyan(\" pnpm install\"));\n }\n }\n\n // Print success message\n console.log();\n console.log(\n chalk.green.bold(\"Success!\") + ` Created ${chalk.cyan(appName)}`,\n );\n console.log();\n console.log(\"Next steps:\");\n console.log();\n console.log(chalk.cyan(` cd ${appName}`));\n if (!config.installDeps) {\n console.log(chalk.cyan(\" pnpm install\"));\n }\n if (config.template === TEMPLATES.fullstack) {\n console.log(\n chalk.cyan(` ${getRunCommand(\"db:push\")}`) +\n \" # create the database\",\n );\n }\n console.log(chalk.cyan(` ${getRunCommand(\"dev\")}`));\n console.log();\n console.log(\n \"Then open \" +\n chalk.cyan(\"http://localhost:5173\") +\n \" in your browser.\",\n );\n if (config.template === TEMPLATES.fullstack) {\n console.log(\n \"API server: \" + chalk.cyan(\"http://localhost:5173/api/health\"),\n );\n }\n console.log(\n chalk.dim(\n \" (port may differ if 5173 is in use — check the dev server output)\",\n ),\n );\n console.log();\n console.log(\n \"Edit \" +\n chalk.cyan(\"src/portal.config.ts\") +\n \" to customize your navigation.\",\n );\n console.log();\n } catch (error) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (error instanceof Error ? error.message : String(error)),\n );\n console.log();\n process.exit(1);\n }\n });\n\nexport function registerCreateCommand(ctx: PluginContext): void {\n ctx.program.addCommand(createCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport { execa } from \"execa\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { DevOptions } from \"../types.js\";\n\nexport const devCommand: Command = new Command(\"dev\")\n .description(\"Start the development server\")\n .option(\"-p, --port <port>\", \"Port to run the dev server on\", \"5173\")\n .option(\"--host\", \"Expose the dev server to the network\")\n .action(async (options: DevOptions) => {\n const cwd = process.cwd();\n\n // Check if we're in a Fluid project\n const packageJsonPath = join(cwd, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n console.error(\n chalk.red(\"Error: No package.json found in current directory\"),\n );\n console.error(\n chalk.yellow(\"Make sure you're in a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n // Check for vite.config\n const viteConfigPath = join(cwd, \"vite.config.ts\");\n if (!existsSync(viteConfigPath)) {\n console.error(chalk.red(\"Error: No vite.config.ts found\"));\n console.error(\n chalk.yellow(\"This command must be run from a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n // Build vite args\n const viteArgs = [\"vite\"];\n if (options.port) {\n viteArgs.push(\"--port\", String(options.port));\n }\n if (options.host) {\n viteArgs.push(\"--host\");\n }\n\n console.log();\n console.log(chalk.bold(\"Starting development server...\"));\n console.log();\n\n try {\n await execa(\"pnpm\", viteArgs, {\n cwd,\n stdio: \"inherit\",\n });\n } catch (error) {\n // execa v8 sets `signal` (not `code`) when a process is killed by a signal\n const execaError = error as { signal?: string };\n if (execaError.signal === \"SIGINT\") {\n return;\n }\n console.error(chalk.red(\"Development server exited with an error\"));\n process.exit(1);\n }\n });\n\nexport function registerDevCommand(ctx: PluginContext): void {\n ctx.program.addCommand(devCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { execa } from \"execa\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { BuildOptions } from \"../types.js\";\n\nexport const buildCommand: Command = new Command(\"build\")\n .description(\"Build the application for production\")\n .option(\"-o, --out-dir <dir>\", \"Output directory\", \"dist\")\n .action(async (options: BuildOptions) => {\n const cwd = process.cwd();\n\n // Check if we're in a Fluid project\n const packageJsonPath = join(cwd, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n console.error(\n chalk.red(\"Error: No package.json found in current directory\"),\n );\n console.error(\n chalk.yellow(\"Make sure you're in a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n // Check for vite.config\n const viteConfigPath = join(cwd, \"vite.config.ts\");\n if (!existsSync(viteConfigPath)) {\n console.error(chalk.red(\"Error: No vite.config.ts found\"));\n console.error(\n chalk.yellow(\"This command must be run from a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold(\"Building for production...\"));\n console.log();\n\n const spinner = ora(\"Building...\").start();\n\n try {\n // Run the project's build script (handles both starter and fullstack templates)\n await execa(\"pnpm\", [\"run\", \"build\"], {\n cwd,\n stdio: \"pipe\",\n });\n\n spinner.succeed(\"Build completed\");\n console.log();\n console.log(`Output written to ${chalk.cyan(options.outDir ?? \"dist\")}/`);\n console.log();\n console.log(\"To preview the build locally:\");\n console.log(chalk.cyan(\" pnpm vite preview\"));\n console.log();\n } catch (error) {\n spinner.fail(\"Build failed\");\n const execaError = error as { stderr?: string };\n if (execaError.stderr) {\n console.error(execaError.stderr);\n }\n process.exit(1);\n }\n });\n\nexport function registerBuildCommand(ctx: PluginContext): void {\n ctx.program.addCommand(buildCommand);\n}\n","/**\n * Turso database provisioning utilities\n *\n * This module provides functions for provisioning Turso databases\n * using the Turso Platform REST API for Fluid portal app deployments.\n *\n * Credential resolution order:\n * 1. Environment variables (TURSO_API_TOKEN + TURSO_ORG) — CI/CD\n * 2. --turso-org flag > single org > \"fluid\" org > current org > interactive prompt\n * 3. Fail with instructions for both options\n */\n\nimport { type Result, success, failure } from \"./result.js\";\nimport { execa } from \"execa\";\nimport prompts from \"prompts\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Turso Platform API base URL\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst TURSO_API_BASE = \"https://api.turso.tech/v1\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error constants - structured error definitions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const TURSO_ERROR = {\n MISSING_TOKEN: {\n code: \"MISSING_TOKEN\",\n message: \"TURSO_API_TOKEN environment variable is not set\",\n },\n MISSING_ORG: {\n code: \"MISSING_ORG\",\n message: \"TURSO_ORG environment variable is not set\",\n },\n GROUP_CREATION_FAILED: {\n code: \"GROUP_CREATION_FAILED\",\n message: \"Failed to create database group\",\n },\n DATABASE_CREATION_FAILED: {\n code: \"DATABASE_CREATION_FAILED\",\n message: \"Failed to create database\",\n },\n TOKEN_CREATION_FAILED: {\n code: \"TOKEN_CREATION_FAILED\",\n message: \"Failed to create database auth token\",\n },\n DATABASE_DELETION_FAILED: {\n code: \"DATABASE_DELETION_FAILED\",\n message: \"Failed to delete database\",\n },\n INVALID_LOCATION: {\n code: \"INVALID_LOCATION\",\n message: \"Invalid database location\",\n },\n LOCATIONS_FETCH_FAILED: {\n code: \"LOCATIONS_FETCH_FAILED\",\n message: \"Failed to fetch available Turso locations\",\n },\n TURSO_CLI_NOT_FOUND: {\n code: \"TURSO_CLI_NOT_FOUND\",\n message: \"Turso CLI is not installed\",\n },\n TURSO_CLI_NOT_AUTHENTICATED: {\n code: \"TURSO_CLI_NOT_AUTHENTICATED\",\n message: \"Turso CLI is not authenticated\",\n },\n TURSO_NO_ORGS: {\n code: \"TURSO_NO_ORGS\",\n message: \"No organizations found in Turso CLI\",\n },\n} as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Turso error codes derived from error constants\n */\nexport type TursoErrorCode =\n (typeof TURSO_ERROR)[keyof typeof TURSO_ERROR][\"code\"];\n\n/**\n * Structured Turso error with code for pattern matching\n */\nexport interface TursoError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\n/**\n * Create a Turso error from a constant and optional details\n */\nfunction createTursoError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): TursoError {\n return { code: template.code, message: template.message, details };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Configuration types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validated Turso configuration from environment variables\n */\nexport interface TursoConfig {\n readonly apiToken: string;\n readonly org: string;\n}\n\n/**\n * Where the Turso credentials were resolved from\n */\nexport type TursoConfigSource = \"env\" | \"cli\";\n\n/**\n * Turso configuration with source tracking\n */\nexport interface ResolvedTursoConfig extends TursoConfig {\n readonly source: TursoConfigSource;\n}\n\n/**\n * Parsed Turso organization from CLI output\n */\nexport interface TursoOrg {\n readonly name: string;\n readonly slug: string;\n readonly isCurrent: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Provisioned Turso database connection details\n */\nexport interface TursoDatabase {\n readonly url: string;\n readonly authToken: string;\n readonly databaseName: string;\n readonly hostname: string;\n /** true when the database was freshly created (HTTP 200), false when it already existed (HTTP 409) */\n readonly isNew: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Callback types for progress tracking\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Callbacks for database provisioning progress\n */\nexport interface TursoProvisionCallbacks {\n readonly onGroupCreating?: () => void;\n readonly onGroupReady?: () => void;\n readonly onDatabaseCreating?: (name: string) => void;\n readonly onDatabaseReady?: (name: string) => void;\n readonly onTokenCreating?: () => void;\n readonly onTokenReady?: () => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Configuration validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validate that required Turso environment variables are present\n */\nexport function validateTursoConfig(): Result<TursoConfig, TursoError> {\n const apiToken = process.env.TURSO_API_TOKEN;\n if (!apiToken) {\n return failure(createTursoError(TURSO_ERROR.MISSING_TOKEN));\n }\n\n const org = process.env.TURSO_ORG;\n if (!org) {\n return failure(createTursoError(TURSO_ERROR.MISSING_ORG));\n }\n\n return success({ apiToken, org });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Turso CLI helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Check if the Turso CLI is installed and authenticated.\n * Runs `turso auth whoami` which verifies both in a single call.\n */\nasync function isTursoCliAvailable(): Promise<boolean> {\n try {\n await execa(\"turso\", [\"auth\", \"whoami\"], { stdio: \"pipe\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the Turso platform API token from the CLI.\n * Runs `turso auth token` which outputs the token to stdout.\n */\nasync function getTursoCliToken(): Promise<Result<string, TursoError>> {\n try {\n const { stdout } = await execa(\"turso\", [\"auth\", \"token\"], {\n stdio: \"pipe\",\n });\n // The CLI prints a warning before the token — grab the last non-empty line\n const token = stdout.trim().split(\"\\n\").filter(Boolean).pop()?.trim() ?? \"\";\n if (!token) {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_CLI_NOT_AUTHENTICATED,\n \"turso auth token returned an empty value. Run: turso auth login\",\n ),\n );\n }\n return success(token);\n } catch {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_CLI_NOT_AUTHENTICATED,\n \"Failed to get token from Turso CLI. Run: turso auth login\",\n ),\n );\n }\n}\n\n/**\n * Parse the tabular output of `turso org list`.\n *\n * Example input:\n * Name Slug Type\n * My Org my-org personal (current)\n * Team Org team-org team\n *\n * Exported for unit testing.\n */\nexport function parseOrgList(stdout: string): TursoOrg[] {\n const lines = stdout.trim().split(\"\\n\");\n // Skip the header row\n if (lines.length <= 1) return [];\n\n return lines.slice(1).reduce<TursoOrg[]>((orgs, line) => {\n const trimmed = line.trim();\n if (!trimmed) return orgs;\n\n // Split on 2+ whitespace characters (tab-like column separators)\n const columns = trimmed.split(/\\s{2,}/);\n if (columns.length < 2) return orgs;\n\n const name = columns[0]!.trim();\n const slug = columns[1]!.trim().replace(/\\s*\\(current\\)/, \"\");\n const isCurrent = trimmed.includes(\"(current)\");\n\n if (name && slug) {\n orgs.push({ name, slug, isCurrent });\n }\n return orgs;\n }, []);\n}\n\n/**\n * Get the list of Turso organizations from the CLI.\n */\nasync function getTursoCliOrgs(): Promise<Result<TursoOrg[], TursoError>> {\n try {\n const { stdout } = await execa(\"turso\", [\"org\", \"list\"], {\n stdio: \"pipe\",\n });\n const orgs = parseOrgList(stdout);\n if (orgs.length === 0) {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_NO_ORGS,\n \"No organizations found. Create one at https://turso.tech\",\n ),\n );\n }\n return success(orgs);\n } catch {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_NO_ORGS,\n \"Failed to list organizations from Turso CLI.\",\n ),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Credential resolution\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst TURSO_AUTH_HELP = [\n \"Option 1 — Turso CLI (recommended for local dev):\",\n \" curl -sSfL https://get.tur.so/install.sh | bash\",\n \" turso auth login\",\n \"\",\n \"Option 2 — Environment variables (CI/CD):\",\n \" export TURSO_API_TOKEN=your_token_here\",\n \" export TURSO_ORG=your_org_name\",\n \"\",\n \"Get your API token at: https://turso.tech/app/settings/api-tokens\",\n].join(\"\\n\");\n\n/**\n * Resolve Turso credentials from the best available source.\n *\n * Priority:\n * 1. Environment variables (TURSO_API_TOKEN + TURSO_ORG) — immediate, for CI/CD\n * 2. Turso CLI (turso auth token + turso org list) — interactive, for local dev\n * 3. Fail with instructions for both options\n *\n * @param tursoOrgOverride - Optional org slug from --turso-org flag (skips interactive selection)\n */\nexport async function resolveTursoConfig(\n tursoOrgOverride?: string,\n): Promise<Result<ResolvedTursoConfig, TursoError>> {\n // ── Path 1: Environment variables ──────────────────────────────────────\n const envToken = process.env.TURSO_API_TOKEN;\n const envOrg = process.env.TURSO_ORG;\n\n if (envToken && envOrg) {\n return success({ apiToken: envToken, org: envOrg, source: \"env\" });\n }\n\n // ── Path 2: Turso CLI ─────────────────────────────────────────────────\n const cliAvailable = await isTursoCliAvailable();\n if (!cliAvailable) {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_CLI_NOT_FOUND,\n `No Turso credentials found.\\n\\n${TURSO_AUTH_HELP}`,\n ),\n );\n }\n\n // Get API token from CLI\n const tokenResult = await getTursoCliToken();\n if (!tokenResult.success) return tokenResult;\n const apiToken = tokenResult.value;\n\n // Resolve organization\n if (tursoOrgOverride) {\n return success({\n apiToken,\n org: tursoOrgOverride,\n source: \"cli\",\n });\n }\n\n const orgsResult = await getTursoCliOrgs();\n if (!orgsResult.success) return orgsResult;\n const orgs = orgsResult.value;\n\n // Single org — auto-select\n if (orgs.length === 1) {\n return success({\n apiToken,\n org: orgs[0]!.slug,\n source: \"cli\",\n });\n }\n\n // Multiple orgs — check for \"fluid\" org first, then current, then prompt\n const fluidOrg = orgs.find((o) => o.slug === \"fluid\");\n if (fluidOrg) {\n return success({ apiToken, org: fluidOrg.slug, source: \"cli\" });\n }\n\n const currentOrg = orgs.find((o) => o.isCurrent);\n if (currentOrg) {\n return success({\n apiToken,\n org: currentOrg.slug,\n source: \"cli\",\n });\n }\n\n // Interactive selection\n const { orgSlug } = await prompts({\n type: \"select\",\n name: \"orgSlug\",\n message: \"Which Turso organization?\",\n choices: orgs.map((o) => ({\n title: `${o.name} (${o.slug})`,\n value: o.slug,\n })),\n });\n\n if (!orgSlug) {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_NO_ORGS,\n \"Organization selection cancelled.\",\n ),\n );\n }\n\n return success({ apiToken, org: orgSlug, source: \"cli\" });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API helper\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build standard headers for Turso API requests\n */\nfunction buildHeaders(apiToken: string): Record<string, string> {\n return {\n Authorization: `Bearer ${apiToken}`,\n \"Content-Type\": \"application/json\",\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Location validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Fetch available Turso database locations.\n * Returns a map of location ID → description (e.g., \"aws-us-east-1\" → \"US East (N. Virginia)\").\n */\nexport async function fetchLocations(\n config: TursoConfig,\n): Promise<Result<Record<string, string>, TursoError>> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(`${TURSO_API_BASE}/locations`, {\n method: \"GET\",\n headers: buildHeaders(config.apiToken),\n signal: controller.signal,\n });\n clearTimeout(timeout);\n\n if (!response.ok) {\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.LOCATIONS_FETCH_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n }\n\n const data = (await response.json()) as {\n locations: Record<string, string>;\n };\n return success(data.locations);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.LOCATIONS_FETCH_FAILED, message),\n );\n }\n}\n\n/**\n * Validate that a location string is a known Turso location.\n * On failure, returns an error listing all valid locations.\n */\nexport async function validateLocation(\n config: TursoConfig,\n location: string,\n): Promise<Result<void, TursoError>> {\n const locationsResult = await fetchLocations(config);\n if (!locationsResult.success) {\n return locationsResult;\n }\n\n const locations = locationsResult.value;\n if (location in locations) {\n return success(undefined);\n }\n\n const validLocations = Object.entries(locations)\n .map(([id, desc]) => ` ${id} — ${desc}`)\n .join(\"\\n\");\n\n return failure(\n createTursoError(\n TURSO_ERROR.INVALID_LOCATION,\n `\"${location}\" is not a valid Turso location.\\n\\nAvailable locations:\\n${validLocations}`,\n ),\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Group management\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Ensure a default database group exists in the Turso organization.\n * Creates the group if it does not exist; treats 409 (conflict) as success\n * since it means the group already exists.\n */\nexport async function ensureGroup(\n config: TursoConfig,\n location = \"aws-us-east-1\",\n): Promise<Result<void, TursoError>> {\n try {\n // Check if \"default\" group already exists\n const listController = new AbortController();\n const listTimeout = setTimeout(() => listController.abort(), 30_000);\n const listResponse = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/groups`,\n {\n method: \"GET\",\n headers: buildHeaders(config.apiToken),\n signal: listController.signal,\n },\n );\n clearTimeout(listTimeout);\n\n if (listResponse.ok) {\n const data = (await listResponse.json()) as {\n groups: Array<{ name: string }>;\n };\n if (data.groups.some((g) => g.name === \"default\")) {\n return success(undefined);\n }\n }\n\n // Group doesn't exist — create it\n const createController = new AbortController();\n const createTimeout = setTimeout(() => createController.abort(), 30_000);\n const createResponse = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/groups`,\n {\n method: \"POST\",\n headers: buildHeaders(config.apiToken),\n body: JSON.stringify({ name: \"default\", location }),\n signal: createController.signal,\n },\n );\n clearTimeout(createTimeout);\n\n // 409 = group already exists (race condition safety net)\n if (createResponse.ok || createResponse.status === 409) {\n return success(undefined);\n }\n\n const body = await createResponse.text();\n return failure(\n createTursoError(\n TURSO_ERROR.GROUP_CREATION_FAILED,\n `HTTP ${createResponse.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.GROUP_CREATION_FAILED, message),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Database creation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create a new Turso database in the default group.\n * If the database already exists (409), fetches its info via GET instead.\n * Returns the database name and hostname.\n */\nexport async function createDatabase(\n config: TursoConfig,\n name: string,\n): Promise<\n Result<{ name: string; hostname: string; isNew: boolean }, TursoError>\n> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/databases`,\n {\n method: \"POST\",\n headers: buildHeaders(config.apiToken),\n body: JSON.stringify({ name, group: \"default\" }),\n signal: controller.signal,\n },\n );\n clearTimeout(timeout);\n\n if (response.ok) {\n const data = (await response.json()) as {\n database?: Record<string, string>;\n };\n if (!data.database) {\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n \"Unexpected API response: missing database object\",\n ),\n );\n }\n return success({\n name: data.database.Name ?? data.database.name ?? name,\n hostname: data.database.Hostname ?? data.database.hostname ?? \"\",\n isNew: true,\n });\n }\n\n // Database already exists — fetch its info\n if (response.status === 409) {\n const existing = await getDatabaseInfo(config, name);\n if (!existing.success) return existing;\n return success({ ...existing.value, isNew: false });\n }\n\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.DATABASE_CREATION_FAILED, message),\n );\n }\n}\n\n/**\n * Fetch existing database info by name\n */\nasync function getDatabaseInfo(\n config: TursoConfig,\n name: string,\n): Promise<Result<{ name: string; hostname: string }, TursoError>> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/databases/${name}`,\n {\n method: \"GET\",\n headers: buildHeaders(config.apiToken),\n signal: controller.signal,\n },\n );\n clearTimeout(timeout);\n\n if (!response.ok) {\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n `Failed to fetch existing database info: HTTP ${response.status}: ${body}`,\n ),\n );\n }\n\n const data = (await response.json()) as {\n database?: Record<string, string>;\n };\n if (!data.database) {\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n \"Unexpected API response: missing database object\",\n ),\n );\n }\n return success({\n name: data.database.Name ?? data.database.name ?? name,\n hostname: data.database.Hostname ?? data.database.hostname ?? \"\",\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n `Failed to fetch existing database info: ${message}`,\n ),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Database deletion\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Delete a Turso database by name.\n * Returns void on success, or a TursoError on failure.\n */\nexport async function deleteDatabase(\n config: TursoConfig,\n name: string,\n): Promise<Result<void, TursoError>> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/databases/${name}`,\n {\n method: \"DELETE\",\n headers: buildHeaders(config.apiToken),\n signal: controller.signal,\n },\n );\n clearTimeout(timeout);\n\n if (response.ok || response.status === 404) {\n return success(undefined);\n }\n\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_DELETION_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.DATABASE_DELETION_FAILED, message),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Auth token creation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create an auth token for a Turso database.\n * Returns the JWT token string used for database connections.\n */\nexport async function createDatabaseToken(\n config: TursoConfig,\n dbName: string,\n): Promise<Result<string, TursoError>> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/databases/${dbName}/auth/tokens`,\n {\n method: \"POST\",\n headers: buildHeaders(config.apiToken),\n signal: controller.signal,\n },\n );\n clearTimeout(timeout);\n\n if (!response.ok) {\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.TOKEN_CREATION_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n }\n\n const data = (await response.json()) as { jwt?: string };\n if (!data.jwt) {\n return failure(\n createTursoError(\n TURSO_ERROR.TOKEN_CREATION_FAILED,\n \"Unexpected API response: missing jwt field\",\n ),\n );\n }\n return success(data.jwt);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.TOKEN_CREATION_FAILED, message),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Full provisioning workflow\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Provision a complete Turso database for a project.\n *\n * Orchestrates the full flow:\n * 1. Ensure a default group exists\n * 2. Create (or retrieve) the database\n * 3. Generate an auth token\n *\n * Calls progress callbacks at each step so callers can display status.\n */\nexport async function provisionDatabase(\n config: TursoConfig,\n projectName: string,\n location?: string,\n callbacks?: TursoProvisionCallbacks,\n): Promise<Result<TursoDatabase, TursoError>> {\n // Step 0: Validate location if provided\n if (location) {\n const locationResult = await validateLocation(config, location);\n if (!locationResult.success) return locationResult;\n }\n\n // Step 1: Ensure default group exists\n callbacks?.onGroupCreating?.();\n const groupResult = await ensureGroup(config, location);\n if (!groupResult.success) {\n return groupResult;\n }\n callbacks?.onGroupReady?.();\n\n // Step 2: Create the database\n callbacks?.onDatabaseCreating?.(projectName);\n const dbResult = await createDatabase(config, projectName);\n if (!dbResult.success) {\n return dbResult;\n }\n const { name: databaseName, hostname, isNew } = dbResult.value;\n callbacks?.onDatabaseReady?.(databaseName);\n\n // Step 3: Create an auth token\n callbacks?.onTokenCreating?.();\n const tokenResult = await createDatabaseToken(config, databaseName);\n if (!tokenResult.success) {\n return tokenResult;\n }\n const authToken = tokenResult.value;\n callbacks?.onTokenReady?.();\n\n return success({\n url: `libsql://${hostname}`,\n authToken,\n databaseName,\n hostname,\n isNew,\n });\n}\n","/**\n * Cloud Run deployment utilities\n *\n * This module provides functions for deploying Fluid portal apps to Google Cloud Run\n * using the gcloud CLI via execa for programmatic deployments.\n */\n\nimport { execa } from \"execa\";\nimport { type Result, success, failure } from \"./result.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error constants for Cloud Run operations\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Cloud Run error codes and default messages\n */\nexport const CLOUD_RUN_ERRORS = {\n GCLOUD_NOT_INSTALLED: {\n code: \"GCLOUD_NOT_INSTALLED\",\n message:\n \"gcloud CLI is not installed. Install it from https://cloud.google.com/sdk/docs/install\",\n },\n GCLOUD_NOT_AUTHENTICATED: {\n code: \"GCLOUD_NOT_AUTHENTICATED\",\n message:\n \"gcloud CLI is not authenticated. Run 'gcloud auth login' to authenticate\",\n },\n NO_GCP_PROJECT: {\n code: \"NO_GCP_PROJECT\",\n message:\n \"No GCP project configured. Run 'gcloud config set project PROJECT_ID' or pass --gcp-project\",\n },\n DEPLOY_FAILED: {\n code: \"DEPLOY_FAILED\",\n message: \"Cloud Run deployment failed\",\n },\n SERVICE_DELETION_FAILED: {\n code: \"SERVICE_DELETION_FAILED\",\n message: \"Cloud Run service deletion failed\",\n },\n} as const;\n\n/**\n * Union type for Cloud Run error codes\n */\nexport type CloudRunErrorCode =\n (typeof CLOUD_RUN_ERRORS)[keyof typeof CLOUD_RUN_ERRORS][\"code\"];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Interfaces\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Configuration for Cloud Run deployment\n */\nexport interface CloudRunConfig {\n /** GCP project ID */\n readonly gcpProject: string;\n /** GCP region (e.g., \"us-central1\") */\n readonly region: string;\n /** Cloud Run service name */\n readonly serviceName: string;\n /** Environment variables to set on the service */\n readonly envVars: Record<string, string>;\n /** Path to the source directory to deploy */\n readonly sourceDir: string;\n /** Require IAM authentication (default: public / allow-unauthenticated) */\n readonly requireAuth?: boolean;\n}\n\n/**\n * Structured error for Cloud Run operations\n */\nexport interface CloudRunError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\n/**\n * Result of a successful Cloud Run deployment\n */\nexport interface CloudRunResult {\n /** Service URL (e.g., \"https://my-service-abc123.a.run.app\") */\n readonly url: string;\n /** Cloud Run service name */\n readonly serviceName: string;\n /** GCP region the service is deployed to */\n readonly region: string;\n /** GCP project ID */\n readonly gcpProject: string;\n}\n\n/**\n * Callbacks for Cloud Run deployment progress tracking\n */\nexport interface CloudRunDeployCallbacks {\n readonly onValidating?: () => void;\n readonly onDeploying?: () => void;\n readonly onDeployComplete?: (url: string) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Internal helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create a CloudRunError from a constant and optional details\n */\nfunction createCloudRunError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): CloudRunError {\n return { code: template.code, message: template.message, details };\n}\n\n/**\n * Build a KEY=VALUE string from an env vars record using gcloud's custom\n * delimiter syntax to avoid issues with values containing commas.\n * Prefix with `^::^` and join entries with `::` instead of `,`.\n */\nfunction buildEnvVarsString(envVars: Record<string, string>): string {\n const entries = Object.entries(envVars).map(\n ([key, value]) => `${key}=${value}`,\n );\n return `^::^${entries.join(\"::\")}`;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Validation functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validate that the gcloud CLI is installed\n */\nexport async function validateGcloudInstalled(): Promise<\n Result<void, CloudRunError>\n> {\n try {\n await execa(\"gcloud\", [\"--version\"], { stdio: \"pipe\", timeout: 60_000 });\n return success(undefined);\n } catch {\n return failure(createCloudRunError(CLOUD_RUN_ERRORS.GCLOUD_NOT_INSTALLED));\n }\n}\n\n/**\n * Validate that the gcloud CLI has an active authenticated account\n */\nexport async function validateGcloudAuth(): Promise<\n Result<void, CloudRunError>\n> {\n try {\n const { stdout } = await execa(\n \"gcloud\",\n [\"auth\", \"list\", \"--format=json\"],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n const accounts: readonly { status: string }[] = JSON.parse(stdout);\n const hasActiveAccount = accounts.some(\n (account) => account.status === \"ACTIVE\",\n );\n\n if (!hasActiveAccount) {\n return failure(\n createCloudRunError(CLOUD_RUN_ERRORS.GCLOUD_NOT_AUTHENTICATED),\n );\n }\n\n return success(undefined);\n } catch {\n return failure(\n createCloudRunError(\n CLOUD_RUN_ERRORS.GCLOUD_NOT_AUTHENTICATED,\n \"Failed to check gcloud authentication status\",\n ),\n );\n }\n}\n\n/**\n * Get the currently configured GCP project from gcloud config\n */\nexport async function getGcpProject(): Promise<Result<string, CloudRunError>> {\n try {\n const { stdout } = await execa(\n \"gcloud\",\n [\"config\", \"get-value\", \"project\"],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n const project = stdout.trim();\n\n if (!project || project === \"(unset)\") {\n return failure(createCloudRunError(CLOUD_RUN_ERRORS.NO_GCP_PROJECT));\n }\n\n return success(project);\n } catch {\n return failure(\n createCloudRunError(\n CLOUD_RUN_ERRORS.NO_GCP_PROJECT,\n \"Failed to read GCP project from gcloud config\",\n ),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Build log retrieval (best-effort diagnostics)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Fetch the last 30 lines of the most recent Cloud Build log.\n * Returns `undefined` on any failure — this is best-effort diagnostic info.\n */\nasync function fetchRecentBuildLogs(\n gcpProject: string,\n region: string,\n): Promise<string | undefined> {\n try {\n // Get the most recent build ID\n const { stdout: buildId } = await execa(\n \"gcloud\",\n [\n \"builds\",\n \"list\",\n \"--limit=1\",\n \"--project\",\n gcpProject,\n \"--region\",\n region,\n \"--format=value(id)\",\n ],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n const trimmedId = buildId.trim();\n if (!trimmedId) return undefined;\n\n // Fetch the build log\n const { stdout: logText } = await execa(\n \"gcloud\",\n [\"builds\", \"log\", trimmedId, \"--project\", gcpProject, \"--region\", region],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n // Return last 30 lines\n const lines = logText.split(\"\\n\");\n return lines.slice(-30).join(\"\\n\");\n } catch {\n return undefined;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Service URL retrieval (fallback)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Retrieve the service URL via `gcloud run services describe`.\n * Used as a fallback when the deploy command does not return parseable JSON.\n */\nasync function getServiceUrl(\n serviceName: string,\n gcpProject: string,\n region: string,\n): Promise<Result<string, CloudRunError>> {\n try {\n const { stdout } = await execa(\n \"gcloud\",\n [\n \"run\",\n \"services\",\n \"describe\",\n serviceName,\n \"--project\",\n gcpProject,\n \"--region\",\n region,\n \"--format=json\",\n ],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n const service = JSON.parse(stdout) as {\n status?: { url?: string };\n };\n\n const url = service.status?.url;\n\n if (!url) {\n return failure(\n createCloudRunError(\n CLOUD_RUN_ERRORS.DEPLOY_FAILED,\n \"Service deployed but no URL found in service description\",\n ),\n );\n }\n\n return success(url);\n } catch (err) {\n const details =\n err instanceof Error ? err.message : \"Unknown error describing service\";\n return failure(\n createCloudRunError(CLOUD_RUN_ERRORS.DEPLOY_FAILED, details),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Deployment\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Deploy to Cloud Run using `gcloud run deploy --source`\n *\n * This builds the container from source and deploys it in a single step.\n * Progress is reported through optional callbacks.\n */\nexport async function deployToCloudRun(\n config: Readonly<CloudRunConfig>,\n callbacks?: Readonly<CloudRunDeployCallbacks>,\n): Promise<Result<CloudRunResult, CloudRunError>> {\n // Validate prerequisites\n callbacks?.onValidating?.();\n\n const installCheck = await validateGcloudInstalled();\n if (!installCheck.success) {\n return failure(installCheck.error);\n }\n\n const authCheck = await validateGcloudAuth();\n if (!authCheck.success) {\n return failure(authCheck.error);\n }\n\n // Build the deploy command arguments\n const args = [\n \"run\",\n \"deploy\",\n config.serviceName,\n \"--source\",\n config.sourceDir,\n \"--project\",\n config.gcpProject,\n \"--region\",\n config.region,\n ...(config.requireAuth ? [] : [\"--allow-unauthenticated\"]),\n \"--quiet\",\n \"--format=json\",\n ];\n\n // Append env vars if any are specified\n const envVarsString = buildEnvVarsString(config.envVars);\n if (envVarsString) {\n args.push(\"--set-env-vars\", envVarsString);\n }\n\n // Execute deployment\n callbacks?.onDeploying?.();\n\n try {\n const { stdout } = await execa(\"gcloud\", args, {\n stdio: \"pipe\",\n timeout: 300_000,\n });\n\n // Attempt to parse JSON output from the deploy command\n let url: string | undefined;\n\n try {\n const deployOutput = JSON.parse(stdout) as {\n status?: { url?: string };\n };\n url = deployOutput.status?.url;\n } catch {\n // JSON parse failed — older gcloud versions may not return JSON.\n // Fall through to the fallback below.\n }\n\n // If we couldn't extract the URL from deploy output, describe the service\n if (!url) {\n const fallbackResult = await getServiceUrl(\n config.serviceName,\n config.gcpProject,\n config.region,\n );\n\n if (!fallbackResult.success) {\n return failure(fallbackResult.error);\n }\n\n url = fallbackResult.value;\n }\n\n const result: CloudRunResult = {\n url,\n serviceName: config.serviceName,\n region: config.region,\n gcpProject: config.gcpProject,\n };\n\n callbacks?.onDeployComplete?.(url);\n\n return success(result);\n } catch (err) {\n const execaError = err as { stderr?: string; message?: string };\n let details = execaError.stderr ?? execaError.message ?? String(err);\n\n // Best-effort: append recent Cloud Build logs for easier debugging\n const buildLogs = await fetchRecentBuildLogs(\n config.gcpProject,\n config.region,\n );\n if (buildLogs) {\n details +=\n \"\\n\\n── Recent Cloud Build logs ─────────────────────\\n\" + buildLogs;\n }\n\n return failure(\n createCloudRunError(CLOUD_RUN_ERRORS.DEPLOY_FAILED, details),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Service deletion\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Delete a Cloud Run service using `gcloud run services delete`.\n */\nexport async function deleteCloudRunService(config: {\n readonly serviceName: string;\n readonly gcpProject: string;\n readonly region: string;\n}): Promise<Result<void, CloudRunError>> {\n try {\n await execa(\n \"gcloud\",\n [\n \"run\",\n \"services\",\n \"delete\",\n config.serviceName,\n \"--region\",\n config.region,\n \"--project\",\n config.gcpProject,\n \"--quiet\",\n ],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n return success(undefined);\n } catch (err) {\n const execaError = err as { stderr?: string; message?: string };\n return failure(\n createCloudRunError(\n CLOUD_RUN_ERRORS.SERVICE_DELETION_FAILED,\n execaError.stderr ?? execaError.message ?? String(err),\n ),\n );\n }\n}\n","/**\n * Fluid API validation utilities\n *\n * This module provides functions for validating Fluid API keys\n * before deploying or destroying infrastructure. Ensures the deployer\n * has a valid company token before touching Cloud Run or Turso resources.\n */\n\nimport { type Result, success, failure } from \"./result.js\";\nimport prompts from \"prompts\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst FLUID_API_BASE = \"https://api.fluid.app\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error constants - structured error definitions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const FLUID_API_ERROR = {\n MISSING_API_KEY: {\n code: \"MISSING_API_KEY\",\n message: \"FLUID_COMPANY_API_KEY is not set\",\n },\n INVALID_API_KEY: {\n code: \"INVALID_API_KEY\",\n message: \"FLUID_COMPANY_API_KEY is invalid or expired\",\n },\n API_UNREACHABLE: {\n code: \"API_UNREACHABLE\",\n message: \"Could not reach the Fluid API\",\n },\n} as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Fluid API error codes derived from error constants\n */\nexport type FluidApiErrorCode =\n (typeof FLUID_API_ERROR)[keyof typeof FLUID_API_ERROR][\"code\"];\n\n/**\n * Structured Fluid API error with code for pattern matching\n */\nexport interface FluidApiError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\n/**\n * Create a Fluid API error from a constant and optional details\n */\nfunction createFluidApiError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): FluidApiError {\n return { code: template.code, message: template.message, details };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validated Fluid company info returned on successful API key check\n */\nexport interface FluidCompany {\n readonly name: string;\n readonly apiKey: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API key resolution\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve and validate the Fluid API key.\n *\n * Priority:\n * 1. `apiKeyOverride` parameter (from --fluid-company-api-key flag)\n * 2. FLUID_COMPANY_API_KEY environment variable\n * 3. Interactive hidden-input prompt\n * 4. Fail with instructions if all sources exhausted\n *\n * Once resolved, validates against the Fluid API (GET /api/company/v1/companies/me).\n *\n * @param apiKeyOverride - Optional API key from CLI flag (skips env + prompt)\n */\nexport async function resolveFluidApiKey(\n apiKeyOverride?: string,\n): Promise<Result<FluidCompany, FluidApiError>> {\n // ── Source 1: CLI flag ──────────────────────────────────────────────────\n if (apiKeyOverride) {\n return validateFluidApiKey(apiKeyOverride);\n }\n\n // ── Source 2: Environment variable ──────────────────────────────────────\n const envKey = process.env.FLUID_COMPANY_API_KEY;\n if (envKey) {\n return validateFluidApiKey(envKey);\n }\n\n // ── Source 3: Interactive prompt ────────────────────────────────────────\n const { apiKey } = await prompts({\n type: \"password\",\n name: \"apiKey\",\n message: \"Enter your Fluid company API key (FLUID_COMPANY_API_KEY)\",\n });\n\n if (!apiKey) {\n return failure(\n createFluidApiError(\n FLUID_API_ERROR.MISSING_API_KEY,\n \"Set FLUID_COMPANY_API_KEY in your .env file or pass --fluid-company-api-key <key>.\",\n ),\n );\n }\n\n return validateFluidApiKey(apiKey);\n}\n\n/**\n * Validate a Fluid API key by calling the companies/me endpoint.\n *\n * - 200 → extract company name, return success\n * - 401/403 → invalid or expired key\n * - Network error → API unreachable\n */\nexport async function validateFluidApiKey(\n apiKey: string,\n): Promise<Result<FluidCompany, FluidApiError>> {\n try {\n const response = await fetch(\n `${FLUID_API_BASE}/api/company/v1/companies/me`,\n {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n data: { company: { name: string } };\n };\n return success({ name: data.data.company.name, apiKey });\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createFluidApiError(\n FLUID_API_ERROR.INVALID_API_KEY,\n `HTTP ${response.status}: Check that your FLUID_COMPANY_API_KEY is a valid, non-expired company token.`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createFluidApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createFluidApiError(FLUID_API_ERROR.API_UNREACHABLE, message),\n );\n }\n}\n","import fs from \"fs-extra\";\nimport path from \"path\";\n\n/**\n * Read project name from package.json\n */\nexport async function getProjectName(cwd: string): Promise<string | undefined> {\n const packageJsonPath = path.join(cwd, \"package.json\");\n if (await fs.pathExists(packageJsonPath)) {\n const packageJson = await fs.readJson(packageJsonPath);\n return packageJson.name;\n }\n return undefined;\n}\n\n/**\n * Sanitize a project name into a valid Cloud Run service name.\n * Lowercase, alphanumeric, and hyphens only.\n */\nexport function sanitizeServiceName(projectName: string): string {\n return projectName\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/--+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n","/**\n * Navigation extraction utility\n *\n * Extracts the `navigation` export from a project's navigation.config.ts\n * by writing a minimal wrapper script that imports from navigation.config.ts\n * and serializes the result to stdout, then running it with tsx.\n *\n * This avoids transitive dependency resolution — navigation.config.ts must\n * be self-contained static data with no import statements.\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { type Result, success, failure } from \"./result.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** A code-defined navigation item from navigation.config.ts */\nexport interface CodeNavigationItem {\n readonly label: string;\n readonly slug?: string;\n readonly icon?: string;\n readonly children?: CodeNavigationItem[];\n}\n\nexport interface NavigationExtractionError {\n readonly code: \"EXTRACTION_FAILED\" | \"INVALID_FORMAT\";\n readonly message: string;\n readonly details?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Extraction\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst EXTRACT_FILENAME = \"__fluid_extract_nav.ts\";\n\n/**\n * Extract the `navigation` export from a project's navigation.config.ts.\n *\n * Reads the config file, validates it contains no import statements,\n * writes a minimal wrapper that imports and serializes navigation,\n * and runs it with tsx. This avoids needing project dependencies\n * (React, etc.) to be installed.\n *\n * The temp file is always cleaned up.\n * Returns null if no navigation export exists.\n *\n * @param projectDir - The project root directory containing src/navigation.config.ts\n */\nexport async function extractNavigation(\n projectDir: string,\n): Promise<Result<CodeNavigationItem[] | null, NavigationExtractionError>> {\n const configPath = path.join(projectDir, \"src\", \"navigation.config.ts\");\n const extractFile = path.join(projectDir, EXTRACT_FILENAME);\n\n try {\n // Read the original config\n const configSource = await fs.readFile(configPath, \"utf-8\");\n\n // Check if navigation export exists before spawning a process\n if (!/export\\s+(const|let)\\s+navigation\\s*=/.test(configSource)) {\n return success(null);\n }\n\n // Guard: navigation.config.ts must not contain import statements\n if (/^\\s*import\\s/m.test(configSource)) {\n return failure({\n code: \"EXTRACTION_FAILED\",\n message:\n \"navigation.config.ts must not contain import statements — it should only export static data\",\n details:\n \"Move all imports to portal.config.ts. navigation.config.ts must be self-contained.\",\n });\n }\n\n // Write a minimal wrapper that imports and serializes the navigation\n const wrapperScript = [\n `import { navigation } from \"./src/navigation.config.ts\";`,\n `console.log(JSON.stringify(navigation ?? null));`,\n ].join(\"\\n\");\n await fs.writeFile(extractFile, wrapperScript, \"utf-8\");\n\n const result = await execa(\"npx\", [\"tsx\", EXTRACT_FILENAME], {\n cwd: projectDir,\n stdio: \"pipe\",\n env: {\n ...process.env,\n NODE_ENV: \"production\",\n },\n });\n\n const output = result.stdout.trim();\n\n if (!output || output === \"null\") {\n return success(null);\n }\n\n try {\n const parsed = JSON.parse(output) as unknown;\n if (!Array.isArray(parsed)) {\n return failure({\n code: \"INVALID_FORMAT\",\n message: \"navigation export is not an array\",\n details: `Expected an array, got: ${typeof parsed}`,\n });\n }\n return success(parsed as CodeNavigationItem[]);\n } catch {\n return failure({\n code: \"INVALID_FORMAT\",\n message: \"Failed to parse navigation output as JSON\",\n details: output.slice(0, 200),\n });\n }\n } catch (err) {\n const error = err as { stderr?: string; message?: string };\n return failure({\n code: \"EXTRACTION_FAILED\",\n message: \"Failed to extract navigation from navigation.config.ts\",\n details: error.stderr ?? error.message ?? String(err),\n });\n } finally {\n // Always clean up the temp file\n await fs.remove(extractFile).catch(() => {});\n }\n}\n","/**\n * Navigation sync utility\n *\n * Reconciles code-defined navigation items from portal.config.ts\n * against the Fluid OS API. Only manages items with source: \"code\",\n * leaving user-created and system items untouched.\n */\n\nimport { type Result, success, failure } from \"./result.js\";\nimport type { CodeNavigationItem } from \"./extract-navigation.js\";\nimport type { components } from \"@fluid-app/fluidos-api-client\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ApiNavigationItem = components[\"schemas\"][\"FluidOSNavigationItem\"];\n\nexport interface NavigationSyncError {\n readonly code:\n | \"NO_DEFINITION\"\n | \"NO_NAVIGATION\"\n | \"API_ERROR\"\n | \"SYNC_FAILED\";\n readonly message: string;\n readonly details?: string;\n}\n\nexport interface NavigationSyncResult {\n readonly created: number;\n readonly updated: number;\n readonly deleted: number;\n}\n\ninterface ApiContext {\n readonly apiBase: string;\n readonly apiKey: string;\n readonly definitionId: number;\n readonly navigationId: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API helpers (raw fetch, following fluid-api.ts pattern)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst FLUID_API_BASE = \"https://api.fluid.app\";\n\nasync function apiGet<T>(apiKey: string, path: string): Promise<T> {\n const response = await fetch(`${FLUID_API_BASE}${path}`, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`GET ${path} failed (${response.status}): ${body}`);\n }\n return response.json() as Promise<T>;\n}\n\nasync function apiPost<T>(\n apiKey: string,\n path: string,\n body: unknown,\n): Promise<T> {\n const response = await fetch(`${FLUID_API_BASE}${path}`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`POST ${path} failed (${response.status}): ${text}`);\n }\n return response.json() as Promise<T>;\n}\n\nasync function apiPut<T>(\n apiKey: string,\n path: string,\n body: unknown,\n): Promise<T> {\n const response = await fetch(`${FLUID_API_BASE}${path}`, {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`PUT ${path} failed (${response.status}): ${text}`);\n }\n return response.json() as Promise<T>;\n}\n\nasync function apiDelete(apiKey: string, path: string): Promise<void> {\n const response = await fetch(`${FLUID_API_BASE}${path}`, {\n method: \"DELETE\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`DELETE ${path} failed (${response.status}): ${text}`);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Discovery: find active definition and web navigation\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface DefinitionListResponse {\n definitions: Array<{ id: number; active: boolean }>;\n}\n\ninterface NavigationListResponse {\n navigations: Array<{ id: number; name: string }>;\n}\n\ninterface NavigationItemsResponse {\n navigation_items: ApiNavigationItem[];\n}\n\nasync function discoverContext(\n apiKey: string,\n): Promise<Result<Omit<ApiContext, \"apiBase\">, NavigationSyncError>> {\n // Find active definition\n let definitionId: number;\n try {\n const defs = await apiGet<DefinitionListResponse>(\n apiKey,\n \"/api/company/fluid_os/definitions\",\n );\n const active = defs.definitions.find((d) => d.active);\n if (!active) {\n return failure({\n code: \"NO_DEFINITION\",\n message: \"No active Fluid OS definition found\",\n details:\n \"Create and activate a definition in the Fluid admin dashboard first.\",\n });\n }\n definitionId = active.id;\n } catch (err) {\n return failure({\n code: \"API_ERROR\",\n message: \"Failed to fetch Fluid OS definitions\",\n details: err instanceof Error ? err.message : String(err),\n });\n }\n\n // Find web navigation\n let navigationId: number;\n try {\n const navs = await apiGet<NavigationListResponse>(\n apiKey,\n `/api/company/fluid_os/definitions/${definitionId}/navigations`,\n );\n // Prefer \"Web Navigation\" or first available\n const webNav =\n navs.navigations.find((n) => n.name.toLowerCase().includes(\"web\")) ??\n navs.navigations[0];\n if (!webNav) {\n return failure({\n code: \"NO_NAVIGATION\",\n message: \"No navigation found for the active definition\",\n details:\n \"The active definition has no navigations. Create one in the Fluid admin dashboard.\",\n });\n }\n navigationId = webNav.id;\n } catch (err) {\n return failure({\n code: \"API_ERROR\",\n message: \"Failed to fetch navigations\",\n details: err instanceof Error ? err.message : String(err),\n });\n }\n\n return success({ apiKey, definitionId, navigationId });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sync algorithm\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build a lookup key for matching code items to API items.\n * Items with a slug are keyed by slug; headers (no slug) are keyed by \"header:{label}\".\n */\nfunction itemKey(item: {\n slug?: string | null;\n label?: string | null;\n}): string {\n if (item.slug) return item.slug;\n return `header:${item.label ?? \"\"}`;\n}\n\n/**\n * Recursively delete an API navigation item and all its source: \"code\" descendants\n * in post-order (deepest children first, then the item itself).\n */\nasync function deleteItemRecursive(\n apiKey: string,\n basePath: string,\n item: ApiNavigationItem,\n stats: { deleted: number },\n): Promise<void> {\n const codeChildren = (item.children ?? []).filter((c) => c.source === \"code\");\n for (const child of codeChildren) {\n await deleteItemRecursive(apiKey, basePath, child, stats);\n }\n await apiDelete(apiKey, `${basePath}/${item.id}`);\n stats.deleted++;\n}\n\n/**\n * Sync a level of code-defined navigation items against existing API items.\n * Recurses for children.\n */\nasync function syncLevel(\n ctx: ApiContext,\n codeItems: CodeNavigationItem[],\n existingItems: ApiNavigationItem[],\n parentId: number | null,\n stats: { created: number; updated: number; deleted: number },\n): Promise<void> {\n // Build a map of existing code-sourced items by key\n const existingByKey = new Map<string, ApiNavigationItem>();\n for (const item of existingItems) {\n if (item.source === \"code\") {\n existingByKey.set(itemKey(item), item);\n }\n }\n\n const basePath = `/api/company/fluid_os/definitions/${ctx.definitionId}/navigations/${ctx.navigationId}/navigation_items`;\n const matched = new Set<string>();\n\n // Walk code-defined items\n for (let i = 0; i < codeItems.length; i++) {\n const codeItem = codeItems[i]!;\n const key = itemKey(codeItem);\n const existing = existingByKey.get(key);\n matched.add(key);\n\n if (existing) {\n // UPDATE: check if anything changed\n const needsUpdate =\n existing.label !== codeItem.label ||\n existing.icon !== (codeItem.icon ?? null) ||\n existing.position !== i + 1;\n\n if (needsUpdate) {\n await apiPut(ctx.apiKey, `${basePath}/${existing.id}`, {\n navigation_item: {\n label: codeItem.label,\n icon: codeItem.icon ?? null,\n position: i + 1,\n },\n });\n stats.updated++;\n }\n\n // Recurse for children\n if (codeItem.children?.length) {\n await syncLevel(\n ctx,\n codeItem.children,\n existing.children ?? [],\n existing.id,\n stats,\n );\n }\n } else {\n // CREATE\n const response = await apiPost<{\n navigation_item: ApiNavigationItem;\n }>(ctx.apiKey, basePath, {\n navigation_item: {\n label: codeItem.label,\n slug: codeItem.slug ?? null,\n icon: codeItem.icon ?? null,\n position: i + 1,\n parent_id: parentId,\n source: \"code\",\n },\n });\n stats.created++;\n\n // Recurse for children of newly created item\n if (codeItem.children?.length) {\n const newId = response.navigation_item.id;\n await syncLevel(ctx, codeItem.children, [], newId, stats);\n }\n }\n }\n\n // DELETE: remaining unmatched source: \"code\" items (post-order: children before parent)\n for (const [key, item] of existingByKey) {\n if (!matched.has(key)) {\n await deleteItemRecursive(ctx.apiKey, basePath, item, stats);\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public API\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Sync code-defined navigation items to the Fluid OS API.\n *\n * @param apiKey - Valid Fluid company API key\n * @param codeItems - Navigation items from portal.config.ts\n */\nexport async function syncNavigation(\n apiKey: string,\n codeItems: CodeNavigationItem[],\n): Promise<Result<NavigationSyncResult, NavigationSyncError>> {\n // Discover active definition and web navigation\n const contextResult = await discoverContext(apiKey);\n if (!contextResult.success) {\n return contextResult;\n }\n\n const ctx: ApiContext = {\n apiBase: FLUID_API_BASE,\n ...contextResult.value,\n };\n\n // Fetch existing navigation items\n let existingItems: ApiNavigationItem[];\n try {\n const response = await apiGet<NavigationItemsResponse>(\n ctx.apiKey,\n `/api/company/fluid_os/definitions/${ctx.definitionId}/navigations/${ctx.navigationId}/navigation_items`,\n );\n existingItems = response.navigation_items;\n } catch (err) {\n return failure({\n code: \"API_ERROR\",\n message: \"Failed to fetch existing navigation items\",\n details: err instanceof Error ? err.message : String(err),\n });\n }\n\n // Run the sync\n const stats = { created: 0, updated: 0, deleted: 0 };\n try {\n await syncLevel(ctx, codeItems, existingItems, null, stats);\n } catch (err) {\n return failure({\n code: \"SYNC_FAILED\",\n message: \"Navigation sync failed during reconciliation\",\n details: err instanceof Error ? err.message : String(err),\n });\n }\n\n return success(stats);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { execa } from \"execa\";\nimport { config as loadDotenv } from \"dotenv\";\nimport type { DeployOptions } from \"../types.js\";\nimport { resolveTursoConfig, provisionDatabase } from \"../utils/turso.js\";\nimport {\n validateGcloudInstalled,\n validateGcloudAuth,\n getGcpProject,\n deployToCloudRun,\n} from \"../utils/cloud-run.js\";\nimport { getErrorMessage } from \"../utils/result.js\";\nimport { resolveFluidApiKey } from \"../utils/fluid-api.js\";\nimport { getRunCommand } from \"../utils/package-manager.js\";\nimport { getProjectName, sanitizeServiceName } from \"../utils/project.js\";\nimport { extractNavigation } from \"../utils/extract-navigation.js\";\nimport { syncNavigation } from \"../utils/navigation-sync.js\";\n\n/**\n * Detect if the project is a fullstack template (has server entry)\n */\nasync function isFullstackProject(cwd: string): Promise<boolean> {\n return fs.pathExists(path.join(cwd, \"src\", \"server\", \"index.ts\"));\n}\n\nexport const deployCommand: Command = new Command(\"deploy\")\n .description(\"Deploy the fullstack application to Cloud Run + Turso\")\n .option(\"--region <region>\", \"Cloud Run region\", \"us-central1\")\n .option(\"--gcp-project <id>\", \"GCP project ID (default: from gcloud config)\")\n .option(\n \"-p, --project <name>\",\n \"Service name override (default: from package.json)\",\n )\n .option(\n \"--db-region <location>\",\n \"Turso database group location\",\n \"aws-us-east-1\",\n )\n .option(\n \"--require-auth\",\n \"Require IAM authentication for the Cloud Run service (default: public)\",\n )\n .option(\n \"--migrate\",\n \"Run database migrations (db:push) after successful deploy\",\n )\n .option(\n \"--skip-local-build\",\n \"Skip the local Docker build check before deploying\",\n )\n .option(\n \"--turso-org <slug>\",\n \"Turso organization slug (skips interactive org selection)\",\n )\n .option(\n \"--fluid-company-api-key <key>\",\n \"Fluid company API key (skips env var lookup and prompt)\",\n )\n .option(\"--skip-nav-sync\", \"Skip navigation sync from portal.config.ts\")\n .action(async (options: DeployOptions) => {\n const cwd = process.cwd();\n\n // Load .env from the project directory (does not override existing env vars)\n loadDotenv({ path: path.join(cwd, \".env\") });\n\n console.log();\n console.log(\n chalk.blue.bold(\"Fluid Deploy\") + chalk.gray(\" (Cloud Run + Turso)\"),\n );\n console.log();\n\n // ── Check project type ──────────────────────────────────────────────\n if (!(await isFullstackProject(cwd))) {\n console.log(\n chalk.red(\"Error:\") + \" This project is not a fullstack template.\",\n );\n console.log();\n console.log(\n chalk.yellow(\"fluid deploy\") +\n \" only supports fullstack projects with a Hono API server.\",\n );\n console.log();\n console.log(\n \"For static sites (starter template), deploy the \" +\n chalk.cyan(\"dist/\") +\n \" folder to:\",\n );\n console.log(\n \" - Firebase Hosting: \" +\n chalk.gray(\"https://firebase.google.com/docs/hosting\"),\n );\n console.log(\n \" - Cloud Storage: \" +\n chalk.gray(\n \"https://cloud.google.com/storage/docs/hosting-static-website\",\n ),\n );\n console.log(\" - Vercel: \" + chalk.gray(\"https://vercel.com\"));\n console.log(\" - Netlify: \" + chalk.gray(\"https://netlify.com\"));\n console.log();\n process.exit(1);\n }\n\n // ── Validate Fluid API key ────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Validating Fluid API key...\");\n const fluidResult = await resolveFluidApiKey(options.fluidCompanyApiKey);\n if (!fluidResult.success) {\n spinner.fail(\"Fluid API key validation failed\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + fluidResult.error.message);\n if (fluidResult.error.details) {\n console.log();\n console.log(fluidResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n spinner.succeed(`Fluid company: ${chalk.cyan(fluidResult.value.name)}`);\n\n // ── Check for Dockerfile ────────────────────────────────────────────\n if (!(await fs.pathExists(path.join(cwd, \"Dockerfile\")))) {\n console.log(\n chalk.red(\"Error:\") + \" No Dockerfile found in current directory.\",\n );\n console.log();\n console.log(\n \"Fullstack projects created with the latest template include a Dockerfile.\",\n );\n console.log(\n \"If you upgraded from an older template, add a Dockerfile to your project.\",\n );\n console.log();\n process.exit(1);\n }\n\n // ── Validate gcloud CLI ─────────────────────────────────────────────\n spinner.start(\"Checking gcloud CLI...\");\n const gcloudResult = await validateGcloudInstalled();\n if (!gcloudResult.success) {\n spinner.fail(\"gcloud CLI not found\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + gcloudResult.error.message);\n console.log();\n console.log(\n \"Install the Google Cloud SDK: \" +\n chalk.cyan(\"https://cloud.google.com/sdk/docs/install\"),\n );\n console.log();\n process.exit(1);\n }\n\n const authResult = await validateGcloudAuth();\n if (!authResult.success) {\n spinner.fail(\"gcloud not authenticated\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + authResult.error.message);\n console.log();\n console.log(\n \"Run \" + chalk.cyan(\"gcloud auth login\") + \" to authenticate.\",\n );\n console.log();\n process.exit(1);\n }\n spinner.succeed(\"gcloud CLI ready\");\n\n // ── Resolve GCP project ─────────────────────────────────────────────\n let gcpProject = options.gcpProject;\n if (!gcpProject) {\n spinner.start(\"Detecting GCP project...\");\n const projectResult = await getGcpProject();\n if (!projectResult.success) {\n spinner.fail(\"No GCP project configured\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + projectResult.error.message);\n console.log();\n console.log(\"Either:\");\n console.log(\n \" - Run \" + chalk.cyan(\"gcloud config set project PROJECT_ID\"),\n );\n console.log(\n \" - Use the \" + chalk.cyan(\"--gcp-project <id>\") + \" flag\",\n );\n console.log();\n process.exit(1);\n }\n gcpProject = projectResult.value;\n spinner.succeed(`GCP project: ${chalk.cyan(gcpProject)}`);\n }\n\n // ── Resolve Turso credentials ──────────────────────────────────────\n spinner.start(\"Resolving Turso credentials...\");\n const tursoConfigResult = await resolveTursoConfig(options.tursoOrg);\n if (!tursoConfigResult.success) {\n spinner.fail(\"Turso credentials not found\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + tursoConfigResult.error.message);\n if (tursoConfigResult.error.details) {\n console.log();\n console.log(tursoConfigResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n const tursoConfig = tursoConfigResult.value;\n const sourceLabel =\n tursoConfig.source === \"env\" ? \"environment variables\" : \"Turso CLI\";\n spinner.succeed(`Turso: authenticated via ${sourceLabel}`);\n\n // ── Get project/service name ────────────────────────────────────────\n const projectName = options.project ?? (await getProjectName(cwd));\n if (!projectName) {\n console.log(chalk.red(\"Error:\") + \" Could not determine project name.\");\n console.log();\n console.log(\"Either:\");\n console.log(\" - Add a \" + chalk.cyan(\"name\") + \" field to package.json\");\n console.log(\" - Use the \" + chalk.cyan(\"--project <name>\") + \" flag\");\n console.log();\n process.exit(1);\n }\n\n const serviceName = sanitizeServiceName(projectName);\n if (!serviceName) {\n console.log(\n chalk.red(\"Error:\") +\n \" Project name sanitizes to an empty service name.\",\n );\n console.log();\n console.log(\n \"Use the \" +\n chalk.cyan(\"--project <name>\") +\n \" flag to provide a valid service name.\",\n );\n console.log();\n process.exit(1);\n }\n\n const region = options.region ?? \"us-central1\";\n\n console.log();\n console.log(chalk.gray(\"Service: \") + chalk.white(serviceName));\n console.log(chalk.gray(\"Region: \") + chalk.white(region));\n console.log(chalk.gray(\"GCP Project: \") + chalk.white(gcpProject));\n console.log(\n chalk.gray(\"Turso Org: \") +\n chalk.white(tursoConfig.org) +\n chalk.gray(` (via ${sourceLabel})`),\n );\n console.log();\n\n // ── Local Docker build check ────────────────────────────────────────\n if (!options.skipLocalBuild) {\n let dockerAvailable = false;\n try {\n await execa(\"docker\", [\"--version\"], { stdio: \"pipe\" });\n dockerAvailable = true;\n } catch {\n spinner.warn(\"Docker not found — skipping local build check\");\n }\n\n if (dockerAvailable) {\n spinner.start(\"Running local Docker build check...\");\n try {\n await execa(\"docker\", [\"build\", \"-t\", `${serviceName}-check`, \".\"], {\n cwd,\n stdio: \"pipe\",\n });\n spinner.succeed(\"Local Docker build passed\");\n } catch (buildErr) {\n spinner.fail(\"Local Docker build failed\");\n const buildError = buildErr as { stderr?: string };\n if (buildError.stderr) {\n console.log();\n console.log(chalk.gray(buildError.stderr));\n }\n console.log();\n console.log(\n chalk.red(\"Fix the Dockerfile errors above before deploying.\"),\n );\n console.log(\n chalk.gray(\"Tip: Use --skip-local-build to bypass this check.\"),\n );\n console.log();\n process.exit(1);\n }\n console.log();\n }\n }\n\n // ── Provision Turso database ────────────────────────────────────────\n try {\n const dbResult = await provisionDatabase(\n tursoConfig,\n serviceName,\n options.dbRegion,\n {\n onGroupCreating: () => {\n spinner.start(\"Ensuring Turso database group...\");\n },\n onGroupReady: () => {\n spinner.succeed(\"Database group ready\");\n },\n onDatabaseCreating: (name) => {\n spinner.start(`Creating database \"${name}\"...`);\n },\n onDatabaseReady: (name) => {\n spinner.succeed(`Database \"${name}\" ready`);\n },\n onTokenCreating: () => {\n spinner.start(\"Creating database auth token...\");\n },\n onTokenReady: () => {\n spinner.succeed(\"Auth token created\");\n },\n },\n );\n\n if (!dbResult.success) {\n spinner.fail(\"Turso provisioning failed\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + dbResult.error.message);\n if (dbResult.error.details) {\n console.log(chalk.gray(\"Details: \") + dbResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n\n const database = dbResult.value;\n console.log();\n console.log(chalk.gray(\"Database URL: \") + chalk.cyan(database.url));\n console.log();\n\n // ── Deploy to Cloud Run ───────────────────────────────────────────\n const deployResult = await deployToCloudRun(\n {\n gcpProject,\n region,\n serviceName,\n sourceDir: cwd,\n requireAuth: options.requireAuth,\n envVars: {\n DATABASE_URL: database.url,\n DATABASE_AUTH_TOKEN: database.authToken,\n NODE_ENV: \"production\",\n FLUID_COMPANY_API_KEY: fluidResult.value.apiKey,\n },\n },\n {\n onValidating: () => {\n spinner.start(\"Preparing Cloud Run deployment...\");\n },\n onDeploying: () => {\n spinner.text =\n \"Deploying to Cloud Run (this may take 2-5 minutes)...\";\n },\n onDeployComplete: () => {\n spinner.succeed(\"Deployed to Cloud Run\");\n },\n },\n );\n\n if (!deployResult.success) {\n spinner.fail(\"Cloud Run deployment failed\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + deployResult.error.message);\n if (deployResult.error.details) {\n console.log(chalk.gray(\"Details: \") + deployResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.green.bold(\"Deployed successfully!\"));\n console.log();\n console.log(\n chalk.gray(\"Service URL: \") + chalk.cyan(deployResult.value.url),\n );\n console.log(\n chalk.gray(\"Database: \") + chalk.cyan(database.databaseName),\n );\n console.log(\n chalk.gray(\"Region: \") + chalk.cyan(deployResult.value.region),\n );\n console.log(\n chalk.gray(\"GCP Project: \") +\n chalk.cyan(deployResult.value.gcpProject),\n );\n console.log();\n\n // ── Navigation sync ─────────────────────────────────────────────\n if (!options.skipNavSync) {\n const configPath = path.join(cwd, \"src\", \"navigation.config.ts\");\n if (await fs.pathExists(configPath)) {\n const navSpinner = ora(\n \"Extracting navigation from navigation.config.ts...\",\n ).start();\n const extractResult = await extractNavigation(cwd);\n\n if (extractResult.success && extractResult.value != null) {\n const navItems = extractResult.value;\n navSpinner.text = `Syncing ${navItems.length} navigation item(s)...`;\n\n const syncResult = await syncNavigation(\n fluidResult.value.apiKey,\n navItems,\n );\n\n if (syncResult.success) {\n const { created, updated, deleted } = syncResult.value;\n const parts: string[] = [];\n if (created > 0) parts.push(`${created} created`);\n if (updated > 0) parts.push(`${updated} updated`);\n if (deleted > 0) parts.push(`${deleted} deleted`);\n if (parts.length > 0) {\n navSpinner.succeed(`Navigation synced (${parts.join(\", \")})`);\n } else {\n navSpinner.succeed(\"Navigation up to date\");\n }\n } else {\n navSpinner.warn(\"Navigation sync failed (deploy succeeded)\");\n console.log(\n chalk.yellow(\" Warning: \") + syncResult.error.message,\n );\n if (syncResult.error.details) {\n console.log(\n chalk.gray(\" Details: \") + syncResult.error.details,\n );\n }\n }\n } else if (extractResult.success && extractResult.value == null) {\n navSpinner.info(\n \"No navigation export found in navigation.config.ts — skipping sync\",\n );\n } else {\n navSpinner.warn(\"Could not extract navigation (deploy succeeded)\");\n if (!extractResult.success && extractResult.error.details) {\n console.log(\n chalk.gray(\" Details: \") + extractResult.error.details,\n );\n }\n }\n }\n }\n\n // ── Health check ──────────────────────────────────────────────────\n const serviceUrl = deployResult.value.url;\n const healthSpinner = ora(\"Running health check...\").start();\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30000);\n const healthRes = await fetch(`${serviceUrl}/api/health`, {\n signal: controller.signal,\n });\n clearTimeout(timeout);\n if (healthRes.ok) {\n healthSpinner.succeed(\"Health check passed\");\n } else {\n healthSpinner.warn(\n \"Health check returned non-200 (service may still be starting)\",\n );\n }\n } catch {\n healthSpinner.warn(\n \"Health check timed out (cold start may take a moment)\",\n );\n }\n\n // ── Migration ─────────────────────────────────────────────────────\n const shouldMigrate = options.migrate || database.isNew;\n if (shouldMigrate) {\n if (database.isNew && !options.migrate) {\n console.log();\n console.log(\n chalk.blue(\"New database\") +\n \" — running migrations automatically...\",\n );\n }\n const migrateCmd = getRunCommand(\"db:push\");\n const migrateSpinner = ora(\n `Running migrations (${migrateCmd})...`,\n ).start();\n try {\n const [pmBin, ...pmArgs] = migrateCmd.split(\" \");\n await execa(pmBin!, pmArgs, {\n cwd,\n stdio: \"pipe\",\n env: {\n ...process.env,\n DATABASE_URL: database.url,\n DATABASE_AUTH_TOKEN: database.authToken,\n },\n });\n migrateSpinner.succeed(\"Database migrations complete\");\n } catch (migrateErr) {\n const migrateError = migrateErr as {\n stderr?: string;\n message?: string;\n };\n migrateSpinner.warn(\"Database migration failed (deploy succeeded)\");\n console.log();\n console.log(\n chalk.yellow(\"Migration error:\") +\n \" \" +\n (migrateError.stderr ??\n migrateError.message ??\n String(migrateErr)),\n );\n console.log();\n console.log(\"Run migrations manually:\");\n console.log(\n ` DATABASE_URL=${database.url} DATABASE_AUTH_TOKEN=<token> ${migrateCmd}`,\n );\n }\n } else {\n console.log();\n console.log(\n chalk.yellow(\"Important:\") +\n \" Run database migrations against your production database:\",\n );\n console.log(\n ` DATABASE_URL=${database.url} DATABASE_AUTH_TOKEN=<token> pnpm db:push`,\n );\n console.log(\n chalk.gray(\"Tip: Use --migrate to run migrations automatically.\"),\n );\n }\n console.log();\n } catch (error) {\n spinner.fail(\"Deployment failed\");\n console.log();\n const errorMessage = getErrorMessage(error);\n console.log(chalk.red(\"Error:\") + \" \" + errorMessage);\n console.log();\n process.exit(1);\n }\n });\n\nexport function registerDeployCommand(ctx: PluginContext): void {\n ctx.program.addCommand(deployCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { config as loadDotenv } from \"dotenv\";\nimport path from \"path\";\nimport prompts from \"prompts\";\nimport type { DestroyOptions } from \"../types.js\";\nimport { resolveTursoConfig, deleteDatabase } from \"../utils/turso.js\";\nimport { resolveFluidApiKey } from \"../utils/fluid-api.js\";\nimport {\n validateGcloudInstalled,\n validateGcloudAuth,\n getGcpProject,\n deleteCloudRunService,\n} from \"../utils/cloud-run.js\";\nimport { getProjectName, sanitizeServiceName } from \"../utils/project.js\";\n\nexport const destroyCommand: Command = new Command(\"destroy\")\n .description(\"Tear down deployed Cloud Run service and Turso database\")\n .option(\"--region <region>\", \"Cloud Run region\", \"us-central1\")\n .option(\"--gcp-project <id>\", \"GCP project ID (default: from gcloud config)\")\n .option(\n \"-p, --project <name>\",\n \"Service name override (default: from package.json)\",\n )\n .option(\n \"--turso-org <slug>\",\n \"Turso organization slug (skips interactive org selection)\",\n )\n .option(\n \"--fluid-company-api-key <key>\",\n \"Fluid company API key (skips env var lookup and prompt)\",\n )\n .option(\"-y, --yes\", \"Skip confirmation prompt\")\n .action(async (options: DestroyOptions) => {\n const cwd = process.cwd();\n\n // Load .env from the project directory (does not override existing env vars)\n loadDotenv({ path: path.join(cwd, \".env\") });\n\n console.log();\n console.log(\n chalk.red.bold(\"Fluid Destroy\") + chalk.gray(\" (Cloud Run + Turso)\"),\n );\n console.log();\n\n // ── Validate Fluid API key ────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Validating Fluid API key...\");\n const fluidResult = await resolveFluidApiKey(options.fluidCompanyApiKey);\n if (!fluidResult.success) {\n spinner.fail(\"Fluid API key validation failed\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + fluidResult.error.message);\n if (fluidResult.error.details) {\n console.log();\n console.log(fluidResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n spinner.succeed(`Fluid company: ${chalk.cyan(fluidResult.value.name)}`);\n\n spinner.start(\"Checking gcloud CLI...\");\n const gcloudResult = await validateGcloudInstalled();\n if (!gcloudResult.success) {\n spinner.fail(\"gcloud CLI not found\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + gcloudResult.error.message);\n console.log();\n process.exit(1);\n }\n\n const authResult = await validateGcloudAuth();\n if (!authResult.success) {\n spinner.fail(\"gcloud not authenticated\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + authResult.error.message);\n console.log();\n process.exit(1);\n }\n spinner.succeed(\"gcloud CLI ready\");\n\n // ── Resolve GCP project ─────────────────────────────────────────────\n let gcpProject = options.gcpProject;\n if (!gcpProject) {\n spinner.start(\"Detecting GCP project...\");\n const projectResult = await getGcpProject();\n if (!projectResult.success) {\n spinner.fail(\"No GCP project configured\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + projectResult.error.message);\n console.log();\n process.exit(1);\n }\n gcpProject = projectResult.value;\n spinner.succeed(`GCP project: ${chalk.cyan(gcpProject)}`);\n }\n\n // ── Resolve Turso credentials ──────────────────────────────────────\n spinner.start(\"Resolving Turso credentials...\");\n const tursoConfigResult = await resolveTursoConfig(options.tursoOrg);\n if (!tursoConfigResult.success) {\n spinner.fail(\"Turso credentials not found\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + tursoConfigResult.error.message);\n if (tursoConfigResult.error.details) {\n console.log();\n console.log(tursoConfigResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n const tursoConfig = tursoConfigResult.value;\n spinner.succeed(\"Turso credentials resolved\");\n\n // ── Get project/service name ────────────────────────────────────────\n const projectName = options.project ?? (await getProjectName(cwd));\n if (!projectName) {\n console.log(chalk.red(\"Error:\") + \" Could not determine project name.\");\n console.log();\n console.log(\"Either:\");\n console.log(\" - Add a \" + chalk.cyan(\"name\") + \" field to package.json\");\n console.log(\" - Use the \" + chalk.cyan(\"--project <name>\") + \" flag\");\n console.log();\n process.exit(1);\n }\n\n const serviceName = sanitizeServiceName(projectName);\n if (!serviceName) {\n console.log(\n chalk.red(\"Error:\") +\n \" Project name sanitizes to an empty service name.\",\n );\n console.log();\n console.log(\n \"Use the \" +\n chalk.cyan(\"--project <name>\") +\n \" flag to provide a valid service name.\",\n );\n console.log();\n process.exit(1);\n }\n\n const region = options.region ?? \"us-central1\";\n\n // ── Confirmation ────────────────────────────────────────────────────\n console.log();\n console.log(chalk.yellow(\"The following resources will be destroyed:\"));\n console.log();\n console.log(chalk.gray(\" Cloud Run service: \") + chalk.white(serviceName));\n console.log(chalk.gray(\" Region: \") + chalk.white(region));\n console.log(chalk.gray(\" GCP Project: \") + chalk.white(gcpProject));\n console.log(chalk.gray(\" Turso database: \") + chalk.white(serviceName));\n console.log(\n chalk.gray(\" Turso org: \") + chalk.white(tursoConfig.org),\n );\n console.log();\n\n if (!options.yes) {\n const { confirmed } = await prompts({\n type: \"confirm\",\n name: \"confirmed\",\n message: \"Are you sure you want to destroy these resources?\",\n initial: false,\n });\n\n if (!confirmed) {\n console.log();\n console.log(chalk.gray(\"Destroy cancelled.\"));\n console.log();\n return;\n }\n }\n\n // ── Delete Cloud Run service ────────────────────────────────────────\n console.log();\n spinner.start(`Deleting Cloud Run service \"${serviceName}\"...`);\n const deleteServiceResult = await deleteCloudRunService({\n serviceName,\n gcpProject,\n region,\n });\n\n if (!deleteServiceResult.success) {\n spinner.warn(\"Cloud Run service deletion failed\");\n console.log(\n chalk.yellow(\"Warning:\") + \" \" + deleteServiceResult.error.message,\n );\n if (deleteServiceResult.error.details) {\n console.log(\n chalk.gray(\"Details: \") + deleteServiceResult.error.details,\n );\n }\n } else {\n spinner.succeed(\"Cloud Run service deleted\");\n }\n\n // ── Delete Turso database ───────────────────────────────────────────\n spinner.start(`Deleting Turso database \"${serviceName}\"...`);\n const deleteDbResult = await deleteDatabase(tursoConfig, serviceName);\n\n if (!deleteDbResult.success) {\n spinner.warn(\"Turso database deletion failed\");\n console.log(\n chalk.yellow(\"Warning:\") + \" \" + deleteDbResult.error.message,\n );\n if (deleteDbResult.error.details) {\n console.log(chalk.gray(\"Details: \") + deleteDbResult.error.details);\n }\n } else {\n spinner.succeed(\"Turso database deleted\");\n }\n\n // ── Summary ─────────────────────────────────────────────────────────\n console.log();\n if (deleteServiceResult.success && deleteDbResult.success) {\n console.log(chalk.green.bold(\"All resources destroyed successfully.\"));\n } else {\n console.log(\n chalk.yellow.bold(\"Destroy completed with warnings.\") +\n \" Some resources may need manual cleanup.\",\n );\n }\n console.log();\n });\n\nexport function registerDestroyCommand(ctx: PluginContext): void {\n ctx.program.addCommand(destroyCommand);\n}\n","/**\n * @fluid-app/fluid-cli-portal\n *\n * Fluid CLI plugin for building and deploying portal applications.\n * Auto-discovered by @fluid-app/fluid-cli via the fluid-cli-* naming convention.\n */\n\nimport type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { registerCreateCommand } from \"./commands/create.js\";\nimport { registerDevCommand } from \"./commands/dev.js\";\nimport { registerBuildCommand } from \"./commands/build.js\";\nimport { registerDeployCommand } from \"./commands/deploy.js\";\nimport { registerDestroyCommand } from \"./commands/destroy.js\";\n\nconst plugin: FluidPlugin = {\n name: \"fluid-cli-portal\",\n version: \"0.1.0\",\n async register(ctx: PluginContext) {\n registerCreateCommand(ctx);\n registerDevCommand(ctx);\n registerBuildCommand(ctx);\n registerDeployCommand(ctx);\n registerDestroyCommand(ctx);\n },\n};\n\nexport default plugin;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Re-exports for programmatic usage (e.g., @fluid-app/create-portal-app)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type {\n ProjectConfig,\n CreateOptions,\n DevOptions,\n BuildOptions,\n DeployOptions,\n DestroyOptions,\n TemplateVariables,\n SelectedPageTemplate,\n TemplateName,\n} from \"./types.js\";\n\nexport { TEMPLATES } from \"./types.js\";\nexport { isTemplateName } from \"./types.js\";\n\nexport {\n getInstallCommand,\n getRunCommand,\n runPackageManager,\n installDependencies,\n} from \"./utils/package-manager.js\";\n\nexport type { Result, Success, Failure } from \"./utils/result.js\";\nexport {\n success,\n failure,\n isSuccess,\n isFailure,\n tryCatch,\n tryCatchAsync,\n unwrap,\n unwrapOr,\n mapResult,\n mapError,\n isError,\n isNodeError,\n getErrorMessage,\n} from \"./utils/result.js\";\n\nexport {\n getTemplatePaths,\n copyTemplate,\n directoryExists,\n fileExists,\n pathExists,\n createDirectory,\n getSdkVersion,\n readFileSafe,\n writeFileSafe,\n createDirectorySafe,\n copyTemplateSafe,\n getSdkVersionSafe,\n FILE_SYSTEM_ERRORS,\n type FileSystemErrorCode,\n type FileSystemError,\n type TemplatePaths,\n} from \"./utils/file-system.js\";\n\nexport { promptProjectConfig } from \"./utils/prompts.js\";\n\n// Expose the standalone Command for create-portal-app\nexport { createCommand } from \"./commands/create.js\";\nexport { destroyCommand } from \"./commands/destroy.js\";\n\nexport {\n validateGcloudInstalled,\n validateGcloudAuth,\n getGcpProject,\n deployToCloudRun,\n deleteCloudRunService,\n CLOUD_RUN_ERRORS,\n type CloudRunConfig,\n type CloudRunResult,\n type CloudRunError,\n type CloudRunDeployCallbacks,\n} from \"./utils/cloud-run.js\";\n\nexport {\n resolveFluidApiKey,\n validateFluidApiKey,\n FLUID_API_ERROR,\n type FluidApiError,\n type FluidApiErrorCode,\n type FluidCompany,\n} from \"./utils/fluid-api.js\";\n\nexport {\n validateTursoConfig,\n resolveTursoConfig,\n parseOrgList,\n ensureGroup,\n createDatabase,\n createDatabaseToken,\n deleteDatabase,\n provisionDatabase,\n fetchLocations,\n validateLocation,\n TURSO_ERROR,\n type TursoConfig,\n type ResolvedTursoConfig,\n type TursoConfigSource,\n type TursoOrg,\n type TursoDatabase,\n type TursoError,\n type TursoProvisionCallbacks,\n} from \"./utils/turso.js\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["FLUID_API_BASE"],"sources":["../src/types.ts","../src/utils/prompts.ts","../src/utils/file-system.ts","../src/utils/package-manager.ts","../src/commands/create.ts","../src/commands/dev.ts","../src/commands/build.ts","../src/utils/turso.ts","../src/utils/cloud-run.ts","../src/utils/fluid-api.ts","../src/utils/project.ts","../src/utils/extract-navigation.ts","../src/utils/navigation-sync.ts","../src/commands/deploy.ts","../src/commands/destroy.ts","../src/index.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// Template types - derived from const object\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Available project templates\n */\nexport const TEMPLATES = {\n starter: \"starter\",\n fullstack: \"fullstack\",\n} as const;\n\n/**\n * Union type of valid template names\n */\nexport type TemplateName = (typeof TEMPLATES)[keyof typeof TEMPLATES];\n\n/**\n * Type guard to check if a string is a valid template name\n */\nexport function isTemplateName(value: string): value is TemplateName {\n return Object.values(TEMPLATES).includes(value as TemplateName);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Project configuration types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Selected page template info\n */\nexport interface SelectedPageTemplate {\n readonly id: string;\n readonly slug: string;\n readonly name: string;\n}\n\n/**\n * Configuration options collected during project scaffolding\n */\nexport interface ProjectConfig {\n /** Project name (used for directory and package.json name) */\n readonly name: string;\n /** Template to scaffold from */\n readonly template: TemplateName;\n /** Whether to install dependencies after scaffolding */\n readonly installDeps: boolean;\n /** Selected optional page templates to include */\n readonly selectedPages: readonly SelectedPageTemplate[];\n}\n\n/**\n * Options for the create command (from CLI arguments)\n * Uses string types since CLI input needs validation before narrowing\n */\nexport interface CreateOptions {\n /** Template name (validated against TemplateName type at runtime) */\n readonly template?: string;\n /** Skip dependency installation */\n readonly skipInstall?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Command option types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Options for the dev command\n */\nexport interface DevOptions {\n readonly port?: number;\n readonly host?: boolean;\n}\n\n/**\n * Options for the build command\n */\nexport interface BuildOptions {\n readonly outDir?: string;\n}\n\n/**\n * Options for the deploy command\n */\nexport interface DeployOptions {\n /** Cloud Run region (default: us-central1) */\n readonly region?: string;\n /** GCP project ID (default: from gcloud config) */\n readonly gcpProject?: string;\n /** Service name override (default: from package.json) */\n readonly project?: string;\n /** Turso database group location (default: aws-us-east-1) */\n readonly dbRegion?: string;\n /** Require IAM authentication for the Cloud Run service (default: public) */\n readonly requireAuth?: boolean;\n /** Run database migrations (db:push) after successful deploy */\n readonly migrate?: boolean;\n /** Skip the local Docker build check before deploying */\n readonly skipLocalBuild?: boolean;\n /** Turso organization slug (skips interactive org selection) */\n readonly tursoOrg?: string;\n /** Fluid company API key (skips env var lookup and interactive prompt) */\n readonly fluidCompanyApiKey?: string;\n /** Skip navigation sync from portal.config.ts */\n readonly skipNavSync?: boolean;\n}\n\n/**\n * Options for the destroy command\n */\nexport interface DestroyOptions {\n /** Cloud Run region (default: us-central1) */\n readonly region?: string;\n /** GCP project ID (default: from gcloud config) */\n readonly gcpProject?: string;\n /** Service name override (default: from package.json) */\n readonly project?: string;\n /** Turso organization slug (skips interactive org selection) */\n readonly tursoOrg?: string;\n /** Fluid company API key (skips env var lookup and interactive prompt) */\n readonly fluidCompanyApiKey?: string;\n /** Skip confirmation prompt */\n readonly yes?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Template processing types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Template variables for Handlebars processing\n */\nexport interface TemplateVariables {\n readonly projectName: string;\n readonly sdkVersion: string;\n /** Selected page templates for the project */\n readonly selectedPages: readonly SelectedPageTemplate[];\n /** Whether any optional pages were selected */\n readonly hasSelectedPages: boolean;\n}\n","import prompts from \"prompts\";\nimport {\n type ProjectConfig,\n type CreateOptions,\n type SelectedPageTemplate,\n type TemplateName,\n TEMPLATES,\n isTemplateName,\n} from \"../types.js\";\n\n/**\n * Optional page template shape\n */\ninterface OptionalPageTemplate {\n readonly id: string;\n readonly slug: string;\n readonly name: string;\n readonly description: string;\n}\n\n/**\n * Available optional page templates that can be selected during project creation.\n * Core pages (Messaging, Contacts, CRM) are always included automatically.\n */\nconst OPTIONAL_PAGE_TEMPLATES: readonly OptionalPageTemplate[] = [\n // Currently no optional pages - all pages are core\n // Future optional pages can be added here:\n // { id: 'orders', slug: 'orders', name: 'Orders', description: 'Order management page' },\n // { id: 'products', slug: 'products', name: 'Products', description: 'Product catalog page' },\n];\n\n/**\n * Prompts the user for project configuration\n * Pre-fills values from CLI options when provided\n */\nexport async function promptProjectConfig(\n projectName: string,\n options: CreateOptions,\n): Promise<ProjectConfig | null> {\n // Build questions based on what options are missing\n const questions: prompts.PromptObject[] = [];\n\n // Template selection (if not provided via CLI)\n if (!options.template) {\n questions.push({\n type: \"select\",\n name: \"template\",\n message: \"Select a project template\",\n choices: [\n {\n title: \"Starter\",\n value: TEMPLATES.starter,\n description: \"Frontend-only (React + Vite + Tailwind + portal-sdk)\",\n },\n {\n title: \"Fullstack\",\n value: TEMPLATES.fullstack,\n description: \"Frontend + API server (Hono + Drizzle + SQLite)\",\n },\n ],\n });\n }\n\n // Page template selection (only if there are optional templates)\n if (OPTIONAL_PAGE_TEMPLATES.length > 0) {\n questions.push({\n type: \"multiselect\",\n name: \"selectedPages\",\n message: \"Select additional page templates to include\",\n instructions:\n \"\\n Space to select, Enter to confirm. Core pages (Messaging, Contacts, CRM) are always included.\",\n choices: OPTIONAL_PAGE_TEMPLATES.map((page) => ({\n title: page.name,\n value: { id: page.id, slug: page.slug, name: page.name },\n description: page.description,\n })),\n });\n }\n\n // Install dependencies\n if (!options.skipInstall) {\n questions.push({\n type: \"confirm\",\n name: \"installDeps\",\n message: \"Install dependencies?\",\n initial: true,\n });\n }\n\n // Fast-path: all options provided via CLI flags, no prompts needed\n if (questions.length === 0) {\n const templateRaw = options.template;\n const template: TemplateName = isTemplateName(templateRaw ?? \"\")\n ? (templateRaw as TemplateName)\n : TEMPLATES.starter;\n\n return {\n name: projectName,\n template,\n installDeps: options.skipInstall ? false : true,\n selectedPages: [],\n } satisfies ProjectConfig;\n }\n\n // Handle Ctrl+C gracefully\n let cancelled = false;\n const response = await prompts(questions, {\n onCancel: () => {\n cancelled = true;\n return false;\n },\n });\n\n if (cancelled) {\n return null;\n }\n\n // Merge CLI options with prompted values\n const templateRaw = options.template ?? response.template;\n const template: TemplateName = isTemplateName(templateRaw)\n ? templateRaw\n : TEMPLATES.starter;\n\n // Parse selected pages\n const selectedPages: readonly SelectedPageTemplate[] =\n response.selectedPages ?? [];\n\n return {\n name: projectName,\n template,\n installDeps: options.skipInstall ? false : (response.installDeps ?? true),\n selectedPages,\n } satisfies ProjectConfig;\n}\n","import type { CliError } from \"@fluid-app/fluid-cli\";\nimport { readdir, readFile, stat, mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport Handlebars from \"handlebars\";\nimport type { TemplateVariables } from \"../types.js\";\nimport { type Result, success, failure } from \"@fluid-app/fluid-cli\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Find the package root by walking up from __dirname to the nearest package.json.\n * Works whether running from dist/ (bundled) or src/utils/ (tsx dev mode).\n */\nfunction findPackageRoot(): string {\n let dir = __dirname;\n while (!existsSync(join(dir, \"package.json\"))) {\n const parent = dirname(dir);\n if (parent === dir) throw new Error(\"Could not find package root\");\n dir = parent;\n }\n return dir;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// File system operation error types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Error types for file system operations\n */\nexport const FILE_SYSTEM_ERRORS = {\n directoryNotFound: \"DIRECTORY_NOT_FOUND\",\n fileNotFound: \"FILE_NOT_FOUND\",\n readError: \"READ_ERROR\",\n writeError: \"WRITE_ERROR\",\n templateError: \"TEMPLATE_ERROR\",\n} as const;\n\n/**\n * Union type for file system error codes\n */\nexport type FileSystemErrorCode =\n (typeof FILE_SYSTEM_ERRORS)[keyof typeof FILE_SYSTEM_ERRORS];\n\n/**\n * Structured file system error with code for pattern matching\n */\nexport interface FileSystemError extends CliError {\n readonly code: FileSystemErrorCode;\n readonly message: string;\n readonly path?: string;\n readonly cause?: Error;\n}\n\n/**\n * Create a file system error\n */\nfunction createFsError(\n code: FileSystemErrorCode,\n message: string,\n path?: string,\n cause?: Error,\n): FileSystemError {\n return { code, message, path, cause };\n}\n\n/**\n * Paths for the base + overlay template system\n */\nexport interface TemplatePaths {\n /** Path to shared frontend files used by all templates */\n readonly base: string;\n /** Path to template-specific overlay files */\n readonly overlay: string;\n}\n\n/**\n * Gets paths for the base + overlay template system.\n *\n * The create command copies `base` first, then the `overlay` on top.\n * Any overlay file with the same relative path overwrites the base version.\n */\nexport function getTemplatePaths(templateName: string): TemplatePaths {\n const packageRoot = findPackageRoot();\n const templatesDir = join(packageRoot, \"templates\");\n return {\n base: join(templatesDir, \"base\"),\n overlay: join(templatesDir, templateName),\n };\n}\n\n/**\n * Gets all files in a directory recursively\n */\nasync function getFiles(dir: string, baseDir: string = dir): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await getFiles(fullPath, baseDir)));\n } else {\n // Return relative path from baseDir\n files.push(fullPath.slice(baseDir.length + 1));\n }\n }\n\n return files;\n}\n\n/**\n * Processes a template file with Handlebars\n * Files ending in .template have the extension removed and content processed\n * Other files are copied as-is\n */\nfunction processTemplate(\n content: string,\n variables: TemplateVariables,\n isTemplate: boolean,\n filePath?: string,\n): string {\n if (!isTemplate) {\n return content;\n }\n\n try {\n const template = Handlebars.compile(content);\n return template(variables);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Template processing failed${filePath ? ` for ${filePath}` : \"\"}: ${message}`,\n );\n }\n}\n\n/**\n * Gets the output filename for a template file\n * Removes .template extension if present\n */\nfunction getOutputFilename(filename: string): string {\n if (filename.endsWith(\".template\")) {\n return filename.slice(0, -\".template\".length);\n }\n return filename;\n}\n\n/**\n * Copies a template directory to the target directory\n * Processes .template files with Handlebars\n */\nexport async function copyTemplate(\n templatePath: string,\n targetPath: string,\n variables: TemplateVariables,\n): Promise<void> {\n const files = await getFiles(templatePath);\n\n for (const file of files) {\n const sourcePath = join(templatePath, file);\n const isTemplate = file.endsWith(\".template\");\n const outputFile = getOutputFilename(file);\n const destPath = join(targetPath, outputFile);\n\n // Create directory if needed\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n\n // Read source file\n const content = await readFile(sourcePath, \"utf-8\");\n\n // Process and write\n const processed = processTemplate(\n content,\n variables,\n isTemplate,\n sourcePath,\n );\n await writeFile(destPath, processed, \"utf-8\");\n }\n}\n\n/**\n * Checks if a directory exists\n */\nexport async function directoryExists(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Checks if a file exists\n */\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Checks if a path exists (file or directory)\n */\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Creates a directory\n */\nexport async function createDirectory(path: string): Promise<void> {\n await mkdir(path, { recursive: true });\n}\n\n/**\n * Reads the SDK version from the workspace package.json\n * Falls back to ^0.1.0 if not found\n */\nexport async function getSdkVersion(): Promise<string> {\n try {\n // Try to read from workspace\n // CLI lives at packages/cli/portal/, SDK at packages/portal/sdk/\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPackagePath = join(packagesRoot, \"portal\", \"sdk\", \"package.json\");\n\n const content = await readFile(sdkPackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n return `^${pkg.version ?? \"0.1.0\"}`;\n } catch {\n // Fallback for when running outside the workspace\n return \"^0.1.0\";\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result-based variants for type-safe error handling\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Read a file's content with Result-based error handling\n */\nexport async function readFileSafe(\n path: string,\n): Promise<Result<string, FileSystemError>> {\n try {\n const content = await readFile(path, \"utf-8\");\n return success(content);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.readError,\n `Failed to read file: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Write content to a file with Result-based error handling\n */\nexport async function writeFileSafe(\n path: string,\n content: string,\n): Promise<Result<void, FileSystemError>> {\n try {\n await writeFile(path, content, \"utf-8\");\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.writeError,\n `Failed to write file: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Create a directory with Result-based error handling\n */\nexport async function createDirectorySafe(\n path: string,\n): Promise<Result<void, FileSystemError>> {\n try {\n await mkdir(path, { recursive: true });\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.writeError,\n `Failed to create directory: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Copy a template directory with Result-based error handling\n */\nexport async function copyTemplateSafe(\n templatePath: string,\n targetPath: string,\n variables: Readonly<TemplateVariables>,\n): Promise<Result<void, FileSystemError>> {\n try {\n const files = await getFiles(templatePath);\n\n for (const file of files) {\n const sourcePath = join(templatePath, file);\n const isTemplateFile = file.endsWith(\".template\");\n const outputFile = getOutputFilename(file);\n const destPath = join(targetPath, outputFile);\n\n // Create directory if needed\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n\n // Read source file\n const content = await readFile(sourcePath, \"utf-8\");\n\n // Process and write\n const processed = processTemplate(\n content,\n variables,\n isTemplateFile,\n sourcePath,\n );\n await writeFile(destPath, processed, \"utf-8\");\n }\n\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.templateError,\n `Failed to copy template from ${templatePath} to ${targetPath}`,\n templatePath,\n error,\n ),\n );\n }\n}\n\n/**\n * Get SDK version with Result-based error handling\n * Unlike getSdkVersion, this returns an error instead of a fallback\n */\nexport async function getSdkVersionSafe(): Promise<\n Result<string, FileSystemError>\n> {\n try {\n // CLI lives at packages/cli/portal/, SDK at packages/portal/sdk/\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPackagePath = join(packagesRoot, \"portal\", \"sdk\", \"package.json\");\n\n const content = await readFile(sdkPackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n const version = pkg.version;\n\n if (version === undefined) {\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.readError,\n \"SDK package.json does not contain a version field\",\n sdkPackagePath,\n ),\n );\n }\n\n return success(`^${version}`);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.fileNotFound,\n \"Could not find SDK package.json\",\n undefined,\n error,\n ),\n );\n }\n}\n","import { execa } from \"execa\";\n\n/**\n * Returns the install command for pnpm\n */\nexport function getInstallCommand(): string {\n return \"pnpm install\";\n}\n\n/**\n * Returns the run command for pnpm\n */\nexport function getRunCommand(script: string): string {\n return `pnpm run ${script}`;\n}\n\n/**\n * Runs a pnpm command in the specified directory\n */\nexport async function runPackageManager(\n args: string[],\n cwd: string,\n): Promise<void> {\n await execa(\"pnpm\", args, {\n cwd,\n stdio: \"inherit\",\n });\n}\n\n/**\n * Installs dependencies using pnpm\n */\nexport async function installDependencies(cwd: string): Promise<void> {\n await runPackageManager([\"install\"], cwd);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { join } from \"node:path\";\nimport { copyFile } from \"node:fs/promises\";\nimport { type CreateOptions, TEMPLATES } from \"../types.js\";\nimport { promptProjectConfig } from \"../utils/prompts.js\";\nimport {\n getTemplatePaths,\n copyTemplate,\n directoryExists,\n createDirectory,\n getSdkVersion,\n fileExists,\n} from \"../utils/file-system.js\";\nimport {\n installDependencies,\n getRunCommand,\n} from \"../utils/package-manager.js\";\n\nexport const createCommand: Command = new Command(\"create\")\n .description(\"Create a new Fluid portal application\")\n .argument(\"<app-name>\", \"Name of the application to create\")\n .option(\"-t, --template <template>\", \"Project template (starter, fullstack)\")\n .option(\"--skip-install\", \"Skip dependency installation\")\n .action(async (appName: string, options: CreateOptions) => {\n try {\n console.log();\n console.log(chalk.bold(\"Creating a new Fluid portal application\"));\n console.log();\n\n // Validate app name\n if (!/^[a-z0-9-]+$/.test(appName)) {\n console.error(\n chalk.red(\n \"Error: App name must contain only lowercase letters, numbers, and hyphens\",\n ),\n );\n process.exit(1);\n }\n\n // Check if directory already exists\n const targetPath = join(process.cwd(), appName);\n if (await directoryExists(targetPath)) {\n console.error(\n chalk.red(`Error: Directory \"${appName}\" already exists`),\n );\n process.exit(1);\n }\n\n // Prompt for configuration\n const config = await promptProjectConfig(appName, options);\n if (!config) {\n console.log();\n console.log(chalk.yellow(\"Cancelled\"));\n process.exit(0);\n }\n\n console.log();\n\n // Get template paths (base + overlay)\n const templatePaths = getTemplatePaths(config.template);\n if (!(await directoryExists(templatePaths.base))) {\n console.error(chalk.red(\"Error: Base template not found\"));\n process.exit(1);\n }\n if (!(await directoryExists(templatePaths.overlay))) {\n console.error(\n chalk.red(`Error: Template \"${config.template}\" not found`),\n );\n process.exit(1);\n }\n\n // Get SDK version\n const sdkVersion = await getSdkVersion();\n\n // Create project directory\n const spinner = ora(\"Creating project directory...\").start();\n try {\n await createDirectory(targetPath);\n spinner.succeed(\"Created project directory\");\n } catch (error) {\n spinner.fail(\"Failed to create project directory\");\n throw error;\n }\n\n // Copy base template first, then overlay template-specific files on top\n const templateVariables = {\n projectName: config.name,\n sdkVersion,\n selectedPages: config.selectedPages,\n hasSelectedPages: config.selectedPages.length > 0,\n };\n\n spinner.start(\"Copying template files...\");\n try {\n await copyTemplate(templatePaths.base, targetPath, templateVariables);\n await copyTemplate(\n templatePaths.overlay,\n targetPath,\n templateVariables,\n );\n\n // Copy .env.example → .env so dotenv works out of the box\n const envExamplePath = join(targetPath, \".env.example\");\n if (await fileExists(envExamplePath)) {\n await copyFile(envExamplePath, join(targetPath, \".env\"));\n }\n\n spinner.succeed(\"Copied template files\");\n } catch (error) {\n spinner.fail(\"Failed to copy template files\");\n throw error;\n }\n\n // Install dependencies\n if (config.installDeps) {\n spinner.start(\"Installing dependencies with pnpm...\");\n try {\n await installDependencies(targetPath);\n spinner.succeed(\"Installed dependencies\");\n } catch {\n spinner.fail(\"Failed to install dependencies\");\n console.log();\n console.log(\n chalk.yellow(\"You can try installing dependencies manually:\"),\n );\n console.log(chalk.cyan(` cd ${appName}`));\n console.log(chalk.cyan(\" pnpm install\"));\n }\n }\n\n // Print success message\n console.log();\n console.log(\n chalk.green.bold(\"Success!\") + ` Created ${chalk.cyan(appName)}`,\n );\n console.log();\n console.log(\"Next steps:\");\n console.log();\n console.log(chalk.cyan(` cd ${appName}`));\n if (!config.installDeps) {\n console.log(chalk.cyan(\" pnpm install\"));\n }\n if (config.template === TEMPLATES.fullstack) {\n console.log(\n chalk.cyan(` ${getRunCommand(\"db:push\")}`) +\n \" # create the database\",\n );\n }\n console.log(chalk.cyan(` ${getRunCommand(\"dev\")}`));\n console.log();\n console.log(\n \"Then open \" +\n chalk.cyan(\"http://localhost:5173\") +\n \" in your browser.\",\n );\n if (config.template === TEMPLATES.fullstack) {\n console.log(\n \"API server: \" + chalk.cyan(\"http://localhost:5173/api/health\"),\n );\n }\n console.log(\n chalk.dim(\n \" (port may differ if 5173 is in use — check the dev server output)\",\n ),\n );\n console.log();\n console.log(\n \"Edit \" +\n chalk.cyan(\"src/portal.config.ts\") +\n \" to customize your navigation.\",\n );\n console.log();\n } catch (error) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (error instanceof Error ? error.message : String(error)),\n );\n console.log();\n process.exit(1);\n }\n });\n\nexport function registerCreateCommand(ctx: PluginContext): void {\n ctx.program.addCommand(createCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport { execa } from \"execa\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { DevOptions } from \"../types.js\";\n\nexport const devCommand: Command = new Command(\"dev\")\n .description(\"Start the development server\")\n .option(\"-p, --port <port>\", \"Port to run the dev server on\", \"5173\")\n .option(\"--host\", \"Expose the dev server to the network\")\n .action(async (options: DevOptions) => {\n const cwd = process.cwd();\n\n // Check if we're in a Fluid project\n const packageJsonPath = join(cwd, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n console.error(\n chalk.red(\"Error: No package.json found in current directory\"),\n );\n console.error(\n chalk.yellow(\"Make sure you're in a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n // Check for vite.config\n const viteConfigPath = join(cwd, \"vite.config.ts\");\n if (!existsSync(viteConfigPath)) {\n console.error(chalk.red(\"Error: No vite.config.ts found\"));\n console.error(\n chalk.yellow(\"This command must be run from a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n // Build vite args\n const viteArgs = [\"vite\"];\n if (options.port) {\n viteArgs.push(\"--port\", String(options.port));\n }\n if (options.host) {\n viteArgs.push(\"--host\");\n }\n\n console.log();\n console.log(chalk.bold(\"Starting development server...\"));\n console.log();\n\n try {\n await execa(\"pnpm\", viteArgs, {\n cwd,\n stdio: \"inherit\",\n });\n } catch (error) {\n // execa v8 sets `signal` (not `code`) when a process is killed by a signal\n const execaError = error as { signal?: string };\n if (execaError.signal === \"SIGINT\") {\n return;\n }\n console.error(chalk.red(\"Development server exited with an error\"));\n process.exit(1);\n }\n });\n\nexport function registerDevCommand(ctx: PluginContext): void {\n ctx.program.addCommand(devCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { execa } from \"execa\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { BuildOptions } from \"../types.js\";\n\nexport const buildCommand: Command = new Command(\"build\")\n .description(\"Build the application for production\")\n .option(\"-o, --out-dir <dir>\", \"Output directory\", \"dist\")\n .action(async (options: BuildOptions) => {\n const cwd = process.cwd();\n\n // Check if we're in a Fluid project\n const packageJsonPath = join(cwd, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n console.error(\n chalk.red(\"Error: No package.json found in current directory\"),\n );\n console.error(\n chalk.yellow(\"Make sure you're in a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n // Check for vite.config\n const viteConfigPath = join(cwd, \"vite.config.ts\");\n if (!existsSync(viteConfigPath)) {\n console.error(chalk.red(\"Error: No vite.config.ts found\"));\n console.error(\n chalk.yellow(\"This command must be run from a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold(\"Building for production...\"));\n console.log();\n\n const spinner = ora(\"Building...\").start();\n\n try {\n // Run the project's build script (handles both starter and fullstack templates)\n await execa(\"pnpm\", [\"run\", \"build\"], {\n cwd,\n stdio: \"pipe\",\n });\n\n spinner.succeed(\"Build completed\");\n console.log();\n console.log(`Output written to ${chalk.cyan(options.outDir ?? \"dist\")}/`);\n console.log();\n console.log(\"To preview the build locally:\");\n console.log(chalk.cyan(\" pnpm vite preview\"));\n console.log();\n } catch (error) {\n spinner.fail(\"Build failed\");\n const execaError = error as { stderr?: string };\n if (execaError.stderr) {\n console.error(execaError.stderr);\n }\n process.exit(1);\n }\n });\n\nexport function registerBuildCommand(ctx: PluginContext): void {\n ctx.program.addCommand(buildCommand);\n}\n","/**\n * Turso database provisioning utilities\n *\n * This module provides functions for provisioning Turso databases\n * using the Turso Platform REST API for Fluid portal app deployments.\n *\n * Credential resolution order:\n * 1. Environment variables (TURSO_API_TOKEN + TURSO_ORG) — CI/CD\n * 2. --turso-org flag > single org > \"fluid\" org > current org > interactive prompt\n * 3. Fail with instructions for both options\n */\n\nimport type { CliError } from \"@fluid-app/fluid-cli\";\nimport { type Result, success, failure } from \"@fluid-app/fluid-cli\";\nimport { execa } from \"execa\";\nimport prompts from \"prompts\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Turso Platform API base URL\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst TURSO_API_BASE = \"https://api.turso.tech/v1\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error constants - structured error definitions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const TURSO_ERROR = {\n MISSING_TOKEN: {\n code: \"MISSING_TOKEN\",\n message: \"TURSO_API_TOKEN environment variable is not set\",\n },\n MISSING_ORG: {\n code: \"MISSING_ORG\",\n message: \"TURSO_ORG environment variable is not set\",\n },\n GROUP_CREATION_FAILED: {\n code: \"GROUP_CREATION_FAILED\",\n message: \"Failed to create database group\",\n },\n DATABASE_CREATION_FAILED: {\n code: \"DATABASE_CREATION_FAILED\",\n message: \"Failed to create database\",\n },\n TOKEN_CREATION_FAILED: {\n code: \"TOKEN_CREATION_FAILED\",\n message: \"Failed to create database auth token\",\n },\n DATABASE_DELETION_FAILED: {\n code: \"DATABASE_DELETION_FAILED\",\n message: \"Failed to delete database\",\n },\n INVALID_LOCATION: {\n code: \"INVALID_LOCATION\",\n message: \"Invalid database location\",\n },\n LOCATIONS_FETCH_FAILED: {\n code: \"LOCATIONS_FETCH_FAILED\",\n message: \"Failed to fetch available Turso locations\",\n },\n TURSO_CLI_NOT_FOUND: {\n code: \"TURSO_CLI_NOT_FOUND\",\n message: \"Turso CLI is not installed\",\n },\n TURSO_CLI_NOT_AUTHENTICATED: {\n code: \"TURSO_CLI_NOT_AUTHENTICATED\",\n message: \"Turso CLI is not authenticated\",\n },\n TURSO_NO_ORGS: {\n code: \"TURSO_NO_ORGS\",\n message: \"No organizations found in Turso CLI\",\n },\n} as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Turso error codes derived from error constants\n */\nexport type TursoErrorCode =\n (typeof TURSO_ERROR)[keyof typeof TURSO_ERROR][\"code\"];\n\n/**\n * Structured Turso error with code for pattern matching\n */\nexport interface TursoError extends CliError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\n/**\n * Create a Turso error from a constant and optional details\n */\nfunction createTursoError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): TursoError {\n return { code: template.code, message: template.message, details };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Configuration types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validated Turso configuration from environment variables\n */\nexport interface TursoConfig {\n readonly apiToken: string;\n readonly org: string;\n}\n\n/**\n * Where the Turso credentials were resolved from\n */\nexport type TursoConfigSource = \"env\" | \"cli\";\n\n/**\n * Turso configuration with source tracking\n */\nexport interface ResolvedTursoConfig extends TursoConfig {\n readonly source: TursoConfigSource;\n}\n\n/**\n * Parsed Turso organization from CLI output\n */\nexport interface TursoOrg {\n readonly name: string;\n readonly slug: string;\n readonly isCurrent: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Provisioned Turso database connection details\n */\nexport interface TursoDatabase {\n readonly url: string;\n readonly authToken: string;\n readonly databaseName: string;\n readonly hostname: string;\n /** true when the database was freshly created (HTTP 200), false when it already existed (HTTP 409) */\n readonly isNew: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Callback types for progress tracking\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Callbacks for database provisioning progress\n */\nexport interface TursoProvisionCallbacks {\n readonly onGroupCreating?: () => void;\n readonly onGroupReady?: () => void;\n readonly onDatabaseCreating?: (name: string) => void;\n readonly onDatabaseReady?: (name: string) => void;\n readonly onTokenCreating?: () => void;\n readonly onTokenReady?: () => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Configuration validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validate that required Turso environment variables are present\n */\nexport function validateTursoConfig(): Result<TursoConfig, TursoError> {\n const apiToken = process.env.TURSO_API_TOKEN;\n if (!apiToken) {\n return failure(createTursoError(TURSO_ERROR.MISSING_TOKEN));\n }\n\n const org = process.env.TURSO_ORG;\n if (!org) {\n return failure(createTursoError(TURSO_ERROR.MISSING_ORG));\n }\n\n return success({ apiToken, org });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Turso CLI helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Check if the Turso CLI is installed and authenticated.\n * Runs `turso auth whoami` which verifies both in a single call.\n */\nasync function isTursoCliAvailable(): Promise<boolean> {\n try {\n await execa(\"turso\", [\"auth\", \"whoami\"], { stdio: \"pipe\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the Turso platform API token from the CLI.\n * Runs `turso auth token` which outputs the token to stdout.\n */\nasync function getTursoCliToken(): Promise<Result<string, TursoError>> {\n try {\n const { stdout } = await execa(\"turso\", [\"auth\", \"token\"], {\n stdio: \"pipe\",\n });\n // The CLI prints a warning before the token — grab the last non-empty line\n const token = stdout.trim().split(\"\\n\").filter(Boolean).pop()?.trim() ?? \"\";\n if (!token) {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_CLI_NOT_AUTHENTICATED,\n \"turso auth token returned an empty value. Run: turso auth login\",\n ),\n );\n }\n return success(token);\n } catch {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_CLI_NOT_AUTHENTICATED,\n \"Failed to get token from Turso CLI. Run: turso auth login\",\n ),\n );\n }\n}\n\n/**\n * Parse the tabular output of `turso org list`.\n *\n * Example input:\n * Name Slug Type\n * My Org my-org personal (current)\n * Team Org team-org team\n *\n * Exported for unit testing.\n */\nexport function parseOrgList(stdout: string): TursoOrg[] {\n const lines = stdout.trim().split(\"\\n\");\n // Skip the header row\n if (lines.length <= 1) return [];\n\n return lines.slice(1).reduce<TursoOrg[]>((orgs, line) => {\n const trimmed = line.trim();\n if (!trimmed) return orgs;\n\n // Split on 2+ whitespace characters (tab-like column separators)\n const columns = trimmed.split(/\\s{2,}/);\n if (columns.length < 2) return orgs;\n\n const name = columns[0]!.trim();\n const slug = columns[1]!.trim().replace(/\\s*\\(current\\)/, \"\");\n const isCurrent = trimmed.includes(\"(current)\");\n\n if (name && slug) {\n orgs.push({ name, slug, isCurrent });\n }\n return orgs;\n }, []);\n}\n\n/**\n * Get the list of Turso organizations from the CLI.\n */\nasync function getTursoCliOrgs(): Promise<Result<TursoOrg[], TursoError>> {\n try {\n const { stdout } = await execa(\"turso\", [\"org\", \"list\"], {\n stdio: \"pipe\",\n });\n const orgs = parseOrgList(stdout);\n if (orgs.length === 0) {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_NO_ORGS,\n \"No organizations found. Create one at https://turso.tech\",\n ),\n );\n }\n return success(orgs);\n } catch {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_NO_ORGS,\n \"Failed to list organizations from Turso CLI.\",\n ),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Credential resolution\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst TURSO_AUTH_HELP = [\n \"Option 1 — Turso CLI (recommended for local dev):\",\n \" curl -sSfL https://get.tur.so/install.sh | bash\",\n \" turso auth login\",\n \"\",\n \"Option 2 — Environment variables (CI/CD):\",\n \" export TURSO_API_TOKEN=your_token_here\",\n \" export TURSO_ORG=your_org_name\",\n \"\",\n \"Get your API token at: https://turso.tech/app/settings/api-tokens\",\n].join(\"\\n\");\n\n/**\n * Resolve Turso credentials from the best available source.\n *\n * Priority:\n * 1. Environment variables (TURSO_API_TOKEN + TURSO_ORG) — immediate, for CI/CD\n * 2. Turso CLI (turso auth token + turso org list) — interactive, for local dev\n * 3. Fail with instructions for both options\n *\n * @param tursoOrgOverride - Optional org slug from --turso-org flag (skips interactive selection)\n */\nexport async function resolveTursoConfig(\n tursoOrgOverride?: string,\n): Promise<Result<ResolvedTursoConfig, TursoError>> {\n // ── Path 1: Environment variables ──────────────────────────────────────\n const envToken = process.env.TURSO_API_TOKEN;\n const envOrg = process.env.TURSO_ORG;\n\n if (envToken && envOrg) {\n return success({ apiToken: envToken, org: envOrg, source: \"env\" });\n }\n\n // ── Path 2: Turso CLI ─────────────────────────────────────────────────\n const cliAvailable = await isTursoCliAvailable();\n if (!cliAvailable) {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_CLI_NOT_FOUND,\n `No Turso credentials found.\\n\\n${TURSO_AUTH_HELP}`,\n ),\n );\n }\n\n // Get API token from CLI\n const tokenResult = await getTursoCliToken();\n if (!tokenResult.success) return tokenResult;\n const apiToken = tokenResult.value;\n\n // Resolve organization\n if (tursoOrgOverride) {\n return success({\n apiToken,\n org: tursoOrgOverride,\n source: \"cli\",\n });\n }\n\n const orgsResult = await getTursoCliOrgs();\n if (!orgsResult.success) return orgsResult;\n const orgs = orgsResult.value;\n\n // Single org — auto-select\n if (orgs.length === 1) {\n return success({\n apiToken,\n org: orgs[0]!.slug,\n source: \"cli\",\n });\n }\n\n // Multiple orgs — check for \"fluid\" org first, then current, then prompt\n const fluidOrg = orgs.find((o) => o.slug === \"fluid\");\n if (fluidOrg) {\n return success({ apiToken, org: fluidOrg.slug, source: \"cli\" });\n }\n\n const currentOrg = orgs.find((o) => o.isCurrent);\n if (currentOrg) {\n return success({\n apiToken,\n org: currentOrg.slug,\n source: \"cli\",\n });\n }\n\n // Interactive selection\n const { orgSlug } = await prompts({\n type: \"select\",\n name: \"orgSlug\",\n message: \"Which Turso organization?\",\n choices: orgs.map((o) => ({\n title: `${o.name} (${o.slug})`,\n value: o.slug,\n })),\n });\n\n if (!orgSlug) {\n return failure(\n createTursoError(\n TURSO_ERROR.TURSO_NO_ORGS,\n \"Organization selection cancelled.\",\n ),\n );\n }\n\n return success({ apiToken, org: orgSlug, source: \"cli\" });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API helper\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build standard headers for Turso API requests\n */\nfunction buildHeaders(apiToken: string): Record<string, string> {\n return {\n Authorization: `Bearer ${apiToken}`,\n \"Content-Type\": \"application/json\",\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Location validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Fetch available Turso database locations.\n * Returns a map of location ID → description (e.g., \"aws-us-east-1\" → \"US East (N. Virginia)\").\n */\nexport async function fetchLocations(\n config: TursoConfig,\n): Promise<Result<Record<string, string>, TursoError>> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(`${TURSO_API_BASE}/locations`, {\n method: \"GET\",\n headers: buildHeaders(config.apiToken),\n signal: controller.signal,\n });\n clearTimeout(timeout);\n\n if (!response.ok) {\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.LOCATIONS_FETCH_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n }\n\n const data = (await response.json()) as {\n locations: Record<string, string>;\n };\n return success(data.locations);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.LOCATIONS_FETCH_FAILED, message),\n );\n }\n}\n\n/**\n * Validate that a location string is a known Turso location.\n * On failure, returns an error listing all valid locations.\n */\nexport async function validateLocation(\n config: TursoConfig,\n location: string,\n): Promise<Result<void, TursoError>> {\n const locationsResult = await fetchLocations(config);\n if (!locationsResult.success) {\n return locationsResult;\n }\n\n const locations = locationsResult.value;\n if (location in locations) {\n return success(undefined);\n }\n\n const validLocations = Object.entries(locations)\n .map(([id, desc]) => ` ${id} — ${desc}`)\n .join(\"\\n\");\n\n return failure(\n createTursoError(\n TURSO_ERROR.INVALID_LOCATION,\n `\"${location}\" is not a valid Turso location.\\n\\nAvailable locations:\\n${validLocations}`,\n ),\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Group management\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Ensure a default database group exists in the Turso organization.\n * Creates the group if it does not exist; treats 409 (conflict) as success\n * since it means the group already exists.\n */\nexport async function ensureGroup(\n config: TursoConfig,\n location = \"aws-us-east-1\",\n): Promise<Result<void, TursoError>> {\n try {\n // Check if \"default\" group already exists\n const listController = new AbortController();\n const listTimeout = setTimeout(() => listController.abort(), 30_000);\n const listResponse = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/groups`,\n {\n method: \"GET\",\n headers: buildHeaders(config.apiToken),\n signal: listController.signal,\n },\n );\n clearTimeout(listTimeout);\n\n if (listResponse.ok) {\n const data = (await listResponse.json()) as {\n groups: Array<{ name: string }>;\n };\n if (data.groups.some((g) => g.name === \"default\")) {\n return success(undefined);\n }\n }\n\n // Group doesn't exist — create it\n const createController = new AbortController();\n const createTimeout = setTimeout(() => createController.abort(), 30_000);\n const createResponse = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/groups`,\n {\n method: \"POST\",\n headers: buildHeaders(config.apiToken),\n body: JSON.stringify({ name: \"default\", location }),\n signal: createController.signal,\n },\n );\n clearTimeout(createTimeout);\n\n // 409 = group already exists (race condition safety net)\n if (createResponse.ok || createResponse.status === 409) {\n return success(undefined);\n }\n\n const body = await createResponse.text();\n return failure(\n createTursoError(\n TURSO_ERROR.GROUP_CREATION_FAILED,\n `HTTP ${createResponse.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.GROUP_CREATION_FAILED, message),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Database creation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create a new Turso database in the default group.\n * If the database already exists (409), fetches its info via GET instead.\n * Returns the database name and hostname.\n */\nexport async function createDatabase(\n config: TursoConfig,\n name: string,\n): Promise<\n Result<{ name: string; hostname: string; isNew: boolean }, TursoError>\n> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/databases`,\n {\n method: \"POST\",\n headers: buildHeaders(config.apiToken),\n body: JSON.stringify({ name, group: \"default\" }),\n signal: controller.signal,\n },\n );\n clearTimeout(timeout);\n\n if (response.ok) {\n const data = (await response.json()) as {\n database?: Record<string, string>;\n };\n if (!data.database) {\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n \"Unexpected API response: missing database object\",\n ),\n );\n }\n return success({\n name: data.database.Name ?? data.database.name ?? name,\n hostname: data.database.Hostname ?? data.database.hostname ?? \"\",\n isNew: true,\n });\n }\n\n // Database already exists — fetch its info\n if (response.status === 409) {\n const existing = await getDatabaseInfo(config, name);\n if (!existing.success) return existing;\n return success({ ...existing.value, isNew: false });\n }\n\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.DATABASE_CREATION_FAILED, message),\n );\n }\n}\n\n/**\n * Fetch existing database info by name\n */\nasync function getDatabaseInfo(\n config: TursoConfig,\n name: string,\n): Promise<Result<{ name: string; hostname: string }, TursoError>> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/databases/${name}`,\n {\n method: \"GET\",\n headers: buildHeaders(config.apiToken),\n signal: controller.signal,\n },\n );\n clearTimeout(timeout);\n\n if (!response.ok) {\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n `Failed to fetch existing database info: HTTP ${response.status}: ${body}`,\n ),\n );\n }\n\n const data = (await response.json()) as {\n database?: Record<string, string>;\n };\n if (!data.database) {\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n \"Unexpected API response: missing database object\",\n ),\n );\n }\n return success({\n name: data.database.Name ?? data.database.name ?? name,\n hostname: data.database.Hostname ?? data.database.hostname ?? \"\",\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_CREATION_FAILED,\n `Failed to fetch existing database info: ${message}`,\n ),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Database deletion\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Delete a Turso database by name.\n * Returns void on success, or a TursoError on failure.\n */\nexport async function deleteDatabase(\n config: TursoConfig,\n name: string,\n): Promise<Result<void, TursoError>> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/databases/${name}`,\n {\n method: \"DELETE\",\n headers: buildHeaders(config.apiToken),\n signal: controller.signal,\n },\n );\n clearTimeout(timeout);\n\n if (response.ok || response.status === 404) {\n return success(undefined);\n }\n\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.DATABASE_DELETION_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.DATABASE_DELETION_FAILED, message),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Auth token creation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create an auth token for a Turso database.\n * Returns the JWT token string used for database connections.\n */\nexport async function createDatabaseToken(\n config: TursoConfig,\n dbName: string,\n): Promise<Result<string, TursoError>> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n const response = await fetch(\n `${TURSO_API_BASE}/organizations/${config.org}/databases/${dbName}/auth/tokens`,\n {\n method: \"POST\",\n headers: buildHeaders(config.apiToken),\n signal: controller.signal,\n },\n );\n clearTimeout(timeout);\n\n if (!response.ok) {\n const body = await response.text();\n return failure(\n createTursoError(\n TURSO_ERROR.TOKEN_CREATION_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n }\n\n const data = (await response.json()) as { jwt?: string };\n if (!data.jwt) {\n return failure(\n createTursoError(\n TURSO_ERROR.TOKEN_CREATION_FAILED,\n \"Unexpected API response: missing jwt field\",\n ),\n );\n }\n return success(data.jwt);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createTursoError(TURSO_ERROR.TOKEN_CREATION_FAILED, message),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Full provisioning workflow\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Provision a complete Turso database for a project.\n *\n * Orchestrates the full flow:\n * 1. Ensure a default group exists\n * 2. Create (or retrieve) the database\n * 3. Generate an auth token\n *\n * Calls progress callbacks at each step so callers can display status.\n */\nexport async function provisionDatabase(\n config: TursoConfig,\n projectName: string,\n location?: string,\n callbacks?: TursoProvisionCallbacks,\n): Promise<Result<TursoDatabase, TursoError>> {\n // Step 0: Validate location if provided\n if (location) {\n const locationResult = await validateLocation(config, location);\n if (!locationResult.success) return locationResult;\n }\n\n // Step 1: Ensure default group exists\n callbacks?.onGroupCreating?.();\n const groupResult = await ensureGroup(config, location);\n if (!groupResult.success) {\n return groupResult;\n }\n callbacks?.onGroupReady?.();\n\n // Step 2: Create the database\n callbacks?.onDatabaseCreating?.(projectName);\n const dbResult = await createDatabase(config, projectName);\n if (!dbResult.success) {\n return dbResult;\n }\n const { name: databaseName, hostname, isNew } = dbResult.value;\n callbacks?.onDatabaseReady?.(databaseName);\n\n // Step 3: Create an auth token\n callbacks?.onTokenCreating?.();\n const tokenResult = await createDatabaseToken(config, databaseName);\n if (!tokenResult.success) {\n return tokenResult;\n }\n const authToken = tokenResult.value;\n callbacks?.onTokenReady?.();\n\n return success({\n url: `libsql://${hostname}`,\n authToken,\n databaseName,\n hostname,\n isNew,\n });\n}\n","/**\n * Cloud Run deployment utilities\n *\n * This module provides functions for deploying Fluid portal apps to Google Cloud Run\n * using the gcloud CLI via execa for programmatic deployments.\n */\n\nimport type { CliError } from \"@fluid-app/fluid-cli\";\nimport { execa } from \"execa\";\nimport { type Result, success, failure } from \"@fluid-app/fluid-cli\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error constants for Cloud Run operations\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Cloud Run error codes and default messages\n */\nexport const CLOUD_RUN_ERRORS = {\n GCLOUD_NOT_INSTALLED: {\n code: \"GCLOUD_NOT_INSTALLED\",\n message:\n \"gcloud CLI is not installed. Install it from https://cloud.google.com/sdk/docs/install\",\n },\n GCLOUD_NOT_AUTHENTICATED: {\n code: \"GCLOUD_NOT_AUTHENTICATED\",\n message:\n \"gcloud CLI is not authenticated. Run 'gcloud auth login' to authenticate\",\n },\n NO_GCP_PROJECT: {\n code: \"NO_GCP_PROJECT\",\n message:\n \"No GCP project configured. Run 'gcloud config set project PROJECT_ID' or pass --gcp-project\",\n },\n DEPLOY_FAILED: {\n code: \"DEPLOY_FAILED\",\n message: \"Cloud Run deployment failed\",\n },\n SERVICE_DELETION_FAILED: {\n code: \"SERVICE_DELETION_FAILED\",\n message: \"Cloud Run service deletion failed\",\n },\n} as const;\n\n/**\n * Union type for Cloud Run error codes\n */\nexport type CloudRunErrorCode =\n (typeof CLOUD_RUN_ERRORS)[keyof typeof CLOUD_RUN_ERRORS][\"code\"];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Interfaces\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Configuration for Cloud Run deployment\n */\nexport interface CloudRunConfig {\n /** GCP project ID */\n readonly gcpProject: string;\n /** GCP region (e.g., \"us-central1\") */\n readonly region: string;\n /** Cloud Run service name */\n readonly serviceName: string;\n /** Environment variables to set on the service */\n readonly envVars: Record<string, string>;\n /** Path to the source directory to deploy */\n readonly sourceDir: string;\n /** Require IAM authentication (default: public / allow-unauthenticated) */\n readonly requireAuth?: boolean;\n}\n\n/**\n * Structured error for Cloud Run operations\n */\nexport interface CloudRunError extends CliError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\n/**\n * Result of a successful Cloud Run deployment\n */\nexport interface CloudRunResult {\n /** Service URL (e.g., \"https://my-service-abc123.a.run.app\") */\n readonly url: string;\n /** Cloud Run service name */\n readonly serviceName: string;\n /** GCP region the service is deployed to */\n readonly region: string;\n /** GCP project ID */\n readonly gcpProject: string;\n}\n\n/**\n * Callbacks for Cloud Run deployment progress tracking\n */\nexport interface CloudRunDeployCallbacks {\n readonly onValidating?: () => void;\n readonly onDeploying?: () => void;\n readonly onDeployComplete?: (url: string) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Internal helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create a CloudRunError from a constant and optional details\n */\nfunction createCloudRunError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): CloudRunError {\n return { code: template.code, message: template.message, details };\n}\n\n/**\n * Build a KEY=VALUE string from an env vars record using gcloud's custom\n * delimiter syntax to avoid issues with values containing commas.\n * Prefix with `^::^` and join entries with `::` instead of `,`.\n */\nfunction buildEnvVarsString(envVars: Record<string, string>): string {\n const entries = Object.entries(envVars).map(\n ([key, value]) => `${key}=${value}`,\n );\n return `^::^${entries.join(\"::\")}`;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Validation functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validate that the gcloud CLI is installed\n */\nexport async function validateGcloudInstalled(): Promise<\n Result<void, CloudRunError>\n> {\n try {\n await execa(\"gcloud\", [\"--version\"], { stdio: \"pipe\", timeout: 60_000 });\n return success(undefined);\n } catch {\n return failure(createCloudRunError(CLOUD_RUN_ERRORS.GCLOUD_NOT_INSTALLED));\n }\n}\n\n/**\n * Validate that the gcloud CLI has an active authenticated account\n */\nexport async function validateGcloudAuth(): Promise<\n Result<void, CloudRunError>\n> {\n try {\n const { stdout } = await execa(\n \"gcloud\",\n [\"auth\", \"list\", \"--format=json\"],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n const accounts: readonly { status: string }[] = JSON.parse(stdout);\n const hasActiveAccount = accounts.some(\n (account) => account.status === \"ACTIVE\",\n );\n\n if (!hasActiveAccount) {\n return failure(\n createCloudRunError(CLOUD_RUN_ERRORS.GCLOUD_NOT_AUTHENTICATED),\n );\n }\n\n return success(undefined);\n } catch {\n return failure(\n createCloudRunError(\n CLOUD_RUN_ERRORS.GCLOUD_NOT_AUTHENTICATED,\n \"Failed to check gcloud authentication status\",\n ),\n );\n }\n}\n\n/**\n * Get the currently configured GCP project from gcloud config\n */\nexport async function getGcpProject(): Promise<Result<string, CloudRunError>> {\n try {\n const { stdout } = await execa(\n \"gcloud\",\n [\"config\", \"get-value\", \"project\"],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n const project = stdout.trim();\n\n if (!project || project === \"(unset)\") {\n return failure(createCloudRunError(CLOUD_RUN_ERRORS.NO_GCP_PROJECT));\n }\n\n return success(project);\n } catch {\n return failure(\n createCloudRunError(\n CLOUD_RUN_ERRORS.NO_GCP_PROJECT,\n \"Failed to read GCP project from gcloud config\",\n ),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Build log retrieval (best-effort diagnostics)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Fetch the last 30 lines of the most recent Cloud Build log.\n * Returns `undefined` on any failure — this is best-effort diagnostic info.\n */\nasync function fetchRecentBuildLogs(\n gcpProject: string,\n region: string,\n): Promise<string | undefined> {\n try {\n // Get the most recent build ID\n const { stdout: buildId } = await execa(\n \"gcloud\",\n [\n \"builds\",\n \"list\",\n \"--limit=1\",\n \"--project\",\n gcpProject,\n \"--region\",\n region,\n \"--format=value(id)\",\n ],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n const trimmedId = buildId.trim();\n if (!trimmedId) return undefined;\n\n // Fetch the build log\n const { stdout: logText } = await execa(\n \"gcloud\",\n [\"builds\", \"log\", trimmedId, \"--project\", gcpProject, \"--region\", region],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n // Return last 30 lines\n const lines = logText.split(\"\\n\");\n return lines.slice(-30).join(\"\\n\");\n } catch {\n return undefined;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Service URL retrieval (fallback)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Retrieve the service URL via `gcloud run services describe`.\n * Used as a fallback when the deploy command does not return parseable JSON.\n */\nasync function getServiceUrl(\n serviceName: string,\n gcpProject: string,\n region: string,\n): Promise<Result<string, CloudRunError>> {\n try {\n const { stdout } = await execa(\n \"gcloud\",\n [\n \"run\",\n \"services\",\n \"describe\",\n serviceName,\n \"--project\",\n gcpProject,\n \"--region\",\n region,\n \"--format=json\",\n ],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n\n const service = JSON.parse(stdout) as {\n status?: { url?: string };\n };\n\n const url = service.status?.url;\n\n if (!url) {\n return failure(\n createCloudRunError(\n CLOUD_RUN_ERRORS.DEPLOY_FAILED,\n \"Service deployed but no URL found in service description\",\n ),\n );\n }\n\n return success(url);\n } catch (err) {\n const details =\n err instanceof Error ? err.message : \"Unknown error describing service\";\n return failure(\n createCloudRunError(CLOUD_RUN_ERRORS.DEPLOY_FAILED, details),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Deployment\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Deploy to Cloud Run using `gcloud run deploy --source`\n *\n * This builds the container from source and deploys it in a single step.\n * Progress is reported through optional callbacks.\n */\nexport async function deployToCloudRun(\n config: Readonly<CloudRunConfig>,\n callbacks?: Readonly<CloudRunDeployCallbacks>,\n): Promise<Result<CloudRunResult, CloudRunError>> {\n // Validate prerequisites\n callbacks?.onValidating?.();\n\n const installCheck = await validateGcloudInstalled();\n if (!installCheck.success) {\n return failure(installCheck.error);\n }\n\n const authCheck = await validateGcloudAuth();\n if (!authCheck.success) {\n return failure(authCheck.error);\n }\n\n // Build the deploy command arguments\n const args = [\n \"run\",\n \"deploy\",\n config.serviceName,\n \"--source\",\n config.sourceDir,\n \"--project\",\n config.gcpProject,\n \"--region\",\n config.region,\n ...(config.requireAuth ? [] : [\"--allow-unauthenticated\"]),\n \"--quiet\",\n \"--format=json\",\n ];\n\n // Append env vars if any are specified\n const envVarsString = buildEnvVarsString(config.envVars);\n if (envVarsString) {\n args.push(\"--set-env-vars\", envVarsString);\n }\n\n // Execute deployment\n callbacks?.onDeploying?.();\n\n try {\n const { stdout } = await execa(\"gcloud\", args, {\n stdio: \"pipe\",\n timeout: 300_000,\n });\n\n // Attempt to parse JSON output from the deploy command\n let url: string | undefined;\n\n try {\n const deployOutput = JSON.parse(stdout) as {\n status?: { url?: string };\n };\n url = deployOutput.status?.url;\n } catch {\n // JSON parse failed — older gcloud versions may not return JSON.\n // Fall through to the fallback below.\n }\n\n // If we couldn't extract the URL from deploy output, describe the service\n if (!url) {\n const fallbackResult = await getServiceUrl(\n config.serviceName,\n config.gcpProject,\n config.region,\n );\n\n if (!fallbackResult.success) {\n return failure(fallbackResult.error);\n }\n\n url = fallbackResult.value;\n }\n\n const result: CloudRunResult = {\n url,\n serviceName: config.serviceName,\n region: config.region,\n gcpProject: config.gcpProject,\n };\n\n callbacks?.onDeployComplete?.(url);\n\n return success(result);\n } catch (err) {\n const execaError = err as { stderr?: string; message?: string };\n let details = execaError.stderr ?? execaError.message ?? String(err);\n\n // Best-effort: append recent Cloud Build logs for easier debugging\n const buildLogs = await fetchRecentBuildLogs(\n config.gcpProject,\n config.region,\n );\n if (buildLogs) {\n details +=\n \"\\n\\n── Recent Cloud Build logs ─────────────────────\\n\" + buildLogs;\n }\n\n return failure(\n createCloudRunError(CLOUD_RUN_ERRORS.DEPLOY_FAILED, details),\n );\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Service deletion\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Delete a Cloud Run service using `gcloud run services delete`.\n */\nexport async function deleteCloudRunService(config: {\n readonly serviceName: string;\n readonly gcpProject: string;\n readonly region: string;\n}): Promise<Result<void, CloudRunError>> {\n try {\n await execa(\n \"gcloud\",\n [\n \"run\",\n \"services\",\n \"delete\",\n config.serviceName,\n \"--region\",\n config.region,\n \"--project\",\n config.gcpProject,\n \"--quiet\",\n ],\n { stdio: \"pipe\", timeout: 60_000 },\n );\n return success(undefined);\n } catch (err) {\n const execaError = err as { stderr?: string; message?: string };\n return failure(\n createCloudRunError(\n CLOUD_RUN_ERRORS.SERVICE_DELETION_FAILED,\n execaError.stderr ?? execaError.message ?? String(err),\n ),\n );\n }\n}\n","/**\n * Fluid API validation utilities\n *\n * This module provides functions for validating Fluid API keys\n * before deploying or destroying infrastructure. Ensures the deployer\n * has a valid company token before touching Cloud Run or Turso resources.\n */\n\nimport type { CliError } from \"@fluid-app/fluid-cli\";\nimport { type Result, success, failure } from \"@fluid-app/fluid-cli\";\nimport prompts from \"prompts\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst FLUID_API_BASE = \"https://api.fluid.app\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error constants - structured error definitions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const FLUID_API_ERROR = {\n MISSING_API_KEY: {\n code: \"MISSING_API_KEY\",\n message: \"FLUID_COMPANY_API_KEY is not set\",\n },\n INVALID_API_KEY: {\n code: \"INVALID_API_KEY\",\n message: \"FLUID_COMPANY_API_KEY is invalid or expired\",\n },\n API_UNREACHABLE: {\n code: \"API_UNREACHABLE\",\n message: \"Could not reach the Fluid API\",\n },\n} as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Fluid API error codes derived from error constants\n */\nexport type FluidApiErrorCode =\n (typeof FLUID_API_ERROR)[keyof typeof FLUID_API_ERROR][\"code\"];\n\n/**\n * Structured Fluid API error with code for pattern matching\n */\nexport interface FluidApiError extends CliError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\n/**\n * Create a Fluid API error from a constant and optional details\n */\nfunction createFluidApiError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): FluidApiError {\n return { code: template.code, message: template.message, details };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validated Fluid company info returned on successful API key check\n */\nexport interface FluidCompany {\n readonly name: string;\n readonly apiKey: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API key resolution\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve and validate the Fluid API key.\n *\n * Priority:\n * 1. `apiKeyOverride` parameter (from --fluid-company-api-key flag)\n * 2. FLUID_COMPANY_API_KEY environment variable\n * 3. Interactive hidden-input prompt\n * 4. Fail with instructions if all sources exhausted\n *\n * Once resolved, validates against the Fluid API (GET /api/company/v1/companies/me).\n *\n * @param apiKeyOverride - Optional API key from CLI flag (skips env + prompt)\n */\nexport async function resolveFluidApiKey(\n apiKeyOverride?: string,\n): Promise<Result<FluidCompany, FluidApiError>> {\n // ── Source 1: CLI flag ──────────────────────────────────────────────────\n if (apiKeyOverride) {\n return validateFluidApiKey(apiKeyOverride);\n }\n\n // ── Source 2: Environment variable ──────────────────────────────────────\n const envKey = process.env.FLUID_COMPANY_API_KEY;\n if (envKey) {\n return validateFluidApiKey(envKey);\n }\n\n // ── Source 3: Interactive prompt ────────────────────────────────────────\n const { apiKey } = await prompts({\n type: \"password\",\n name: \"apiKey\",\n message: \"Enter your Fluid company API key (FLUID_COMPANY_API_KEY)\",\n });\n\n if (!apiKey) {\n return failure(\n createFluidApiError(\n FLUID_API_ERROR.MISSING_API_KEY,\n \"Set FLUID_COMPANY_API_KEY in your .env file or pass --fluid-company-api-key <key>.\",\n ),\n );\n }\n\n return validateFluidApiKey(apiKey);\n}\n\n/**\n * Validate a Fluid API key by calling the companies/me endpoint.\n *\n * - 200 → extract company name, return success\n * - 401/403 → invalid or expired key\n * - Network error → API unreachable\n */\nexport async function validateFluidApiKey(\n apiKey: string,\n): Promise<Result<FluidCompany, FluidApiError>> {\n try {\n const response = await fetch(\n `${FLUID_API_BASE}/api/company/v1/companies/me`,\n {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n data: { company: { name: string } };\n };\n return success({ name: data.data.company.name, apiKey });\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createFluidApiError(\n FLUID_API_ERROR.INVALID_API_KEY,\n `HTTP ${response.status}: Check that your FLUID_COMPANY_API_KEY is a valid, non-expired company token.`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createFluidApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n createFluidApiError(FLUID_API_ERROR.API_UNREACHABLE, message),\n );\n }\n}\n","import fs from \"fs-extra\";\nimport path from \"path\";\n\n/**\n * Read project name from package.json\n */\nexport async function getProjectName(cwd: string): Promise<string | undefined> {\n const packageJsonPath = path.join(cwd, \"package.json\");\n if (await fs.pathExists(packageJsonPath)) {\n const packageJson = await fs.readJson(packageJsonPath);\n return packageJson.name;\n }\n return undefined;\n}\n\n/**\n * Sanitize a project name into a valid Cloud Run service name.\n * Lowercase, alphanumeric, and hyphens only.\n */\nexport function sanitizeServiceName(projectName: string): string {\n return projectName\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/--+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n","/**\n * Navigation extraction utility\n *\n * Extracts the `navigation` export from a project's navigation.config.ts\n * by writing a minimal wrapper script that imports from navigation.config.ts\n * and serializes the result to stdout, then running it with tsx.\n *\n * This avoids transitive dependency resolution — navigation.config.ts must\n * be self-contained static data with no import statements.\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { type Result, success, failure } from \"@fluid-app/fluid-cli\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** A code-defined navigation item from navigation.config.ts */\nexport interface CodeNavigationItem {\n readonly label: string;\n readonly slug?: string;\n readonly icon?: string;\n readonly children?: CodeNavigationItem[];\n}\n\nexport interface NavigationExtractionError {\n readonly code: \"EXTRACTION_FAILED\" | \"INVALID_FORMAT\";\n readonly message: string;\n readonly details?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Extraction\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst EXTRACT_FILENAME = \"__fluid_extract_nav.ts\";\n\n/**\n * Extract the `navigation` export from a project's navigation.config.ts.\n *\n * Reads the config file, validates it contains no import statements,\n * writes a minimal wrapper that imports and serializes navigation,\n * and runs it with tsx. This avoids needing project dependencies\n * (React, etc.) to be installed.\n *\n * The temp file is always cleaned up.\n * Returns null if no navigation export exists.\n *\n * @param projectDir - The project root directory containing src/navigation.config.ts\n */\nexport async function extractNavigation(\n projectDir: string,\n): Promise<Result<CodeNavigationItem[] | null, NavigationExtractionError>> {\n const configPath = path.join(projectDir, \"src\", \"navigation.config.ts\");\n const extractFile = path.join(projectDir, EXTRACT_FILENAME);\n\n try {\n // Read the original config\n const configSource = await fs.readFile(configPath, \"utf-8\");\n\n // Check if navigation export exists before spawning a process\n if (!/export\\s+(const|let)\\s+navigation\\s*=/.test(configSource)) {\n return success(null);\n }\n\n // Guard: navigation.config.ts must not contain import statements\n if (/^\\s*import\\s/m.test(configSource)) {\n return failure({\n code: \"EXTRACTION_FAILED\",\n message:\n \"navigation.config.ts must not contain import statements — it should only export static data\",\n details:\n \"Move all imports to portal.config.ts. navigation.config.ts must be self-contained.\",\n });\n }\n\n // Write a minimal wrapper that imports and serializes the navigation\n const wrapperScript = [\n `import { navigation } from \"./src/navigation.config.ts\";`,\n `console.log(JSON.stringify(navigation ?? null));`,\n ].join(\"\\n\");\n await fs.writeFile(extractFile, wrapperScript, \"utf-8\");\n\n const result = await execa(\"npx\", [\"tsx\", EXTRACT_FILENAME], {\n cwd: projectDir,\n stdio: \"pipe\",\n env: {\n ...process.env,\n NODE_ENV: \"production\",\n },\n });\n\n const output = result.stdout.trim();\n\n if (!output || output === \"null\") {\n return success(null);\n }\n\n try {\n const parsed = JSON.parse(output) as unknown;\n if (!Array.isArray(parsed)) {\n return failure({\n code: \"INVALID_FORMAT\",\n message: \"navigation export is not an array\",\n details: `Expected an array, got: ${typeof parsed}`,\n });\n }\n return success(parsed as CodeNavigationItem[]);\n } catch {\n return failure({\n code: \"INVALID_FORMAT\",\n message: \"Failed to parse navigation output as JSON\",\n details: output.slice(0, 200),\n });\n }\n } catch (err) {\n const error = err as { stderr?: string; message?: string };\n return failure({\n code: \"EXTRACTION_FAILED\",\n message: \"Failed to extract navigation from navigation.config.ts\",\n details: error.stderr ?? error.message ?? String(err),\n });\n } finally {\n // Always clean up the temp file\n await fs.remove(extractFile).catch(() => {});\n }\n}\n","/**\n * Navigation sync utility\n *\n * Reconciles code-defined navigation items from portal.config.ts\n * against the Fluid OS API. Only manages items with source: \"code\",\n * leaving user-created and system items untouched.\n */\n\nimport { type Result, success, failure } from \"@fluid-app/fluid-cli\";\nimport type { CodeNavigationItem } from \"./extract-navigation.js\";\nimport type { components } from \"@fluid-app/fluidos-api-client\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ApiNavigationItem = components[\"schemas\"][\"FluidOSNavigationItem\"];\n\nexport interface NavigationSyncError {\n readonly code:\n | \"NO_DEFINITION\"\n | \"NO_NAVIGATION\"\n | \"API_ERROR\"\n | \"SYNC_FAILED\";\n readonly message: string;\n readonly details?: string;\n}\n\nexport interface NavigationSyncResult {\n readonly created: number;\n readonly updated: number;\n readonly deleted: number;\n}\n\ninterface ApiContext {\n readonly apiBase: string;\n readonly apiKey: string;\n readonly definitionId: number;\n readonly navigationId: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API helpers (raw fetch, following fluid-api.ts pattern)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst FLUID_API_BASE = \"https://api.fluid.app\";\n\nasync function apiGet<T>(apiKey: string, path: string): Promise<T> {\n const response = await fetch(`${FLUID_API_BASE}${path}`, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`GET ${path} failed (${response.status}): ${body}`);\n }\n return response.json() as Promise<T>;\n}\n\nasync function apiPost<T>(\n apiKey: string,\n path: string,\n body: unknown,\n): Promise<T> {\n const response = await fetch(`${FLUID_API_BASE}${path}`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`POST ${path} failed (${response.status}): ${text}`);\n }\n return response.json() as Promise<T>;\n}\n\nasync function apiPut<T>(\n apiKey: string,\n path: string,\n body: unknown,\n): Promise<T> {\n const response = await fetch(`${FLUID_API_BASE}${path}`, {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`PUT ${path} failed (${response.status}): ${text}`);\n }\n return response.json() as Promise<T>;\n}\n\nasync function apiDelete(apiKey: string, path: string): Promise<void> {\n const response = await fetch(`${FLUID_API_BASE}${path}`, {\n method: \"DELETE\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`DELETE ${path} failed (${response.status}): ${text}`);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Discovery: find active definition and web navigation\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface DefinitionListResponse {\n definitions: Array<{ id: number; active: boolean }>;\n}\n\ninterface NavigationListResponse {\n navigations: Array<{ id: number; name: string }>;\n}\n\ninterface NavigationItemsResponse {\n navigation_items: ApiNavigationItem[];\n}\n\nasync function discoverContext(\n apiKey: string,\n): Promise<Result<Omit<ApiContext, \"apiBase\">, NavigationSyncError>> {\n // Find active definition\n let definitionId: number;\n try {\n const defs = await apiGet<DefinitionListResponse>(\n apiKey,\n \"/api/company/fluid_os/definitions\",\n );\n const active = defs.definitions.find((d) => d.active);\n if (!active) {\n return failure({\n code: \"NO_DEFINITION\",\n message: \"No active Fluid OS definition found\",\n details:\n \"Create and activate a definition in the Fluid admin dashboard first.\",\n });\n }\n definitionId = active.id;\n } catch (err) {\n return failure({\n code: \"API_ERROR\",\n message: \"Failed to fetch Fluid OS definitions\",\n details: err instanceof Error ? err.message : String(err),\n });\n }\n\n // Find web navigation\n let navigationId: number;\n try {\n const navs = await apiGet<NavigationListResponse>(\n apiKey,\n `/api/company/fluid_os/definitions/${definitionId}/navigations`,\n );\n // Prefer \"Web Navigation\" or first available\n const webNav =\n navs.navigations.find((n) => n.name.toLowerCase().includes(\"web\")) ??\n navs.navigations[0];\n if (!webNav) {\n return failure({\n code: \"NO_NAVIGATION\",\n message: \"No navigation found for the active definition\",\n details:\n \"The active definition has no navigations. Create one in the Fluid admin dashboard.\",\n });\n }\n navigationId = webNav.id;\n } catch (err) {\n return failure({\n code: \"API_ERROR\",\n message: \"Failed to fetch navigations\",\n details: err instanceof Error ? err.message : String(err),\n });\n }\n\n return success({ apiKey, definitionId, navigationId });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sync algorithm\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build a lookup key for matching code items to API items.\n * Items with a slug are keyed by slug; headers (no slug) are keyed by \"header:{label}\".\n */\nfunction itemKey(item: {\n slug?: string | null;\n label?: string | null;\n}): string {\n if (item.slug) return item.slug;\n return `header:${item.label ?? \"\"}`;\n}\n\n/**\n * Recursively delete an API navigation item and all its source: \"code\" descendants\n * in post-order (deepest children first, then the item itself).\n */\nasync function deleteItemRecursive(\n apiKey: string,\n basePath: string,\n item: ApiNavigationItem,\n stats: { deleted: number },\n): Promise<void> {\n const codeChildren = (item.children ?? []).filter((c) => c.source === \"code\");\n for (const child of codeChildren) {\n await deleteItemRecursive(apiKey, basePath, child, stats);\n }\n await apiDelete(apiKey, `${basePath}/${item.id}`);\n stats.deleted++;\n}\n\n/**\n * Sync a level of code-defined navigation items against existing API items.\n * Recurses for children.\n */\nasync function syncLevel(\n ctx: ApiContext,\n codeItems: CodeNavigationItem[],\n existingItems: ApiNavigationItem[],\n parentId: number | null,\n stats: { created: number; updated: number; deleted: number },\n): Promise<void> {\n // Build a map of existing code-sourced items by key\n const existingByKey = new Map<string, ApiNavigationItem>();\n for (const item of existingItems) {\n if (item.source === \"code\") {\n existingByKey.set(itemKey(item), item);\n }\n }\n\n const basePath = `/api/company/fluid_os/definitions/${ctx.definitionId}/navigations/${ctx.navigationId}/navigation_items`;\n const matched = new Set<string>();\n\n // Walk code-defined items\n for (let i = 0; i < codeItems.length; i++) {\n const codeItem = codeItems[i]!;\n const key = itemKey(codeItem);\n const existing = existingByKey.get(key);\n matched.add(key);\n\n if (existing) {\n // UPDATE: check if anything changed\n const needsUpdate =\n existing.label !== codeItem.label ||\n existing.icon !== (codeItem.icon ?? null) ||\n existing.position !== i + 1;\n\n if (needsUpdate) {\n await apiPut(ctx.apiKey, `${basePath}/${existing.id}`, {\n navigation_item: {\n label: codeItem.label,\n icon: codeItem.icon ?? null,\n position: i + 1,\n },\n });\n stats.updated++;\n }\n\n // Recurse for children\n if (codeItem.children?.length) {\n await syncLevel(\n ctx,\n codeItem.children,\n existing.children ?? [],\n existing.id,\n stats,\n );\n }\n } else {\n // CREATE\n const response = await apiPost<{\n navigation_item: ApiNavigationItem;\n }>(ctx.apiKey, basePath, {\n navigation_item: {\n label: codeItem.label,\n slug: codeItem.slug ?? null,\n icon: codeItem.icon ?? null,\n position: i + 1,\n parent_id: parentId,\n source: \"code\",\n },\n });\n stats.created++;\n\n // Recurse for children of newly created item\n if (codeItem.children?.length) {\n const newId = response.navigation_item.id;\n await syncLevel(ctx, codeItem.children, [], newId, stats);\n }\n }\n }\n\n // DELETE: remaining unmatched source: \"code\" items (post-order: children before parent)\n for (const [key, item] of existingByKey) {\n if (!matched.has(key)) {\n await deleteItemRecursive(ctx.apiKey, basePath, item, stats);\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public API\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Sync code-defined navigation items to the Fluid OS API.\n *\n * @param apiKey - Valid Fluid company API key\n * @param codeItems - Navigation items from portal.config.ts\n */\nexport async function syncNavigation(\n apiKey: string,\n codeItems: CodeNavigationItem[],\n): Promise<Result<NavigationSyncResult, NavigationSyncError>> {\n // Discover active definition and web navigation\n const contextResult = await discoverContext(apiKey);\n if (!contextResult.success) {\n return contextResult;\n }\n\n const ctx: ApiContext = {\n apiBase: FLUID_API_BASE,\n ...contextResult.value,\n };\n\n // Fetch existing navigation items\n let existingItems: ApiNavigationItem[];\n try {\n const response = await apiGet<NavigationItemsResponse>(\n ctx.apiKey,\n `/api/company/fluid_os/definitions/${ctx.definitionId}/navigations/${ctx.navigationId}/navigation_items`,\n );\n existingItems = response.navigation_items;\n } catch (err) {\n return failure({\n code: \"API_ERROR\",\n message: \"Failed to fetch existing navigation items\",\n details: err instanceof Error ? err.message : String(err),\n });\n }\n\n // Run the sync\n const stats = { created: 0, updated: 0, deleted: 0 };\n try {\n await syncLevel(ctx, codeItems, existingItems, null, stats);\n } catch (err) {\n return failure({\n code: \"SYNC_FAILED\",\n message: \"Navigation sync failed during reconciliation\",\n details: err instanceof Error ? err.message : String(err),\n });\n }\n\n return success(stats);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { execa } from \"execa\";\nimport { config as loadDotenv } from \"dotenv\";\nimport type { DeployOptions } from \"../types.js\";\nimport { resolveTursoConfig, provisionDatabase } from \"../utils/turso.js\";\nimport {\n validateGcloudInstalled,\n validateGcloudAuth,\n getGcpProject,\n deployToCloudRun,\n} from \"../utils/cloud-run.js\";\nimport { getErrorMessage } from \"@fluid-app/fluid-cli\";\nimport { resolveFluidApiKey } from \"../utils/fluid-api.js\";\nimport { getRunCommand } from \"../utils/package-manager.js\";\nimport { getProjectName, sanitizeServiceName } from \"../utils/project.js\";\nimport { extractNavigation } from \"../utils/extract-navigation.js\";\nimport { syncNavigation } from \"../utils/navigation-sync.js\";\n\n/**\n * Detect if the project is a fullstack template (has server entry)\n */\nasync function isFullstackProject(cwd: string): Promise<boolean> {\n return fs.pathExists(path.join(cwd, \"src\", \"server\", \"index.ts\"));\n}\n\nexport const deployCommand: Command = new Command(\"deploy\")\n .description(\"Deploy the fullstack application to Cloud Run + Turso\")\n .option(\"--region <region>\", \"Cloud Run region\", \"us-central1\")\n .option(\"--gcp-project <id>\", \"GCP project ID (default: from gcloud config)\")\n .option(\n \"-p, --project <name>\",\n \"Service name override (default: from package.json)\",\n )\n .option(\n \"--db-region <location>\",\n \"Turso database group location\",\n \"aws-us-east-1\",\n )\n .option(\n \"--require-auth\",\n \"Require IAM authentication for the Cloud Run service (default: public)\",\n )\n .option(\n \"--migrate\",\n \"Run database migrations (db:push) after successful deploy\",\n )\n .option(\n \"--skip-local-build\",\n \"Skip the local Docker build check before deploying\",\n )\n .option(\n \"--turso-org <slug>\",\n \"Turso organization slug (skips interactive org selection)\",\n )\n .option(\n \"--fluid-company-api-key <key>\",\n \"Fluid company API key (skips env var lookup and prompt)\",\n )\n .option(\"--skip-nav-sync\", \"Skip navigation sync from portal.config.ts\")\n .action(async (options: DeployOptions) => {\n const cwd = process.cwd();\n\n // Load .env from the project directory (does not override existing env vars)\n loadDotenv({ path: path.join(cwd, \".env\") });\n\n console.log();\n console.log(\n chalk.blue.bold(\"Fluid Deploy\") + chalk.gray(\" (Cloud Run + Turso)\"),\n );\n console.log();\n\n // ── Check project type ──────────────────────────────────────────────\n if (!(await isFullstackProject(cwd))) {\n console.log(\n chalk.red(\"Error:\") + \" This project is not a fullstack template.\",\n );\n console.log();\n console.log(\n chalk.yellow(\"fluid deploy\") +\n \" only supports fullstack projects with a Hono API server.\",\n );\n console.log();\n console.log(\n \"For static sites (starter template), deploy the \" +\n chalk.cyan(\"dist/\") +\n \" folder to:\",\n );\n console.log(\n \" - Firebase Hosting: \" +\n chalk.gray(\"https://firebase.google.com/docs/hosting\"),\n );\n console.log(\n \" - Cloud Storage: \" +\n chalk.gray(\n \"https://cloud.google.com/storage/docs/hosting-static-website\",\n ),\n );\n console.log(\" - Vercel: \" + chalk.gray(\"https://vercel.com\"));\n console.log(\" - Netlify: \" + chalk.gray(\"https://netlify.com\"));\n console.log();\n process.exit(1);\n }\n\n // ── Validate Fluid API key ────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Validating Fluid API key...\");\n const fluidResult = await resolveFluidApiKey(options.fluidCompanyApiKey);\n if (!fluidResult.success) {\n spinner.fail(\"Fluid API key validation failed\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + fluidResult.error.message);\n if (fluidResult.error.details) {\n console.log();\n console.log(fluidResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n spinner.succeed(`Fluid company: ${chalk.cyan(fluidResult.value.name)}`);\n\n // ── Check for Dockerfile ────────────────────────────────────────────\n if (!(await fs.pathExists(path.join(cwd, \"Dockerfile\")))) {\n console.log(\n chalk.red(\"Error:\") + \" No Dockerfile found in current directory.\",\n );\n console.log();\n console.log(\n \"Fullstack projects created with the latest template include a Dockerfile.\",\n );\n console.log(\n \"If you upgraded from an older template, add a Dockerfile to your project.\",\n );\n console.log();\n process.exit(1);\n }\n\n // ── Validate gcloud CLI ─────────────────────────────────────────────\n spinner.start(\"Checking gcloud CLI...\");\n const gcloudResult = await validateGcloudInstalled();\n if (!gcloudResult.success) {\n spinner.fail(\"gcloud CLI not found\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + gcloudResult.error.message);\n console.log();\n console.log(\n \"Install the Google Cloud SDK: \" +\n chalk.cyan(\"https://cloud.google.com/sdk/docs/install\"),\n );\n console.log();\n process.exit(1);\n }\n\n const authResult = await validateGcloudAuth();\n if (!authResult.success) {\n spinner.fail(\"gcloud not authenticated\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + authResult.error.message);\n console.log();\n console.log(\n \"Run \" + chalk.cyan(\"gcloud auth login\") + \" to authenticate.\",\n );\n console.log();\n process.exit(1);\n }\n spinner.succeed(\"gcloud CLI ready\");\n\n // ── Resolve GCP project ─────────────────────────────────────────────\n let gcpProject = options.gcpProject;\n if (!gcpProject) {\n spinner.start(\"Detecting GCP project...\");\n const projectResult = await getGcpProject();\n if (!projectResult.success) {\n spinner.fail(\"No GCP project configured\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + projectResult.error.message);\n console.log();\n console.log(\"Either:\");\n console.log(\n \" - Run \" + chalk.cyan(\"gcloud config set project PROJECT_ID\"),\n );\n console.log(\n \" - Use the \" + chalk.cyan(\"--gcp-project <id>\") + \" flag\",\n );\n console.log();\n process.exit(1);\n }\n gcpProject = projectResult.value;\n spinner.succeed(`GCP project: ${chalk.cyan(gcpProject)}`);\n }\n\n // ── Resolve Turso credentials ──────────────────────────────────────\n spinner.start(\"Resolving Turso credentials...\");\n const tursoConfigResult = await resolveTursoConfig(options.tursoOrg);\n if (!tursoConfigResult.success) {\n spinner.fail(\"Turso credentials not found\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + tursoConfigResult.error.message);\n if (tursoConfigResult.error.details) {\n console.log();\n console.log(tursoConfigResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n const tursoConfig = tursoConfigResult.value;\n const sourceLabel =\n tursoConfig.source === \"env\" ? \"environment variables\" : \"Turso CLI\";\n spinner.succeed(`Turso: authenticated via ${sourceLabel}`);\n\n // ── Get project/service name ────────────────────────────────────────\n const projectName = options.project ?? (await getProjectName(cwd));\n if (!projectName) {\n console.log(chalk.red(\"Error:\") + \" Could not determine project name.\");\n console.log();\n console.log(\"Either:\");\n console.log(\" - Add a \" + chalk.cyan(\"name\") + \" field to package.json\");\n console.log(\" - Use the \" + chalk.cyan(\"--project <name>\") + \" flag\");\n console.log();\n process.exit(1);\n }\n\n const serviceName = sanitizeServiceName(projectName);\n if (!serviceName) {\n console.log(\n chalk.red(\"Error:\") +\n \" Project name sanitizes to an empty service name.\",\n );\n console.log();\n console.log(\n \"Use the \" +\n chalk.cyan(\"--project <name>\") +\n \" flag to provide a valid service name.\",\n );\n console.log();\n process.exit(1);\n }\n\n const region = options.region ?? \"us-central1\";\n\n console.log();\n console.log(chalk.gray(\"Service: \") + chalk.white(serviceName));\n console.log(chalk.gray(\"Region: \") + chalk.white(region));\n console.log(chalk.gray(\"GCP Project: \") + chalk.white(gcpProject));\n console.log(\n chalk.gray(\"Turso Org: \") +\n chalk.white(tursoConfig.org) +\n chalk.gray(` (via ${sourceLabel})`),\n );\n console.log();\n\n // ── Local Docker build check ────────────────────────────────────────\n if (!options.skipLocalBuild) {\n let dockerAvailable = false;\n try {\n await execa(\"docker\", [\"--version\"], { stdio: \"pipe\" });\n dockerAvailable = true;\n } catch {\n spinner.warn(\"Docker not found — skipping local build check\");\n }\n\n if (dockerAvailable) {\n spinner.start(\"Running local Docker build check...\");\n try {\n await execa(\"docker\", [\"build\", \"-t\", `${serviceName}-check`, \".\"], {\n cwd,\n stdio: \"pipe\",\n });\n spinner.succeed(\"Local Docker build passed\");\n } catch (buildErr) {\n spinner.fail(\"Local Docker build failed\");\n const buildError = buildErr as { stderr?: string };\n if (buildError.stderr) {\n console.log();\n console.log(chalk.gray(buildError.stderr));\n }\n console.log();\n console.log(\n chalk.red(\"Fix the Dockerfile errors above before deploying.\"),\n );\n console.log(\n chalk.gray(\"Tip: Use --skip-local-build to bypass this check.\"),\n );\n console.log();\n process.exit(1);\n }\n console.log();\n }\n }\n\n // ── Provision Turso database ────────────────────────────────────────\n try {\n const dbResult = await provisionDatabase(\n tursoConfig,\n serviceName,\n options.dbRegion,\n {\n onGroupCreating: () => {\n spinner.start(\"Ensuring Turso database group...\");\n },\n onGroupReady: () => {\n spinner.succeed(\"Database group ready\");\n },\n onDatabaseCreating: (name) => {\n spinner.start(`Creating database \"${name}\"...`);\n },\n onDatabaseReady: (name) => {\n spinner.succeed(`Database \"${name}\" ready`);\n },\n onTokenCreating: () => {\n spinner.start(\"Creating database auth token...\");\n },\n onTokenReady: () => {\n spinner.succeed(\"Auth token created\");\n },\n },\n );\n\n if (!dbResult.success) {\n spinner.fail(\"Turso provisioning failed\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + dbResult.error.message);\n if (dbResult.error.details) {\n console.log(chalk.gray(\"Details: \") + dbResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n\n const database = dbResult.value;\n console.log();\n console.log(chalk.gray(\"Database URL: \") + chalk.cyan(database.url));\n console.log();\n\n // ── Deploy to Cloud Run ───────────────────────────────────────────\n const deployResult = await deployToCloudRun(\n {\n gcpProject,\n region,\n serviceName,\n sourceDir: cwd,\n requireAuth: options.requireAuth,\n envVars: {\n DATABASE_URL: database.url,\n DATABASE_AUTH_TOKEN: database.authToken,\n NODE_ENV: \"production\",\n FLUID_COMPANY_API_KEY: fluidResult.value.apiKey,\n },\n },\n {\n onValidating: () => {\n spinner.start(\"Preparing Cloud Run deployment...\");\n },\n onDeploying: () => {\n spinner.text =\n \"Deploying to Cloud Run (this may take 2-5 minutes)...\";\n },\n onDeployComplete: () => {\n spinner.succeed(\"Deployed to Cloud Run\");\n },\n },\n );\n\n if (!deployResult.success) {\n spinner.fail(\"Cloud Run deployment failed\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + deployResult.error.message);\n if (deployResult.error.details) {\n console.log(chalk.gray(\"Details: \") + deployResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.green.bold(\"Deployed successfully!\"));\n console.log();\n console.log(\n chalk.gray(\"Service URL: \") + chalk.cyan(deployResult.value.url),\n );\n console.log(\n chalk.gray(\"Database: \") + chalk.cyan(database.databaseName),\n );\n console.log(\n chalk.gray(\"Region: \") + chalk.cyan(deployResult.value.region),\n );\n console.log(\n chalk.gray(\"GCP Project: \") +\n chalk.cyan(deployResult.value.gcpProject),\n );\n console.log();\n\n // ── Navigation sync ─────────────────────────────────────────────\n if (!options.skipNavSync) {\n const configPath = path.join(cwd, \"src\", \"navigation.config.ts\");\n if (await fs.pathExists(configPath)) {\n const navSpinner = ora(\n \"Extracting navigation from navigation.config.ts...\",\n ).start();\n const extractResult = await extractNavigation(cwd);\n\n if (extractResult.success && extractResult.value != null) {\n const navItems = extractResult.value;\n navSpinner.text = `Syncing ${navItems.length} navigation item(s)...`;\n\n const syncResult = await syncNavigation(\n fluidResult.value.apiKey,\n navItems,\n );\n\n if (syncResult.success) {\n const { created, updated, deleted } = syncResult.value;\n const parts: string[] = [];\n if (created > 0) parts.push(`${created} created`);\n if (updated > 0) parts.push(`${updated} updated`);\n if (deleted > 0) parts.push(`${deleted} deleted`);\n if (parts.length > 0) {\n navSpinner.succeed(`Navigation synced (${parts.join(\", \")})`);\n } else {\n navSpinner.succeed(\"Navigation up to date\");\n }\n } else {\n navSpinner.warn(\"Navigation sync failed (deploy succeeded)\");\n console.log(\n chalk.yellow(\" Warning: \") + syncResult.error.message,\n );\n if (syncResult.error.details) {\n console.log(\n chalk.gray(\" Details: \") + syncResult.error.details,\n );\n }\n }\n } else if (extractResult.success && extractResult.value == null) {\n navSpinner.info(\n \"No navigation export found in navigation.config.ts — skipping sync\",\n );\n } else {\n navSpinner.warn(\"Could not extract navigation (deploy succeeded)\");\n if (!extractResult.success && extractResult.error.details) {\n console.log(\n chalk.gray(\" Details: \") + extractResult.error.details,\n );\n }\n }\n }\n }\n\n // ── Health check ──────────────────────────────────────────────────\n const serviceUrl = deployResult.value.url;\n const healthSpinner = ora(\"Running health check...\").start();\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30000);\n const healthRes = await fetch(`${serviceUrl}/api/health`, {\n signal: controller.signal,\n });\n clearTimeout(timeout);\n if (healthRes.ok) {\n healthSpinner.succeed(\"Health check passed\");\n } else {\n healthSpinner.warn(\n \"Health check returned non-200 (service may still be starting)\",\n );\n }\n } catch {\n healthSpinner.warn(\n \"Health check timed out (cold start may take a moment)\",\n );\n }\n\n // ── Migration ─────────────────────────────────────────────────────\n const shouldMigrate = options.migrate || database.isNew;\n if (shouldMigrate) {\n if (database.isNew && !options.migrate) {\n console.log();\n console.log(\n chalk.blue(\"New database\") +\n \" — running migrations automatically...\",\n );\n }\n const migrateCmd = getRunCommand(\"db:push\");\n const migrateSpinner = ora(\n `Running migrations (${migrateCmd})...`,\n ).start();\n try {\n const [pmBin, ...pmArgs] = migrateCmd.split(\" \");\n await execa(pmBin!, pmArgs, {\n cwd,\n stdio: \"pipe\",\n env: {\n ...process.env,\n DATABASE_URL: database.url,\n DATABASE_AUTH_TOKEN: database.authToken,\n },\n });\n migrateSpinner.succeed(\"Database migrations complete\");\n } catch (migrateErr) {\n const migrateError = migrateErr as {\n stderr?: string;\n message?: string;\n };\n migrateSpinner.warn(\"Database migration failed (deploy succeeded)\");\n console.log();\n console.log(\n chalk.yellow(\"Migration error:\") +\n \" \" +\n (migrateError.stderr ??\n migrateError.message ??\n String(migrateErr)),\n );\n console.log();\n console.log(\"Run migrations manually:\");\n console.log(\n ` DATABASE_URL=${database.url} DATABASE_AUTH_TOKEN=<token> ${migrateCmd}`,\n );\n }\n } else {\n console.log();\n console.log(\n chalk.yellow(\"Important:\") +\n \" Run database migrations against your production database:\",\n );\n console.log(\n ` DATABASE_URL=${database.url} DATABASE_AUTH_TOKEN=<token> pnpm db:push`,\n );\n console.log(\n chalk.gray(\"Tip: Use --migrate to run migrations automatically.\"),\n );\n }\n console.log();\n } catch (error) {\n spinner.fail(\"Deployment failed\");\n console.log();\n const errorMessage = getErrorMessage(error);\n console.log(chalk.red(\"Error:\") + \" \" + errorMessage);\n console.log();\n process.exit(1);\n }\n });\n\nexport function registerDeployCommand(ctx: PluginContext): void {\n ctx.program.addCommand(deployCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { config as loadDotenv } from \"dotenv\";\nimport path from \"path\";\nimport prompts from \"prompts\";\nimport type { DestroyOptions } from \"../types.js\";\nimport { resolveTursoConfig, deleteDatabase } from \"../utils/turso.js\";\nimport { resolveFluidApiKey } from \"../utils/fluid-api.js\";\nimport {\n validateGcloudInstalled,\n validateGcloudAuth,\n getGcpProject,\n deleteCloudRunService,\n} from \"../utils/cloud-run.js\";\nimport { getProjectName, sanitizeServiceName } from \"../utils/project.js\";\n\nexport const destroyCommand: Command = new Command(\"destroy\")\n .description(\"Tear down deployed Cloud Run service and Turso database\")\n .option(\"--region <region>\", \"Cloud Run region\", \"us-central1\")\n .option(\"--gcp-project <id>\", \"GCP project ID (default: from gcloud config)\")\n .option(\n \"-p, --project <name>\",\n \"Service name override (default: from package.json)\",\n )\n .option(\n \"--turso-org <slug>\",\n \"Turso organization slug (skips interactive org selection)\",\n )\n .option(\n \"--fluid-company-api-key <key>\",\n \"Fluid company API key (skips env var lookup and prompt)\",\n )\n .option(\"-y, --yes\", \"Skip confirmation prompt\")\n .action(async (options: DestroyOptions) => {\n const cwd = process.cwd();\n\n // Load .env from the project directory (does not override existing env vars)\n loadDotenv({ path: path.join(cwd, \".env\") });\n\n console.log();\n console.log(\n chalk.red.bold(\"Fluid Destroy\") + chalk.gray(\" (Cloud Run + Turso)\"),\n );\n console.log();\n\n // ── Validate Fluid API key ────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Validating Fluid API key...\");\n const fluidResult = await resolveFluidApiKey(options.fluidCompanyApiKey);\n if (!fluidResult.success) {\n spinner.fail(\"Fluid API key validation failed\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + fluidResult.error.message);\n if (fluidResult.error.details) {\n console.log();\n console.log(fluidResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n spinner.succeed(`Fluid company: ${chalk.cyan(fluidResult.value.name)}`);\n\n spinner.start(\"Checking gcloud CLI...\");\n const gcloudResult = await validateGcloudInstalled();\n if (!gcloudResult.success) {\n spinner.fail(\"gcloud CLI not found\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + gcloudResult.error.message);\n console.log();\n process.exit(1);\n }\n\n const authResult = await validateGcloudAuth();\n if (!authResult.success) {\n spinner.fail(\"gcloud not authenticated\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + authResult.error.message);\n console.log();\n process.exit(1);\n }\n spinner.succeed(\"gcloud CLI ready\");\n\n // ── Resolve GCP project ─────────────────────────────────────────────\n let gcpProject = options.gcpProject;\n if (!gcpProject) {\n spinner.start(\"Detecting GCP project...\");\n const projectResult = await getGcpProject();\n if (!projectResult.success) {\n spinner.fail(\"No GCP project configured\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + projectResult.error.message);\n console.log();\n process.exit(1);\n }\n gcpProject = projectResult.value;\n spinner.succeed(`GCP project: ${chalk.cyan(gcpProject)}`);\n }\n\n // ── Resolve Turso credentials ──────────────────────────────────────\n spinner.start(\"Resolving Turso credentials...\");\n const tursoConfigResult = await resolveTursoConfig(options.tursoOrg);\n if (!tursoConfigResult.success) {\n spinner.fail(\"Turso credentials not found\");\n console.log();\n console.log(chalk.red(\"Error:\") + \" \" + tursoConfigResult.error.message);\n if (tursoConfigResult.error.details) {\n console.log();\n console.log(tursoConfigResult.error.details);\n }\n console.log();\n process.exit(1);\n }\n const tursoConfig = tursoConfigResult.value;\n spinner.succeed(\"Turso credentials resolved\");\n\n // ── Get project/service name ────────────────────────────────────────\n const projectName = options.project ?? (await getProjectName(cwd));\n if (!projectName) {\n console.log(chalk.red(\"Error:\") + \" Could not determine project name.\");\n console.log();\n console.log(\"Either:\");\n console.log(\" - Add a \" + chalk.cyan(\"name\") + \" field to package.json\");\n console.log(\" - Use the \" + chalk.cyan(\"--project <name>\") + \" flag\");\n console.log();\n process.exit(1);\n }\n\n const serviceName = sanitizeServiceName(projectName);\n if (!serviceName) {\n console.log(\n chalk.red(\"Error:\") +\n \" Project name sanitizes to an empty service name.\",\n );\n console.log();\n console.log(\n \"Use the \" +\n chalk.cyan(\"--project <name>\") +\n \" flag to provide a valid service name.\",\n );\n console.log();\n process.exit(1);\n }\n\n const region = options.region ?? \"us-central1\";\n\n // ── Confirmation ────────────────────────────────────────────────────\n console.log();\n console.log(chalk.yellow(\"The following resources will be destroyed:\"));\n console.log();\n console.log(chalk.gray(\" Cloud Run service: \") + chalk.white(serviceName));\n console.log(chalk.gray(\" Region: \") + chalk.white(region));\n console.log(chalk.gray(\" GCP Project: \") + chalk.white(gcpProject));\n console.log(chalk.gray(\" Turso database: \") + chalk.white(serviceName));\n console.log(\n chalk.gray(\" Turso org: \") + chalk.white(tursoConfig.org),\n );\n console.log();\n\n if (!options.yes) {\n const { confirmed } = await prompts({\n type: \"confirm\",\n name: \"confirmed\",\n message: \"Are you sure you want to destroy these resources?\",\n initial: false,\n });\n\n if (!confirmed) {\n console.log();\n console.log(chalk.gray(\"Destroy cancelled.\"));\n console.log();\n return;\n }\n }\n\n // ── Delete Cloud Run service ────────────────────────────────────────\n console.log();\n spinner.start(`Deleting Cloud Run service \"${serviceName}\"...`);\n const deleteServiceResult = await deleteCloudRunService({\n serviceName,\n gcpProject,\n region,\n });\n\n if (!deleteServiceResult.success) {\n spinner.warn(\"Cloud Run service deletion failed\");\n console.log(\n chalk.yellow(\"Warning:\") + \" \" + deleteServiceResult.error.message,\n );\n if (deleteServiceResult.error.details) {\n console.log(\n chalk.gray(\"Details: \") + deleteServiceResult.error.details,\n );\n }\n } else {\n spinner.succeed(\"Cloud Run service deleted\");\n }\n\n // ── Delete Turso database ───────────────────────────────────────────\n spinner.start(`Deleting Turso database \"${serviceName}\"...`);\n const deleteDbResult = await deleteDatabase(tursoConfig, serviceName);\n\n if (!deleteDbResult.success) {\n spinner.warn(\"Turso database deletion failed\");\n console.log(\n chalk.yellow(\"Warning:\") + \" \" + deleteDbResult.error.message,\n );\n if (deleteDbResult.error.details) {\n console.log(chalk.gray(\"Details: \") + deleteDbResult.error.details);\n }\n } else {\n spinner.succeed(\"Turso database deleted\");\n }\n\n // ── Summary ─────────────────────────────────────────────────────────\n console.log();\n if (deleteServiceResult.success && deleteDbResult.success) {\n console.log(chalk.green.bold(\"All resources destroyed successfully.\"));\n } else {\n console.log(\n chalk.yellow.bold(\"Destroy completed with warnings.\") +\n \" Some resources may need manual cleanup.\",\n );\n }\n console.log();\n });\n\nexport function registerDestroyCommand(ctx: PluginContext): void {\n ctx.program.addCommand(destroyCommand);\n}\n","/**\n * @fluid-app/fluid-cli-portal\n *\n * Fluid CLI plugin for building and deploying portal applications.\n * Auto-discovered by @fluid-app/fluid-cli via the fluid-cli-* naming convention.\n */\n\nimport type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { registerCreateCommand } from \"./commands/create.js\";\nimport { registerDevCommand } from \"./commands/dev.js\";\nimport { registerBuildCommand } from \"./commands/build.js\";\nimport { registerDeployCommand } from \"./commands/deploy.js\";\nimport { registerDestroyCommand } from \"./commands/destroy.js\";\n\nconst plugin: FluidPlugin = {\n name: \"fluid-cli-portal\",\n version: \"0.1.0\",\n async register(ctx: PluginContext) {\n registerCreateCommand(ctx);\n registerDevCommand(ctx);\n registerBuildCommand(ctx);\n registerDeployCommand(ctx);\n registerDestroyCommand(ctx);\n },\n};\n\nexport default plugin;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Re-exports for programmatic usage (e.g., @fluid-app/create-portal-app)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type {\n ProjectConfig,\n CreateOptions,\n DevOptions,\n BuildOptions,\n DeployOptions,\n DestroyOptions,\n TemplateVariables,\n SelectedPageTemplate,\n TemplateName,\n} from \"./types.js\";\n\nexport { TEMPLATES } from \"./types.js\";\nexport { isTemplateName } from \"./types.js\";\n\nexport {\n getInstallCommand,\n getRunCommand,\n runPackageManager,\n installDependencies,\n} from \"./utils/package-manager.js\";\n\nexport {\n getTemplatePaths,\n copyTemplate,\n directoryExists,\n fileExists,\n pathExists,\n createDirectory,\n getSdkVersion,\n readFileSafe,\n writeFileSafe,\n createDirectorySafe,\n copyTemplateSafe,\n getSdkVersionSafe,\n FILE_SYSTEM_ERRORS,\n type FileSystemErrorCode,\n type FileSystemError,\n type TemplatePaths,\n} from \"./utils/file-system.js\";\n\nexport { promptProjectConfig } from \"./utils/prompts.js\";\n\n// Expose the standalone Command for create-portal-app\nexport { createCommand } from \"./commands/create.js\";\nexport { destroyCommand } from \"./commands/destroy.js\";\n\nexport {\n validateGcloudInstalled,\n validateGcloudAuth,\n getGcpProject,\n deployToCloudRun,\n deleteCloudRunService,\n CLOUD_RUN_ERRORS,\n type CloudRunConfig,\n type CloudRunResult,\n type CloudRunError,\n type CloudRunDeployCallbacks,\n} from \"./utils/cloud-run.js\";\n\nexport {\n resolveFluidApiKey,\n validateFluidApiKey,\n FLUID_API_ERROR,\n type FluidApiError,\n type FluidApiErrorCode,\n type FluidCompany,\n} from \"./utils/fluid-api.js\";\n\nexport {\n validateTursoConfig,\n resolveTursoConfig,\n parseOrgList,\n ensureGroup,\n createDatabase,\n createDatabaseToken,\n deleteDatabase,\n provisionDatabase,\n fetchLocations,\n validateLocation,\n TURSO_ERROR,\n type TursoConfig,\n type ResolvedTursoConfig,\n type TursoConfigSource,\n type TursoOrg,\n type TursoDatabase,\n type TursoError,\n type TursoProvisionCallbacks,\n} from \"./utils/turso.js\";\n"],"mappings":"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"}