@fluid-app/fluid-cli-portal 0.1.130 → 0.1.131

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":["_currentDir","PORTAL_DIR","readPortalFile","PORTAL_SYNC_DIR","createClient","createClient","DEFAULT_OUT_DIR"],"sources":["../src/types.ts","../src/utils/prompts.ts","../src/utils/file-system.ts","../src/utils/package-manager.ts","../src/lib/git.ts","../src/utils/scaffold-github.ts","../src/commands/create.ts","../src/commands/dev.ts","../src/commands/build.ts","../../../portal/core/src/registries/container-registry.ts","../src/utils/push-validation.ts","../src/api/git-credential.ts","../src/lib/git-sync.ts","../src/api/me.ts","../../../portal/core/src/remote-widget-capability-grants.ts","../src/utils/widget-capability-grants.ts","../src/commands/push.ts","../src/commands/clone.ts","../src/commands/widget-package-publish.ts","../src/utils/widget-helpers.ts","../src/commands/widget-create.ts","../src/commands/deploy.ts","../src/commands/doctor.ts","../src/commands/version.ts","../src/commands/widget-publish.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} as const;\n\n/**\n * Union type of valid template names\n */\nexport type TemplateName = (typeof TEMPLATES)[keyof typeof TEMPLATES];\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 /** Whether to install dependencies after scaffolding */\n readonly installDeps: boolean;\n /** Selected optional page templates to include */\n readonly selectedPages: readonly SelectedPageTemplate[];\n /** CLI profile name for .fluidrc (empty string if none selected) */\n readonly profileName: string;\n}\n\n/**\n * Options for the create command (from CLI arguments)\n */\nexport interface CreateOptions {\n /** Create only the scaffold and skip dependency installation */\n readonly skipInstall?: boolean;\n /** Directory to create the project in (defaults to cwd) */\n readonly outputDir?: string;\n /** Use local monorepo packages via link: paths instead of npm versions */\n readonly local?: boolean;\n /** Explicitly use the GitHub starter instead of the bundled template. */\n readonly remoteTemplate?: 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 widget create command\n */\nexport interface WidgetCreateOptions {\n /** Category for palette grouping */\n readonly category?: string;\n}\n\n/**\n * Options for the portal deploy command\n */\nexport interface PortalDeployOptions {\n readonly environment?: string;\n readonly outDir?: string;\n readonly dryRun?: boolean;\n}\n\n/**\n * Options for the top-level widget publish command\n */\nexport interface WidgetPublishOptions {\n readonly droplet: string;\n readonly outDir?: string;\n readonly dryRun?: 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 /** portal-core link used only for --local scaffolds */\n readonly localCoreVersion?: string;\n /** CLI package version (versioned independently from the SDK) */\n readonly cliVersion: string;\n /** Portal CLI plugin version (versioned independently from CLI core) */\n readonly portalCliVersion: 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 /** CLI profile name for .fluidrc */\n readonly profileName: string;\n}\n","import { getActiveProfile, listProfileNames } from \"@fluid-app/fluid-cli\";\nimport prompts from \"prompts\";\nimport {\n type ProjectConfig,\n type CreateOptions,\n type SelectedPageTemplate,\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 // 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 // CLI profile for .fluidrc\n const existingProfiles = listProfileNames();\n if (existingProfiles.length > 0) {\n const active = getActiveProfile();\n questions.push({\n type: \"select\",\n name: \"profileName\",\n message: \"CLI profile for this project (.fluidrc)\",\n choices: existingProfiles.map((name) => ({\n title: name === active?.name ? `${name} (active)` : name,\n value: name,\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 // Non-interactive mode: if stdin is not a TTY and there are remaining\n // prompts, return safe defaults instead of hanging on interactive input.\n if (!process.stdin.isTTY && questions.length > 0) {\n return {\n name: projectName,\n installDeps: false,\n selectedPages: [],\n profileName: getActiveProfile()?.name ?? \"\",\n } satisfies ProjectConfig;\n }\n\n // Fast-path: all options provided via CLI flags, no prompts needed\n if (questions.length === 0) {\n return {\n name: projectName,\n installDeps: options.skipInstall ? false : true,\n selectedPages: [],\n profileName: getActiveProfile()?.name ?? \"\",\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 // Parse selected pages\n const selectedPages: readonly SelectedPageTemplate[] =\n response.selectedPages ?? [];\n\n return {\n name: projectName,\n installDeps: options.skipInstall ? false : (response.installDeps ?? true),\n selectedPages,\n profileName:\n (response.profileName as string | undefined) ??\n getActiveProfile()?.name ??\n \"\",\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, resolve } 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 _currentFile = fileURLToPath(import.meta.url);\nconst _currentDir = dirname(_currentFile);\n\ndeclare const __FLUID_PORTAL_SDK_VERSION__: string | undefined;\ndeclare const __FLUID_CLI_CORE_VERSION__: string | undefined;\ndeclare const __FLUID_CLI_PORTAL_VERSION__: string | undefined;\n\nconst EMBEDDED_PORTAL_SDK_VERSION =\n typeof __FLUID_PORTAL_SDK_VERSION__ === \"string\"\n ? __FLUID_PORTAL_SDK_VERSION__\n : undefined;\nconst EMBEDDED_CLI_CORE_VERSION =\n typeof __FLUID_CLI_CORE_VERSION__ === \"string\"\n ? __FLUID_CLI_CORE_VERSION__\n : undefined;\nconst EMBEDDED_CLI_PORTAL_VERSION =\n typeof __FLUID_CLI_PORTAL_VERSION__ === \"string\"\n ? __FLUID_CLI_PORTAL_VERSION__\n : undefined;\n\n/**\n * Find the package root by walking up from the current directory 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 = _currentDir;\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 */\ninterface TemplateSkillFile {\n readonly relativePath: string;\n readonly content: string;\n}\n\nasync function getSharedTemplateSkillFiles(): Promise<TemplateSkillFile[]> {\n const packageRoot = findFluidCliPackageRoot();\n const skillsRoot = join(packageRoot, \"template-skills\");\n const files = await getFiles(skillsRoot);\n\n return Promise.all(\n files.map(async (file) => ({\n relativePath: join(\"skills\", file),\n content: await readFile(join(skillsRoot, file), \"utf-8\"),\n })),\n );\n}\n\nfunction findFluidCliPackageRoot(): string {\n const workspacePackageRoot = join(findPackageRoot(), \"..\", \"core\");\n if (existsSync(join(workspacePackageRoot, \"template-skills\"))) {\n return workspacePackageRoot;\n }\n\n let dir = dirname(fileURLToPath(import.meta.resolve(\"@fluid-app/fluid-cli\")));\n while (!existsSync(join(dir, \"package.json\"))) {\n const parent = dirname(dir);\n if (parent === dir)\n throw new Error(\"Could not find Fluid CLI package root\");\n dir = parent;\n }\n return dir;\n}\n\nasync function writeOutputFiles(\n targetPath: string,\n outputFiles: readonly string[],\n content: string,\n): Promise<void> {\n for (const outputFile of outputFiles) {\n const destPath = join(targetPath, outputFile);\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n await writeFile(destPath, content, \"utf-8\");\n }\n}\n\nfunction getOutputFilenames(filename: string): string[] {\n const outputFilename = getOutputFilename(filename);\n const normalized = outputFilename.replace(/\\\\/g, \"/\");\n\n if (normalized === \"AGENTS.md\") return [\"AGENTS.md\", \"CLAUDE.md\"];\n\n if (normalized.startsWith(\"skills/\")) {\n return [join(\".agents\", outputFilename), join(\".claude\", outputFilename)];\n }\n\n return [outputFilename];\n}\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 outputFiles = getOutputFilenames(file);\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\n for (const outputFile of outputFiles) {\n const destPath = join(targetPath, outputFile);\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n await writeFile(destPath, processed, \"utf-8\");\n }\n }\n\n for (const skillFile of await getSharedTemplateSkillFiles()) {\n await writeOutputFiles(\n targetPath,\n getOutputFilenames(skillFile.relativePath),\n skillFile.content,\n );\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 exact SDK version embedded by the build, or from the workspace\n * package while running source directly.\n */\nexport async function getSdkVersion(): Promise<string> {\n return getTemplatePackageVersion(\n [\"portal\", \"sdk\"],\n EMBEDDED_PORTAL_SDK_VERSION,\n );\n}\n\n/**\n * Builds an absolute pnpm link: specifier for local scaffolds.\n *\n * link: keeps the dependency pointed at the workspace package source instead of\n * packing only package.json \"files\" entries like file: does. That lets Vite's\n * development export resolve the SDK source and its workspace dependencies. An\n * absolute path also remains valid when the operating system canonicalizes the\n * generated app path, such as macOS resolving /tmp to /private/tmp.\n */\nexport function getLocalPackageLinkVersion(packagePath: string): string {\n const linkPath = resolve(packagePath).replace(/\\\\/g, \"/\");\n return `link:${linkPath}`;\n}\n\n/**\n * Reads the exact CLI core version embedded by the build, or from the\n * workspace package while running source directly.\n *\n * This is separate from getSdkVersion because the CLI and portal SDK\n * are versioned independently.\n */\nexport async function getCliVersion(): Promise<string> {\n return getTemplatePackageVersion([\"cli\", \"core\"], EMBEDDED_CLI_CORE_VERSION);\n}\n\n/** Reads the independently versioned portal CLI plugin version. */\nexport async function getPortalCliVersion(): Promise<string> {\n return getTemplatePackageVersion(\n [\"cli\", \"portal\"],\n EMBEDDED_CLI_PORTAL_VERSION,\n );\n}\n\nconst NPM_REGISTRY_URL = \"https://registry.npmjs.org\";\nconst NPM_REGISTRY_TIMEOUT_MS = 5_000;\n\n/**\n * Clamp a scaffold dependency pin to a version that `pnpm install` can\n * actually resolve from the registry.\n *\n * The versions above are whatever this CLI build carries — embedded at build\n * time, or read from the workspace when running from source. Both run ahead\n * of npm whenever the monorepo holds a release bump that has not been\n * published yet (routine for anyone running the CLI from a checkout, e.g.\n * Mist in development), and a scaffold pinned to that version fails to\n * install. Only a definitive registry 404 for the exact version downgrades\n * the pin to the registry's `latest`; a network failure or any other registry\n * response keeps the original version, so offline scaffolds behave exactly as\n * before.\n *\n * Scaffold pins are the ONLY sound callsite: consumers that report which\n * version is running (e.g. widget-package-publish) must not route through\n * this — it would silently misreport an unpublished build as the older\n * published one.\n */\nexport async function resolveInstallableTemplateVersion(\n packageName: string,\n version: string,\n): Promise<string> {\n try {\n const exact = await fetch(\n `${NPM_REGISTRY_URL}/${packageName}/${encodeURIComponent(version)}`,\n { signal: AbortSignal.timeout(NPM_REGISTRY_TIMEOUT_MS) },\n );\n if (exact.status !== 404) return version;\n\n const latest = await fetch(`${NPM_REGISTRY_URL}/${packageName}/latest`, {\n signal: AbortSignal.timeout(NPM_REGISTRY_TIMEOUT_MS),\n });\n if (!latest.ok) return version;\n const pkg = (await latest.json()) as { version?: unknown };\n if (typeof pkg.version !== \"string\") return version;\n\n console.warn(\n `${packageName}@${version} is not published yet; the scaffold pins the latest published version ${pkg.version} instead.`,\n );\n return pkg.version;\n } catch {\n return version;\n }\n}\n\nasync function getTemplatePackageVersion(\n packagePathSegments: readonly string[],\n embeddedVersion: string | undefined,\n): Promise<string> {\n if (embeddedVersion) return embeddedVersion;\n\n try {\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const packageJsonPath = join(\n packagesRoot,\n ...packagePathSegments,\n \"package.json\",\n );\n\n const content = await readFile(packageJsonPath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: unknown };\n if (typeof pkg.version !== \"string\") {\n throw new Error(`Missing version in ${packageJsonPath}`);\n }\n return pkg.version;\n } catch (error) {\n throw new Error(\n `Could not resolve the exact template package version for ${packagePathSegments.join(\"/\")}`,\n { cause: error },\n );\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 outputFiles = getOutputFilenames(file);\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\n for (const outputFile of outputFiles) {\n const destPath = join(targetPath, outputFile);\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n await writeFile(destPath, processed, \"utf-8\");\n }\n }\n\n for (const skillFile of await getSharedTemplateSkillFiles()) {\n await writeOutputFiles(\n targetPath,\n getOutputFilenames(skillFile.relativePath),\n skillFile.content,\n );\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","/**\n * Git wrapper for the portal CLI — shells out to the system `git` via\n * execa. We deliberately do NOT bundle git or use a pure-JS impl; users\n * are expected to have it installed (the same stance as `fluid mist`).\n *\n * Security: a token-bearing `remote_url` (minted by the portal\n * `git_credential` endpoint) must never be logged, persisted, *or*\n * exposed in the process table. The token is stripped from the URL and\n * handed to git through a GIT_ASKPASS script via env vars\n * (FLUID_PORTAL_GIT_USER / FLUID_PORTAL_GIT_PASS) — so it never appears\n * in argv (visible to `ps aux` / `/proc/<pid>/cmdline`) and is never\n * written to `.git/config`.\n *\n * This mirrors `packages/cli/mist/src/lib/git.ts`. Kept portal-local for\n * now; if the widget/mist tracks extract a shared git lib we should\n * consume it instead (see overnight/track2-portal.md).\n */\n\nimport {\n parseNameStatusZ,\n type ChangeEntry,\n type CommitMessage,\n} from \"@fluid-app/fluid-cli\";\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { randomUUID } from \"node:crypto\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { execa, type ExecaError } from \"execa\";\n\nexport class GitError extends Error {\n readonly exitCode: number;\n\n constructor(message: string, exitCode = 1) {\n super(message);\n this.name = \"GitError\";\n this.exitCode = exitCode;\n }\n}\n\n/**\n * execa throws ENOENT when the git binary itself is missing — the single\n * most common failure on non-developer machines. Translate it into an\n * actionable message. Returns null for every other failure so call sites\n * keep their specific messages.\n */\nexport function gitMissingError(err: unknown): GitError | null {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code !== \"ENOENT\") return null;\n return new GitError(\n \"Git isn't installed (or isn't on your PATH), so this command can't run.\\n\" +\n \" Install it, then try again:\\n\" +\n \" macOS: xcode-select --install (or `brew install git`)\\n\" +\n \" Windows: https://git-scm.com/download/win\\n\" +\n \" Linux: use your package manager, e.g. `apt install git`\",\n 127,\n );\n}\n\n/**\n * Is `git` available on this machine? Cheap, never throws — used to\n * decide whether to take the GitHub-template path or fall back to the\n * bundled scaffold.\n */\nexport async function isGitAvailable(): Promise<boolean> {\n try {\n await execa(\"git\", [\"--version\"]);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Confirm git is installed, throwing the friendly message otherwise. */\nexport async function ensureGitAvailable(): Promise<void> {\n if (!(await isGitAvailable())) {\n throw new GitError(\n \"Git isn't installed (or isn't on your PATH), so this command can't run.\\n\" +\n \" Install it from https://git-scm.com/downloads (or `brew install git`), then try again.\",\n );\n }\n}\n\n/**\n * Split a token-bearing remote URL into the userless URL + creds. Keeps\n * the token off git's argv: we pass `cleanUrl` to git and feed\n * `username` / `password` back through GIT_ASKPASS env vars.\n *\n * If the URL has no embedded credentials, returns empty strings — fine\n * for public repos where git won't invoke askpass at all.\n */\nfunction splitRemoteCredentials(remoteUrl: string): {\n cleanUrl: string;\n username: string;\n password: string;\n} {\n const u = new URL(remoteUrl);\n const username = decodeURIComponent(u.username);\n const password = decodeURIComponent(u.password);\n u.username = \"\";\n u.password = \"\";\n return { cleanUrl: u.toString(), username, password };\n}\n\n/** Strip credential userinfo from a minted remote URL for safe comparison. */\nexport function cleanGitRemoteUrl(remoteUrl: string): string {\n return splitRemoteCredentials(remoteUrl).cleanUrl;\n}\n\n/**\n * Write a stub GIT_ASKPASS shell script to a per-process tmpdir and\n * return its path. The script contains no secret — it echoes back\n * whichever env var (FLUID_PORTAL_GIT_USER / FLUID_PORTAL_GIT_PASS)\n * matches git's prompt. The credentials only live in the spawned git\n * process's env for the duration of the call.\n *\n * One POSIX script for all platforms: Git for Windows executes\n * GIT_ASKPASS through its bundled sh, so a plain sh script works\n * everywhere. Idempotent: subsequent calls reuse the same path.\n */\nlet askpassPath: string | null = null;\nfunction ensureAskpassScript(): string {\n if (askpassPath && existsSync(askpassPath)) return askpassPath;\n const dir = path.join(os.tmpdir(), `fluid-portal-askpass-${process.pid}`);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });\n const p = path.join(dir, \"askpass.sh\");\n writeFileSync(\n p,\n `#!/bin/sh\\ncase \"$1\" in\\n Username*) printf '%s' \"$FLUID_PORTAL_GIT_USER\" ;;\\n *) printf '%s' \"$FLUID_PORTAL_GIT_PASS\" ;;\\nesac\\n`,\n { mode: 0o700 },\n );\n chmodSync(p, 0o700);\n askpassPath = p;\n return askpassPath;\n}\n\n/**\n * Build the spawn env + cleaned URL for a credentialed git invocation.\n * - `cleanUrl`: URL with userinfo stripped — safe to pass as argv.\n * - `env`: parent env plus GIT_ASKPASS + FLUID_PORTAL_GIT_USER/PASS.\n * GIT_TERMINAL_PROMPT=0 prevents the TTY-prompt fallback if the\n * credentials are empty (auth should fail fast, not hang).\n */\nfunction gitAuthSpawn(remoteUrl: string): {\n cleanUrl: string;\n env: NodeJS.ProcessEnv;\n} {\n const { username, password } = splitRemoteCredentials(remoteUrl);\n return {\n cleanUrl: cleanGitRemoteUrl(remoteUrl),\n env: {\n ...process.env,\n GIT_ASKPASS: ensureAskpassScript(),\n GIT_TERMINAL_PROMPT: \"0\",\n FLUID_PORTAL_GIT_USER: username,\n FLUID_PORTAL_GIT_PASS: password,\n },\n };\n}\n\n/** Map an execa failure to a GitError, preferring the friendly\n * git-missing message when the binary itself is absent. */\nfunction toGitError(err: unknown, verb: string): GitError {\n const missing = gitMissingError(err);\n if (missing) return missing;\n const e = err as ExecaError;\n return new GitError(\n `git ${verb} failed${e.exitCode != null ? ` (exit ${e.exitCode})` : \"\"}`,\n e.exitCode ?? 1,\n );\n}\n\n/**\n * `git clone <cleanUrl> <targetDir>`. When `remoteUrl` carries a token,\n * gitAuthSpawn strips it from the URL and re-injects it via GIT_ASKPASS\n * so it never appears in argv. `credential.helper=` stops git from\n * caching what askpass returned to any system credential store.\n *\n * `depth` shallow-clones (used by the create flow, which discards `.git`\n * afterwards anyway).\n */\nexport async function gitClone(\n remoteUrl: string,\n targetDir: string,\n opts: { depth?: number } = {},\n): Promise<void> {\n const { cleanUrl, env } = gitAuthSpawn(remoteUrl);\n const depthArgs = opts.depth ? [\"--depth\", String(opts.depth)] : [];\n try {\n await execa(\n \"git\",\n [\n \"-c\",\n \"credential.helper=\",\n \"clone\",\n \"--quiet\",\n ...depthArgs,\n cleanUrl,\n targetDir,\n ],\n { env, stdio: [\"ignore\", \"inherit\", \"inherit\"] },\n );\n } catch (err) {\n throw toGitError(err, \"clone\");\n }\n}\n\n/** Initialize a fresh repo (used after scaffolding so the project is\n * git-ready). Best-effort: never throws — a scaffold without a repo is\n * still usable, and a missing git binary is handled by the caller. */\nexport async function gitInit(cwd: string): Promise<boolean> {\n try {\n await execa(\"git\", [\"init\", \"--quiet\"], { cwd });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Is `cwd` inside a git work tree? Cheap, never throws. */\nexport async function isGitRepo(cwd: string): Promise<boolean> {\n try {\n const { stdout } = await execa(\n \"git\",\n [\"rev-parse\", \"--is-inside-work-tree\"],\n { cwd },\n );\n return stdout.trim() === \"true\";\n } catch {\n return false;\n }\n}\n\n/** Read `git remote get-url origin` (or null if no origin / not a repo). */\nexport async function getOriginUrl(cwd: string): Promise<string | null> {\n try {\n const { stdout } = await execa(\"git\", [\"remote\", \"get-url\", \"origin\"], {\n cwd,\n });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Are there uncommitted changes (working tree or index)? `git status\n * --porcelain` is empty when clean. Returns false (never throws) when\n * `cwd` isn't a repo.\n */\nexport async function hasUncommittedChanges(cwd: string): Promise<boolean> {\n try {\n const { stdout } = await execa(\"git\", [\"status\", \"--porcelain\"], { cwd });\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Fetch `branch` from a token-bearing remote URL, writing FETCH_HEAD so\n * `gitAheadBehindVsFetchHead` is meaningful. Token comes through\n * GIT_ASKPASS, never argv or `.git/config`.\n */\nexport async function gitFetch(\n cwd: string,\n remoteUrl: string,\n branch = \"main\",\n): Promise<void> {\n const { cleanUrl, env } = gitAuthSpawn(remoteUrl);\n try {\n await execa(\n \"git\",\n [\"-c\", \"credential.helper=\", \"fetch\", \"--quiet\", cleanUrl, branch],\n { cwd, env },\n );\n } catch (err) {\n throw toGitError(err, \"fetch\");\n }\n}\n\n/**\n * Count commits in local HEAD vs FETCH_HEAD (after `gitFetch`). Returns\n * `{ahead, behind}`. `0/0` means \"in sync\" (or no FETCH_HEAD yet).\n */\nexport async function gitAheadBehindVsFetchHead(\n cwd: string,\n): Promise<{ ahead: number; behind: number }> {\n try {\n const { stdout } = await execa(\n \"git\",\n [\"rev-list\", \"--left-right\", \"--count\", \"HEAD...FETCH_HEAD\"],\n { cwd },\n );\n const [a, b] = stdout.trim().split(/\\s+/);\n const ahead = Number.parseInt(a ?? \"0\", 10);\n const behind = Number.parseInt(b ?? \"0\", 10);\n return {\n ahead: Number.isFinite(ahead) ? ahead : 0,\n behind: Number.isFinite(behind) ? behind : 0,\n };\n } catch {\n return { ahead: 0, behind: 0 };\n }\n}\n\n/** Does `cwd` have any commits yet? (false right after `git init`.) */\nexport async function hasCommits(cwd: string): Promise<boolean> {\n try {\n await execa(\"git\", [\"rev-parse\", \"--verify\", \"HEAD\"], { cwd });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Does `branch` exist on the remote? Throws on transport/auth failure so\n * a network blip is never mistaken for an empty remote (which would skip\n * drift detection). Token via GIT_ASKPASS, never argv or .git/config.\n */\nexport async function gitRemoteBranchExists(\n cwd: string,\n remoteUrl: string,\n branch = \"main\",\n): Promise<boolean> {\n const { cleanUrl, env } = gitAuthSpawn(remoteUrl);\n try {\n const { stdout } = await execa(\n \"git\",\n [\n \"-c\",\n \"credential.helper=\",\n \"ls-remote\",\n \"--heads\",\n cleanUrl,\n `refs/heads/${branch}`,\n ],\n { cwd, env },\n );\n return stdout.trim().length > 0;\n } catch (err) {\n throw toGitError(err, \"ls-remote\");\n }\n}\n\n/**\n * Adopt `FETCH_HEAD` as the current (unborn) branch's base: point the\n * branch ref at it and load its tree into the index, leaving the working\n * tree untouched. The next auto-commit (run with `ignoreRemovals`) then\n * records the local project tree on top of the remote history — local\n * additions/edits win, and remote-only files survive because a\n * content-only local dir (e.g. populated by `fluid portal pull`) is no\n * statement about what the full repo tracks — so the push fast-forwards\n * instead of being rejected for unrelated history. Only call when\n * `hasCommits` is false; with local commits it would silently rewrite\n * the branch ref.\n * Mirrors the widget CLI's first-push adoption (the backend provisions\n * repos with an auto-init/template commit, so a fresh root commit is\n * always unrelated history).\n *\n * Returns the number of remote commits adopted.\n */\nexport async function gitAdoptFetchHead(cwd: string): Promise<number> {\n try {\n await execa(\"git\", [\"reset\", \"--mixed\", \"FETCH_HEAD\"], { cwd });\n const { stdout } = await execa(\n \"git\",\n [\"rev-list\", \"--count\", \"FETCH_HEAD\"],\n { cwd },\n );\n const count = Number.parseInt(stdout.trim(), 10);\n return Number.isFinite(count) ? count : 0;\n } catch (err) {\n throw toGitError(err, \"adopt remote history\");\n }\n}\n\n/**\n * Normalize a git remote URL to `host/owner/repo` for identity\n * comparison — strips credentials, protocol, `.git`, and trailing\n * slashes so a token-bearing minted URL compares equal to the stored\n * origin. Returns null when the URL doesn't parse.\n */\nexport function normalizedRepoIdentity(remoteUrl: string): string | null {\n // scp-style SSH remotes (`git@github.com:owner/repo.git`) aren't URLs —\n // normalize them by hand so an SSH origin still verifies against the\n // https minted URL.\n const scp = remoteUrl.match(/^(?:[^@/\\s]+@)?([^:/\\s]+):([^/].*)$/);\n if (scp?.[1] && scp[2] && !remoteUrl.includes(\"://\")) {\n const path = scp[2].replace(/\\.git$/, \"\").replace(/\\/+$/, \"\");\n return `${scp[1]}/${path}`.toLowerCase();\n }\n // Plain filesystem remotes (local bare repos — tests, offline mirrors):\n // identity is the normalized path itself.\n if (remoteUrl.startsWith(\"/\") || remoteUrl.startsWith(\".\")) {\n return remoteUrl\n .replace(/\\.git$/, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n }\n try {\n const u = new URL(remoteUrl);\n const path = u.pathname\n .replace(/\\.git$/, \"\")\n .replace(/\\/+$/, \"\")\n .replace(/^\\/+/, \"\");\n return `${u.host}/${path}`.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * Do HEAD and FETCH_HEAD share any common ancestor? False for unrelated\n * histories — the guard that keeps a whole-tree auto-commit of the WRONG\n * repository from being pushed to a portal's remote. Never throws.\n */\nexport async function hasCommonAncestorWithFetchHead(\n cwd: string,\n): Promise<boolean> {\n try {\n await execa(\"git\", [\"merge-base\", \"HEAD\", \"FETCH_HEAD\"], { cwd });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Fast-forward HEAD to FETCH_HEAD. Fails (non-zero exit) if not\n * fast-forwardable — callers check the ahead-count first. */\nexport async function gitMergeFastForward(cwd: string): Promise<void> {\n try {\n await execa(\"git\", [\"merge\", \"--ff-only\", \"FETCH_HEAD\"], { cwd });\n } catch (err) {\n throw toGitError(err, \"fast-forward merge\");\n }\n}\n\n/**\n * Local-only state that must never reach a portal's provisioned repo,\n * regardless of which tool created the checkout.\n *\n * `.mist-desktop/` is Mist Desktop's per-machine working state for the\n * project directory it opened — chat transcripts, user attachments,\n * screenshots, source baselines, and a media cache. When Desktop drives\n * `fluid portal push`, that directory sits in the project root, so a\n * whole-tree `git add -A` publishes every private conversation to the\n * portal's GitHub repo. `.mist` is the sibling clone marker (a slug\n * pointer); committing it means a later revert deletes it and breaks\n * slug resolution for every subsequent command.\n *\n * Neither is portal content: the portal's own material lives under\n * `portal/` and is pushed separately by the content push.\n */\nconst LOCAL_ONLY_GIT_PATHS = [\".mist\", \".mist-desktop/\"] as const;\n\n/**\n * Append {@link LOCAL_ONLY_GIT_PATHS} to `.git/info/exclude` when absent.\n *\n * Why the local exclude and not `.gitignore`: a portal's `.gitignore`\n * ships from the starter template (bundled or the GitHub\n * `portal-template` repo), so a template edit alone would never reach\n * the clones that already exist on people's machines. `.git/info/exclude`\n * is per-checkout, so writing it here — at sync time, on every push —\n * heals a pre-existing clone on its very next push with no template\n * release required. The template is updated too, for fresh scaffolds.\n *\n * Idempotent and best-effort: never throws. A repo with an unusual `.git`\n * layout (missing `info/`, `.git` as a worktree pointer file) just skips\n * the exclude — {@link gitAutoCommit} also cleans these paths out of the\n * index after staging, so the exclude is a convenience, not the only\n * defense.\n *\n * Mirrors `packages/cli/mist/src/lib/git.ts::excludeMistMarkerFromGit`.\n */\nexport function excludeLocalStateFromGit(cwd: string): void {\n try {\n const gitDir = path.join(cwd, \".git\");\n if (!existsSync(gitDir)) return;\n const infoDir = path.join(gitDir, \"info\");\n if (!existsSync(infoDir)) mkdirSync(infoDir, { recursive: true });\n const excludePath = path.join(infoDir, \"exclude\");\n const existing = existsSync(excludePath)\n ? readFileSync(excludePath, \"utf8\")\n : \"\";\n const existingLines = new Set(\n existing\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean),\n );\n const missing = LOCAL_ONLY_GIT_PATHS.filter(\n (entry) => !existingLines.has(entry),\n );\n if (missing.length === 0) return;\n const needsLeadingNewline = existing.length > 0 && !existing.endsWith(\"\\n\");\n writeFileSync(\n excludePath,\n `${existing}${needsLeadingNewline ? \"\\n\" : \"\"}${missing.join(\"\\n\")}\\n`,\n \"utf8\",\n );\n } catch {\n // Best-effort — see doc comment. gitAutoCommit's explicit index\n // cleanup still keeps local state out if this fails silently.\n }\n}\n\n/**\n * Absolute path of the repository containing `cwd`, or `cwd` itself when\n * resolution fails (not a repo / git missing — the caller's own git\n * command surfaces the real error).\n *\n * Every local-state defense must anchor here rather than to `cwd`: a push\n * run from a project subdirectory would otherwise look for\n * `<subdir>/.git`, silently skip the exclude write, and then let\n * `git add -A` — which always stages the WHOLE repository — sweep the\n * root `.mist-desktop/` into the commit anyway.\n */\nasync function resolveRepoRoot(cwd: string): Promise<string> {\n try {\n const { stdout } = await execa(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n });\n const root = stdout.trim();\n if (root.length > 0) return root;\n } catch {\n // Fall through to cwd.\n }\n return cwd;\n}\n\n/**\n * Repository-root-anchored {@link excludeLocalStateFromGit}, for callers\n * that only have a possibly-nested `cwd`.\n *\n * Run this BEFORE any working-tree cleanliness check, not just before\n * staging: while `.mist-desktop/` is neither tracked nor excluded, it\n * shows up as untracked in `git status --porcelain`, so a Desktop-driven\n * push reads as \"you have uncommitted changes\" and the sync skips with\n * advice to `git pull` that the user can't act on. Excluding it first\n * makes the cleanliness check reflect actual portal changes.\n */\nexport async function ensureLocalStateExcluded(cwd: string): Promise<void> {\n excludeLocalStateFromGit(await resolveRepoRoot(cwd));\n}\n\n/** Paths currently tracked under `.mist-desktop/`, or [] when none. */\nasync function trackedLocalStatePaths(repoRoot: string): Promise<string[]> {\n try {\n const { stdout } = await execa(\"git\", [\"ls-files\", \"--\", \".mist-desktop\"], {\n cwd: repoRoot,\n });\n return stdout.split(\"\\n\").filter((l) => l.trim().length > 0);\n } catch {\n return [];\n }\n}\n\n/**\n * Untrack private Mist Desktop state and, if anything was tracked,\n * commit ONLY that removal so the next push deletes it from the remote.\n * Returns true when a purge commit was made.\n *\n * Call this on every sync BEFORE deciding whether there is anything to\n * auto-commit — a clean checkout that already tracks `.mist-desktop/`\n * still needs the removal, and ignore rules alone cannot untrack an\n * existing path.\n *\n * The commit is built through a THROWAWAY index (`GIT_INDEX_FILE`), not\n * the user's: `read-tree HEAD` → `rm --cached` → `write-tree` →\n * `commit-tree` → `update-ref`. A plain `git commit` would capture the\n * user's entire index, so anything they had pre-staged would be swept\n * into the purge commit and pushed — defeating `--no-commit` and\n * mislabelling their work. A pathspec commit (`git commit -- .mist-desktop`)\n * is also wrong: with `--only` git takes the *working tree* content of\n * those paths, and the chats are still on disk, so it would re-add\n * exactly what we are removing.\n *\n * The user's real index is then updated with the same `rm --cached`,\n * touching only this path — so any other staged work stays staged, and\n * the working tree is never modified.\n */\nexport async function purgeLocalStateFromIndex(\n cwd: string,\n message: string,\n): Promise<boolean> {\n const repoRoot = await resolveRepoRoot(cwd);\n if ((await trackedLocalStatePaths(repoRoot)).length === 0) return false;\n\n // A tracked path implies a commit exists, but resolve defensively:\n // commit-tree needs a parent.\n let head: string;\n try {\n const { stdout } = await execa(\"git\", [\"rev-parse\", \"HEAD\"], {\n cwd: repoRoot,\n });\n head = stdout.trim();\n if (!head) return false;\n } catch {\n return false;\n }\n\n const tmpIndex = path.join(\n os.tmpdir(),\n `fluid-portal-purge-${process.pid}-${randomUUID()}.index`,\n );\n const indexEnv = { ...process.env, GIT_INDEX_FILE: tmpIndex };\n let commitSha: string;\n try {\n await execa(\"git\", [\"read-tree\", head], { cwd: repoRoot, env: indexEnv });\n await execa(\n \"git\",\n [\"rm\", \"-q\", \"-r\", \"--cached\", \"--ignore-unmatch\", \"--\", \".mist-desktop\"],\n { cwd: repoRoot, env: indexEnv },\n );\n const { stdout: tree } = await execa(\"git\", [\"write-tree\"], {\n cwd: repoRoot,\n env: indexEnv,\n });\n const { stdout: sha } = await execa(\n \"git\",\n [\"commit-tree\", tree.trim(), \"-p\", head, \"-m\", message],\n { cwd: repoRoot, env: { ...indexEnv, ...(await identityEnv(repoRoot)) } },\n );\n commitSha = sha.trim();\n if (!commitSha) return false;\n } catch (err) {\n const missing = gitMissingError(err);\n if (missing) throw missing;\n throw toGitError(err, \"purge local state\");\n } finally {\n rmSync(tmpIndex, { force: true });\n }\n\n // Move the branch, then drop the path from the REAL index so it isn't\n // re-added as a staged addition against the new HEAD. Both are scoped:\n // no other index entry and no working-tree file is touched.\n try {\n await execa(\"git\", [\"update-ref\", \"HEAD\", commitSha, head], {\n cwd: repoRoot,\n });\n } catch (err) {\n throw toGitError(err, \"purge local state\");\n }\n try {\n await execa(\n \"git\",\n [\"rm\", \"-q\", \"-r\", \"--cached\", \"--ignore-unmatch\", \"--\", \".mist-desktop\"],\n { cwd: repoRoot },\n );\n } catch {\n // Non-fatal: HEAD is already correct, so the push ships the removal.\n }\n return true;\n}\n\n/** `GIT_{AUTHOR,COMMITTER}_*` overrides for `commit-tree`, which (unlike\n * `git commit`) fails outright rather than prompting when the repo has\n * no identity. Only set what the repo itself doesn't define. */\nasync function identityEnv(cwd: string): Promise<NodeJS.ProcessEnv> {\n const env: NodeJS.ProcessEnv = {};\n const read = async (key: string): Promise<boolean> => {\n try {\n const r = await execa(\"git\", [\"config\", key], { cwd });\n return r.stdout.trim().length > 0;\n } catch {\n return false;\n }\n };\n if (!(await read(\"user.name\"))) {\n env[\"GIT_AUTHOR_NAME\"] = \"Fluid Portal CLI\";\n env[\"GIT_COMMITTER_NAME\"] = \"Fluid Portal CLI\";\n }\n if (!(await read(\"user.email\"))) {\n env[\"GIT_AUTHOR_EMAIL\"] = \"portal@fluid.app\";\n env[\"GIT_COMMITTER_EMAIL\"] = \"portal@fluid.app\";\n }\n return env;\n}\n\n/** Shared `-c user.*` args: use the repo's identity, else a synthetic\n * Fluid bot identity so the commit isn't rejected. */\nasync function resolveIdentityArgs(cwd: string): Promise<string[]> {\n let hasName = false;\n let hasEmail = false;\n try {\n const r = await execa(\"git\", [\"config\", \"user.name\"], { cwd });\n hasName = r.stdout.trim().length > 0;\n } catch {\n // exit 1 from `git config <key>` just means the key is unset.\n }\n try {\n const r = await execa(\"git\", [\"config\", \"user.email\"], { cwd });\n hasEmail = r.stdout.trim().length > 0;\n } catch {\n // ditto.\n }\n return [\n ...(hasName ? [] : [\"-c\", \"user.name=Fluid Portal CLI\"]),\n ...(hasEmail ? [] : [\"-c\", \"user.email=portal@fluid.app\"]),\n ];\n}\n\nexport interface GitAutoCommitOptions {\n /**\n * Stage with `git add --ignore-removal .` (new + modified files only)\n * instead of `git add -A`, so files present in the index but absent\n * from the working tree are NOT recorded as deletions. Used by the\n * first-push adoption path in git-sync: a directory populated by\n * `fluid portal pull` (content-only sync) has no idea what the full\n * repo tracks, so local additions/edits win but remote-only files are\n * preserved — deletions only ever ship from full checkouts.\n */\n readonly ignoreRemovals?: boolean;\n}\n\n/**\n * `git add -A && git commit -m <message>` (or, with\n * `{ ignoreRemovals: true }`, `git add --ignore-removal .` — see\n * GitAutoCommitOptions).\n *\n * `git add -A` intentionally stages the whole work tree: a portal's\n * provisioned repo tracks the entire project (it's seeded from the\n * starter template), and the \"publish my changes\" UX expects every edit\n * to ship — the same stance as `fluid mist push`. The exclusion boundary\n * is the project's `.gitignore` (the starter ignores `.env`,\n * `node_modules/`, `dist/`, `.portal-sync/`, logs, `*.tsbuildinfo`), so\n * generated output and local secrets stay out of the commit.\n *\n * That boundary is not enough on its own for tool-local state written\n * INTO the project directory by whatever opened it — most importantly\n * Mist Desktop's `.mist-desktop/` (private chats, attachments,\n * screenshots) and the `.mist` clone marker. A `.gitignore`-only fix\n * can't reach clones that already exist, so this applies belt AND\n * suspenders, all anchored to the repository root (see\n * {@link resolveRepoRoot}):\n *\n * 1. {@link excludeLocalStateFromGit} — idempotent `.git/info/exclude`\n * write, so `git add` never picks the paths up in the first place.\n * Heals pre-existing clones on their next push.\n * 2. `git reset -- ':(top).mist' .mist` after staging — unstages the\n * marker if step 1 silently failed. We deliberately do NOT pass a\n * `:(exclude)` pathspec to `git add`: when the path is *also*\n * listed in `.git/info/exclude`, git exits 1 with \"paths are\n * ignored by one of your .gitignore files\", turning the normal\n * (already-healed) case into a hard failure. Add-then-reset is a\n * no-op when there's nothing staged.\n * 3. `git rm --cached -r --ignore-unmatch .mist-desktop` — untracks\n * chat history that an older, unhardened push already committed.\n * Ignore rules alone cannot untrack an existing path. `--cached`\n * keeps every file on disk; only the index entry goes away. This\n * runs even under `ignoreRemovals`, where suppressing deletions\n * protects remote-only *portal* files — private chats are never\n * that, and leaving them tracked would keep republishing them.\n *\n * Steps 2 and 3 are best-effort and never fail the publish; the commit\n * itself is what must succeed.\n *\n * Uses the repo's configured identity, else a synthetic Fluid bot\n * identity so the commit isn't rejected. \"nothing to commit\" is treated\n * as a no-op, not an error.\n */\n/**\n * The staged file set, as {@link ChangeEntry}s — the input a\n * {@link CommitMessage} resolver summarizes.\n *\n * `-z` (NUL-delimited) rather than the default: paths with spaces or\n * quotes are legal in git and common in themes and asset folders, and\n * the newline form quotes and escapes them, which would corrupt both the\n * path and the count.\n *\n * Best-effort — a summary is a nicety, never a reason to fail a publish.\n * An empty result makes the resolver fall back to its own constant.\n */\nasync function stagedChanges(cwd: string): Promise<ChangeEntry[]> {\n try {\n const { stdout } = await execa(\n \"git\",\n [\"diff\", \"--cached\", \"--name-status\", \"-z\"],\n { cwd },\n );\n return parseNameStatusZ(stdout);\n } catch {\n return [];\n }\n}\n\nexport async function gitAutoCommit(\n cwd: string,\n message: CommitMessage,\n options: GitAutoCommitOptions = {},\n): Promise<void> {\n const repoRoot = await resolveRepoRoot(cwd);\n // Defensive: heals a clone made before the sync started excluding these\n // paths, or one whose clone-time exclude write failed.\n excludeLocalStateFromGit(repoRoot);\n\n const addArgs = options.ignoreRemovals\n ? [\"add\", \"--ignore-removal\", \".\"]\n : [\"add\", \"-A\"];\n try {\n await execa(\"git\", addArgs, { cwd });\n } catch (err) {\n throw toGitError(err, \"add\");\n }\n\n // Second line of defense — only reachable if the exclude write above\n // failed. `:(top).mist` resolves against the work-tree root so a push\n // from a subdirectory still unstages the root marker; plain `.mist`\n // additionally covers a project nested inside a larger repo. `git\n // reset` on paths with nothing staged already exits 0, so a failure\n // here is unexpected and still not worth aborting a publish over.\n try {\n await execa(\"git\", [\"reset\", \"-q\", \"--\", \":(top).mist\", \".mist\"], { cwd });\n } catch {\n // Non-fatal — see doc comment.\n }\n // Untrack private Desktop state committed by an older publish, from the\n // repository root so a subdirectory invocation still matches it.\n try {\n await execa(\n \"git\",\n [\"rm\", \"-q\", \"-r\", \"--cached\", \"--ignore-unmatch\", \"--\", \".mist-desktop\"],\n { cwd: repoRoot },\n );\n } catch {\n // Non-fatal — the exclude file remains the primary defense.\n }\n\n // Resolve the subject only now: everything above decides what is\n // actually staged, so a summary built any earlier would describe a\n // file set the commit doesn't carry.\n const subject =\n typeof message === \"string\"\n ? message\n : await message(await stagedChanges(cwd));\n const identityArgs = await resolveIdentityArgs(cwd);\n try {\n // BOTH output streams are piped (not inherited) so the catch below\n // can actually inspect them — with \"inherit\", execa's e.stdout /\n // e.stderr are undefined and the documented \"nothing to commit is a\n // no-op\" contract can never match. Notably git prints \"no changes\n // added to commit\" to STDOUT, so piping stderr alone isn't enough.\n // The sync is meant to be invisible, so swallowing the commit\n // summary on success is fine.\n await execa(\"git\", [...identityArgs, \"commit\", \"-m\", subject], {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n } catch (err) {\n const missing = gitMissingError(err);\n if (missing) throw missing;\n const e = err as ExecaError;\n // git prints \"nothing to commit\" on stdout or stderr depending on\n // version/porcelain state — check both captured streams. With\n // ignoreRemovals, a tree whose only pending changes are deletions\n // stages nothing, and git phrases that as \"no changes added to\n // commit\" instead — equally a no-op here.\n const streams = `${typeof e.stdout === \"string\" ? e.stdout : \"\"}\\n${typeof e.stderr === \"string\" ? e.stderr : \"\"}`;\n if (/nothing to commit|no changes added to commit/i.test(streams)) {\n return;\n }\n throw toGitError(err, \"commit\");\n }\n}\n\n/**\n * `git push <cleanUrl> HEAD:<branch>` — credentials supplied via\n * GIT_ASKPASS, never argv. `http.…/.extraheader=` clears any cached\n * Authorization header a CI-style config might inject.\n */\nexport async function gitPush(\n cwd: string,\n remoteUrl: string,\n branch = \"main\",\n): Promise<void> {\n const { cleanUrl, env } = gitAuthSpawn(remoteUrl);\n try {\n await execa(\n \"git\",\n [\n \"-c\",\n \"credential.helper=\",\n \"-c\",\n \"http.https://github.com/.extraheader=\",\n \"push\",\n cleanUrl,\n `HEAD:${branch}`,\n ],\n { cwd, env, stdio: [\"ignore\", \"inherit\", \"inherit\"] },\n );\n } catch (err) {\n throw toGitError(err, \"push\");\n }\n}\n","/**\n * GitHub-template scaffolding for `fluid portal create`.\n *\n * The canonical starter lives at github.com/Fluid-WeCommerce/portal-template\n * as a **fully rendered, runnable** project (GitHub's generate-from-template\n * copies files verbatim, so it can't contain `{{handlebars}}` placeholders).\n * The project name is baked in as a distinctive token (CANONICAL_PROJECT_NAME);\n * after cloning we re-substitute it with the user's chosen app name.\n *\n * This is an explicit opt-in create path. The bundled template is the\n * deterministic default, so git or network availability cannot change the\n * normal scaffold output.\n */\n\nimport {\n readdir,\n readFile,\n writeFile,\n rm,\n copyFile,\n stat,\n} from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { gitClone } from \"../lib/git.js\";\n\n/** Public HTTPS URL of the portal starter template repo. */\nexport const TEMPLATE_REPO_URL =\n \"https://github.com/Fluid-WeCommerce/portal-template.git\";\n\n/**\n * The project name baked into the rendered template repo. `fluid portal\n * create` replaces every occurrence with the user's app name after\n * cloning. Must match the name the template was rendered with — keep in\n * sync with the render used to publish the repo.\n */\nexport const CANONICAL_PROJECT_NAME = \"fluid-portal-starter\";\n\n/**\n * Files/dirs we never rewrite when re-substituting the project name.\n * The lockfile keys packages by import path (`.`), not the root name, so\n * touching it would only risk corruption for zero benefit.\n */\nconst SUBSTITUTION_SKIP = new Set([\".git\", \"node_modules\", \"pnpm-lock.yaml\"]);\n\n/** Extensions we treat as text and safe to rewrite. Everything else is\n * left byte-for-byte (avoids mangling any binary asset a future template\n * might carry). */\nconst TEXT_EXTENSIONS = new Set([\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".json\",\n \".md\",\n \".yml\",\n \".yaml\",\n \".css\",\n \".html\",\n \".txt\",\n]);\n\nfunction hasTextExtension(file: string): boolean {\n const dot = file.lastIndexOf(\".\");\n if (dot < 0) return false;\n return TEXT_EXTENSIONS.has(file.slice(dot));\n}\n\n/** Recursively list absolute file paths, skipping SUBSTITUTION_SKIP dirs. */\nasync function listFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n for (const entry of entries) {\n if (SUBSTITUTION_SKIP.has(entry.name)) continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await listFiles(full)));\n } else {\n files.push(full);\n }\n }\n return files;\n}\n\n/** Escape a string for use in a RegExp. */\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Replace every occurrence of the canonical project name with `appName`\n * across the cloned tree's text files. Only files that actually contain\n * the token are rewritten.\n */\nexport async function rewriteProjectName(\n targetPath: string,\n appName: string,\n): Promise<void> {\n if (appName === CANONICAL_PROJECT_NAME) return;\n const token = new RegExp(escapeRegExp(CANONICAL_PROJECT_NAME), \"g\");\n // listFiles already prunes the SUBSTITUTION_SKIP dirs/files.\n const files = await listFiles(targetPath);\n for (const file of files) {\n if (!hasTextExtension(file)) continue;\n const content = await readFile(file, \"utf-8\");\n if (!content.includes(CANONICAL_PROJECT_NAME)) continue;\n // Function replacement so `$` sequences in the app name are taken\n // literally instead of as replacement patterns ($&, $$, …).\n await writeFile(\n file,\n content.replace(token, () => appName),\n \"utf-8\",\n );\n }\n}\n\n/** Rewrite `.fluidrc` with the chosen CLI profile (the template ships an\n * empty profile). No-op when the file is absent. */\nexport async function writeFluidrcProfile(\n targetPath: string,\n profileName: string,\n): Promise<void> {\n const fluidrcPath = join(targetPath, \".fluidrc\");\n if (!existsSync(fluidrcPath)) return;\n await writeFile(\n fluidrcPath,\n JSON.stringify({ profile: profileName }, null, 2) + \"\\n\",\n \"utf-8\",\n );\n}\n\nexport interface ScaffoldFromGitHubOptions {\n readonly targetPath: string;\n readonly appName: string;\n readonly profileName: string;\n /** Override the template repo URL (tests / private mirrors). */\n readonly repoUrl?: string;\n}\n\n/**\n * Clone the GitHub starter template into `targetPath`, discard its git\n * history, re-substitute the project name, wire `.fluidrc`, and copy\n * `.env.example` → `.env`.\n *\n * Throws (GitError or otherwise) on any failure so the explicit remote\n * template request is reported as a failure.\n */\nexport async function scaffoldFromGitHub(\n options: ScaffoldFromGitHubOptions,\n): Promise<void> {\n const { targetPath, appName, profileName } = options;\n const repoUrl = options.repoUrl ?? TEMPLATE_REPO_URL;\n\n // Shallow clone — we throw the history away immediately.\n await gitClone(repoUrl, targetPath, { depth: 1 });\n\n // Drop the template's git history so the new project starts clean.\n await rm(join(targetPath, \".git\"), { recursive: true, force: true });\n\n await rewriteProjectName(targetPath, appName);\n await writeFluidrcProfile(targetPath, profileName);\n\n // Copy .env.example → .env so dotenv works out of the box (matches the\n // bundled scaffold behaviour).\n const envExample = join(targetPath, \".env.example\");\n try {\n if ((await stat(envExample)).isFile()) {\n await copyFile(envExample, join(targetPath, \".env\"));\n }\n } catch {\n // No .env.example — nothing to seed.\n }\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { join, dirname, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { constants } from \"node:fs\";\nimport {\n copyFile,\n lstat,\n mkdir,\n mkdtemp,\n readdir,\n rm,\n writeFile,\n} from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport type { CreateOptions, ProjectConfig } from \"../types.js\";\nimport { promptProjectConfig } from \"../utils/prompts.js\";\nimport {\n getTemplatePaths,\n copyTemplate,\n directoryExists,\n createDirectory,\n getSdkVersion,\n getCliVersion,\n getPortalCliVersion,\n resolveInstallableTemplateVersion,\n fileExists,\n getLocalPackageLinkVersion,\n} from \"../utils/file-system.js\";\nimport {\n installDependencies,\n getRunCommand,\n} from \"../utils/package-manager.js\";\nimport { isGitAvailable } from \"../lib/git.js\";\nimport { scaffoldFromGitHub } from \"../utils/scaffold-github.js\";\n\nexport interface CreatePortalProjectDependencies {\n readonly installDependencies: typeof installDependencies;\n readonly isGitAvailable: typeof isGitAvailable;\n readonly scaffoldFromGitHub: typeof scaffoldFromGitHub;\n}\n\nconst defaultCreatePortalProjectDependencies = {\n installDependencies,\n isGitAvailable,\n scaffoldFromGitHub,\n} satisfies CreatePortalProjectDependencies;\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(\n \"--skip-install\",\n \"Create the scaffold only; skip dependency installation\",\n )\n .option(\n \"-o, --output-dir <dir>\",\n \"Directory to create the project in (defaults to cwd)\",\n )\n .option(\n \"--local\",\n \"Use local monorepo packages via link: paths (for development testing)\",\n )\n .option(\n \"--remote-template\",\n \"Use the GitHub starter template instead of the bundled template\",\n )\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(\n resolve(options.outputDir ?? process.cwd()),\n appName,\n );\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 await createPortalProject(appName, options, config);\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 async function createPortalProject(\n appName: string,\n options: CreateOptions,\n config: ProjectConfig,\n dependencies: CreatePortalProjectDependencies = defaultCreatePortalProjectDependencies,\n): Promise<void> {\n const targetPath = join(resolve(options.outputDir ?? process.cwd()), appName);\n const isLocal = options.local === true;\n\n if (options.remoteTemplate === true) {\n if (isLocal) {\n throw new Error(\"--remote-template cannot be combined with --local\");\n }\n if (config.selectedPages.length > 0) {\n throw new Error(\n \"--remote-template cannot be used with optional page selections\",\n );\n }\n if (!(await dependencies.isGitAvailable())) {\n throw new Error(\n \"The remote template requires Git. Remove --remote-template to use the bundled template.\",\n );\n }\n\n const spinner = ora(\"Fetching starter template from GitHub...\").start();\n try {\n await dependencies.scaffoldFromGitHub({\n targetPath,\n appName: config.name,\n profileName: config.profileName,\n });\n spinner.succeed(\"Fetched starter template\");\n } catch (error) {\n spinner.fail(\"Failed to fetch the GitHub starter template\");\n await rm(targetPath, { recursive: true, force: true }).catch(() => {});\n throw error;\n }\n } else {\n await scaffoldFromBundled(targetPath, config, isLocal, true);\n }\n\n if (config.installDeps) {\n const installSpinner = ora(\"Installing dependencies with pnpm...\").start();\n try {\n await dependencies.installDependencies(targetPath);\n installSpinner.succeed(\"Installed dependencies\");\n } catch (error) {\n installSpinner.fail(\"Failed to install dependencies\");\n console.log();\n console.log(chalk.yellow(`The scaffold was retained at ${targetPath}.`));\n console.log(chalk.yellow(\"Install dependencies manually to continue:\"));\n console.log(chalk.cyan(` cd ${formatCdPath(targetPath)}`));\n console.log(chalk.cyan(\" pnpm install\"));\n throw error;\n }\n }\n\n console.log();\n if (!config.installDeps) {\n console.log(\n chalk.yellow(\n \"Scaffold only: dependencies were not installed and must be installed before development.\",\n ),\n );\n console.log();\n }\n console.log(chalk.green.bold(\"Success!\") + ` Created ${chalk.cyan(appName)}`);\n console.log();\n console.log(\"Next steps:\");\n console.log();\n console.log(chalk.cyan(` cd ${formatCdPath(targetPath)}`));\n if (!config.installDeps) {\n console.log(chalk.cyan(\" pnpm install\"));\n }\n console.log(chalk.cyan(` ${getRunCommand(\"dev\")}`));\n console.log();\n console.log(\n \"Then open \" + chalk.cyan(\"http://localhost:5173\") + \" in your browser.\",\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 if (!config.profileName) {\n console.log(\n chalk.yellow(\n \" Run \" +\n chalk.cyan(\"fluid login\") +\n \" and update \" +\n chalk.cyan(\".fluidrc\") +\n \" with your profile name.\",\n ),\n );\n console.log();\n }\n console.log(\n \"Run \" +\n chalk.cyan(\"pnpm pull\") +\n \" and edit \" +\n chalk.cyan(\"portal/\") +\n \" JSON to customize your portal definition.\",\n );\n console.log();\n}\n\nfunction formatCdPath(targetPath: string): string {\n return relative(process.cwd(), targetPath) || \".\";\n}\n\n/**\n * Scaffold from the bundled base + overlay templates (Handlebars). Used\n * by default, or with `--local`\n * (which rewrites SDK/core deps to workspace `link:` paths so the\n * generated project resolves unpublished monorepo source).\n *\n * Throws on failure; the caller reports and exits.\n */\nasync function scaffoldFromBundled(\n targetPath: string,\n config: ProjectConfig,\n isLocal: boolean,\n createTarget: boolean,\n): Promise<void> {\n // Get template paths (base + overlay)\n const templatePaths = getTemplatePaths(\"starter\");\n if (!(await directoryExists(templatePaths.base))) {\n throw new Error(\"Base template not found\");\n }\n if (!(await directoryExists(templatePaths.overlay))) {\n throw new Error(\"Starter template not found\");\n }\n\n // Get package versions (SDK and CLI are versioned independently), clamped\n // to what the registry can actually install — a from-source or locally\n // built CLI carries not-yet-published release bumps.\n let sdkVersion: string;\n let localCoreVersion: string | undefined;\n const [cliVersion, portalCliVersion] = await Promise.all([\n getCliVersion().then((version) =>\n resolveInstallableTemplateVersion(\"@fluid-app/fluid-cli\", version),\n ),\n getPortalCliVersion().then((version) =>\n resolveInstallableTemplateVersion(\"@fluid-app/fluid-cli-portal\", version),\n ),\n ]);\n\n if (isLocal) {\n // Resolve absolute link: paths so pnpm still finds the packages after the\n // operating system canonicalizes the generated project path, such as macOS\n // resolving /tmp to /private/tmp. The trade-off is that the generated\n // project is pinned to this monorepo's current location: moving or\n // re-cloning the monorepo breaks the links and the project has to be\n // scaffolded again. The portal CLI lives at\n // packages/cli/portal/dist/index.mjs\n // so going up four levels lands us at the monorepo root, then\n // `packages/portal/{sdk,core}` is the canonical location for the\n // SDK + core packages.\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const monorepoRoot = join(currentDir, \"..\", \"..\", \"..\", \"..\");\n const sdkPath = join(monorepoRoot, \"packages\", \"portal\", \"sdk\");\n const corePath = join(monorepoRoot, \"packages\", \"portal\", \"core\");\n\n if (\n !(await directoryExists(sdkPath)) ||\n !(await directoryExists(corePath))\n ) {\n throw new Error(\n \"--local requires running from within the fluid-mono monorepo\\n\" +\n \" Could not find packages/portal/sdk or packages/portal/core\",\n );\n }\n\n sdkVersion = getLocalPackageLinkVersion(sdkPath);\n localCoreVersion = getLocalPackageLinkVersion(corePath);\n console.log(chalk.cyan(\" Using local packages (--local mode)\"));\n } else {\n sdkVersion = await getSdkVersion().then((version) =>\n resolveInstallableTemplateVersion(\"@fluid-app/portal-sdk\", version),\n );\n }\n\n const spinner = ora(\n createTarget\n ? \"Creating project directory...\"\n : \"Preparing provisioned portal checkout...\",\n ).start();\n if (createTarget) {\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 } else if (!(await directoryExists(targetPath))) {\n throw new Error(`Portal checkout does not exist: ${targetPath}`);\n }\n\n // Copy base template first, then overlay template-specific files on top\n const templateVariables = {\n projectName: config.name,\n sdkVersion,\n localCoreVersion,\n cliVersion,\n portalCliVersion,\n selectedPages: config.selectedPages,\n hasSelectedPages: config.selectedPages.length > 0,\n profileName: config.profileName,\n };\n\n spinner.start(\"Copying template files...\");\n try {\n await copyTemplate(templatePaths.base, targetPath, templateVariables);\n await copyTemplate(templatePaths.overlay, targetPath, templateVariables);\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\n/**\n * Add the runnable bundled starter to an existing provisioned Git checkout.\n * Portal repositories begin with a bootstrap commit, so a first clone may\n * not contain package.json yet. Rendering into the checkout preserves its\n * .git directory and gives both the CLI and Mist Desktop a usable project\n * without creating unrelated local history.\n */\nexport async function scaffoldPortalProjectIntoExistingDirectory(\n targetPath: string,\n config: ProjectConfig,\n): Promise<void> {\n const pendingMarker = join(targetPath, PORTAL_SCAFFOLD_PENDING_MARKER);\n const stagingRoot = await mkdtemp(\n join(tmpdir(), \"fluid-portal-scaffold-stage-\"),\n );\n const stagedProject = join(stagingRoot, \"portal\");\n let completed = false;\n try {\n // A clone retry can distinguish our interrupted/failed scaffold from an\n // unrelated pre-existing directory. Keep the marker on every failure and\n // remove it only after the complete no-clobber overlay succeeds.\n await writeFile(pendingMarker, \"Fluid Portal scaffold pending\\n\");\n await scaffoldFromBundled(stagedProject, config, false, true);\n const preserved = await copyMissingScaffoldFiles(stagedProject, targetPath);\n if (preserved.length > 0) {\n console.log(\n chalk.dim(\n ` Preserved ${preserved.length} existing repository ${preserved.length === 1 ? \"file\" : \"files\"} while adding the runnable scaffold.`,\n ),\n );\n }\n completed = true;\n } finally {\n if (completed) await rm(pendingMarker, { force: true });\n await rm(stagingRoot, { recursive: true, force: true });\n }\n}\n\nexport const PORTAL_SCAFFOLD_PENDING_MARKER = \".fluid-portal-scaffold-pending\";\n\nasync function copyMissingScaffoldFiles(\n sourceRoot: string,\n targetRoot: string,\n relativeDirectory = \"\",\n): Promise<string[]> {\n const preserved: string[] = [];\n const sourceDirectory = join(sourceRoot, relativeDirectory);\n const entries = await readdir(sourceDirectory, { withFileTypes: true });\n\n for (const entry of entries) {\n const relativePath = join(relativeDirectory, entry.name);\n const sourcePath = join(sourceRoot, relativePath);\n const targetPath = join(targetRoot, relativePath);\n if (entry.isDirectory()) {\n if (!(await ensureScaffoldDirectory(targetPath))) {\n // The tracked repository path still wins, but silently omitting this\n // required subtree would report a successful scaffold that cannot\n // build. Fail without deleting or replacing the cloned repository so\n // the owner can resolve the path conflict and retry.\n throw new Error(\n `Cannot add the runnable Portal scaffold because the repository path \"${relativePath}\" is not a directory. The cloned repository and Git history were preserved.`,\n );\n }\n preserved.push(\n ...(await copyMissingScaffoldFiles(\n sourceRoot,\n targetRoot,\n relativePath,\n )),\n );\n continue;\n }\n if (!entry.isFile()) {\n throw new Error(\n `Portal scaffold contains an unsupported file type at ${relativePath}.`,\n );\n }\n try {\n // COPYFILE_EXCL is the no-clobber boundary. A repository file wins\n // even if it appears after the initial clone inspection.\n await copyFile(sourcePath, targetPath, constants.COPYFILE_EXCL);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n preserved.push(relativePath);\n }\n }\n\n return preserved;\n}\n\nasync function ensureScaffoldDirectory(targetPath: string): Promise<boolean> {\n try {\n return (await lstat(targetPath)).isDirectory();\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n // ENOTDIR means an ancestor was concurrently replaced by a file. That\n // repository path still wins and its subtree must remain untouched.\n if (code === \"ENOTDIR\") return false;\n throw error;\n }\n }\n\n try {\n await mkdir(targetPath);\n return true;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\" && code !== \"ENOTDIR\") throw error;\n try {\n return (await lstat(targetPath)).isDirectory();\n } catch (statError) {\n if ((statError as NodeJS.ErrnoException).code === \"ENOTDIR\") return false;\n throw statError;\n }\n }\n}\n\nexport function registerCreateCommand(ctx: PluginContext): void {\n ctx.program.addCommand(createCommand);\n}\n","/**\n * `fluid portal dev` command\n *\n * Starts the Vite development server with the portal dev plugin,\n * which intercepts manifest API requests and serves content from\n * the local `portal/` directory.\n *\n * If no `portal/` directory exists, prompts the user to run `fluid portal pull`\n * or auto-pulls if they are logged in.\n */\n\nimport { 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 { isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport type { DevOptions } from \"../types.js\";\n\nconst PORTAL_DIR = \"portal\";\nconst WATCHED_DIRECTORIES = [\"portal\", \"public\", \"src\"] as const;\nconst WATCHED_ROOT_FILES = new Set([\n \"index.html\",\n \"package.json\",\n \"tsconfig.json\",\n \"vite.config.cjs\",\n \"vite.config.cts\",\n \"vite.config.js\",\n \"vite.config.mjs\",\n \"vite.config.mts\",\n \"vite.config.ts\",\n]);\n\n/**\n * Restrict Vite's root watcher to files that can affect a portal preview.\n * Directory paths themselves must remain visible to Chokidar so it can\n * descend into them and discover their files.\n */\nexport function createPortalWatchIgnored(cwd: string) {\n const projectRoot = resolve(cwd);\n\n return (filePath: string): boolean => {\n const absolutePath = isAbsolute(filePath)\n ? filePath\n : resolve(projectRoot, filePath);\n const projectPath = relative(projectRoot, absolutePath);\n\n if (projectPath === \"\") return false;\n if (projectPath === \"..\" || projectPath.startsWith(`..${sep}`)) return true;\n\n const [topLevelPath] = projectPath.split(sep);\n if (WATCHED_DIRECTORIES.some((directory) => topLevelPath === directory)) {\n return false;\n }\n\n const isEnvironmentFile =\n projectPath === \".env\" || projectPath.startsWith(\".env.\");\n\n return !WATCHED_ROOT_FILES.has(projectPath) && !isEnvironmentFile;\n };\n}\n\nexport function portalDevServerOptions(\n port: number,\n exposeToNetwork: boolean,\n cwd = process.cwd(),\n) {\n return {\n port,\n host: exposeToNetwork ? true : undefined,\n watch: {\n ignored: createPortalWatchIgnored(cwd),\n },\n };\n}\n\n/**\n * Check if the portal directory exists and has content files.\n * Returns true if at minimum `portal/definition.json` exists.\n */\nfunction hasPortalContent(cwd: string): boolean {\n return existsSync(join(cwd, PORTAL_DIR, \"definition.json\"));\n}\n\n/**\n * Preflight check: does `<cwd>/node_modules` actually contain what this\n * command is about to `import()` (`vite`) and inject into the config\n * (`@fluid-app/portal-sdk`, pulled in transitively by the vite plugins)?\n *\n * Without this, a project whose install failed or was never run (no\n * `node_modules` at all) produces a cascade of confusing failures instead\n * of one clear message: `createServer()` below throws \"failed to load\n * config from .../vite.config.ts\", we catch that and warn \"falling back to\n * `vite`\", and the fallback then dies with a raw\n * `ERR_MODULE_NOT_FOUND: Cannot find package 'vite'` — never once saying\n * the actual problem is a missing `pnpm install`.\n */\nexport function checkDependenciesInstalled(\n cwd: string,\n): { ok: true } | { ok: false; message: string } {\n const nodeModulesPath = join(cwd, \"node_modules\");\n const hasVite = existsSync(join(nodeModulesPath, \"vite\"));\n const hasPortalSdk = existsSync(\n join(nodeModulesPath, \"@fluid-app\", \"portal-sdk\"),\n );\n\n if (existsSync(nodeModulesPath) && hasVite && hasPortalSdk) {\n return { ok: true };\n }\n\n return {\n ok: false,\n message:\n `Dependencies aren't installed in ${cwd} — run \\`pnpm install\\`, ` +\n \"then retry.\",\n };\n}\n\n/**\n * Attempt to auto-pull portal content by invoking the pull command's action.\n * Falls back to a helpful error message if pull is not possible.\n */\nasync function autoPull(cwd: string): Promise<boolean> {\n console.log();\n console.log(\n chalk.yellow(\"No portal/ directory found.\") +\n \" Attempting to pull content...\",\n );\n console.log();\n\n try {\n // Dynamically import the pull command to avoid circular deps at module level.\n // Auth is handled internally by the pull command via stored CLI credentials\n // (getAuthToken / getActiveProfile), so no explicit token args are needed.\n // Note: the pull command may call process.exit(1) on failure (e.g. auth\n // errors), which will terminate the process rather than throwing. Use\n // --skip-pull to bypass this if auto-pull causes issues.\n const { pullCommand } = await import(\"./pull.js\");\n\n await pullCommand.parseAsync([], { from: \"user\" });\n\n // Verify content was pulled\n return hasPortalContent(cwd);\n } catch (err) {\n console.log();\n console.log(\n chalk.red(\"Auto-pull failed: \") +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n console.log(\n \"Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" manually to set up local content.\",\n );\n console.log();\n return false;\n }\n}\n\nexport const devCommand: Command = new Command(\"dev\")\n .description(\"Start the development server with local portal content serving\")\n .option(\"-p, --port <port>\", \"Port to run the dev server on\", \"5173\")\n .option(\"--host\", \"Expose the dev server to the network\")\n .option(\"--skip-pull\", \"Skip auto-pull if portal/ directory is missing\")\n .action(async (options: DevOptions & { skipPull?: boolean }) => {\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 // ── Dependency preflight ──────────────────────────────────────────\n // Must run before anything below attempts to load `vite` or the\n // project's vite.config — those failures are confusing (stack\n // traces, ERR_MODULE_NOT_FOUND) where this one is not.\n const depsCheck = checkDependenciesInstalled(cwd);\n if (!depsCheck.ok) {\n console.error(chalk.red(`Error: ${depsCheck.message}`));\n process.exit(1);\n }\n\n // ── Auto-pull check ────────────────────────────────────────────────\n if (!hasPortalContent(cwd) && !options.skipPull) {\n const pulled = await autoPull(cwd);\n if (!pulled) {\n console.error(\n chalk.red(\"Cannot start dev server without portal content.\"),\n );\n console.error(\n chalk.yellow(\n \"Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" to download content first.\",\n ),\n );\n process.exit(1);\n }\n }\n\n if (hasPortalContent(cwd)) {\n console.log();\n console.log(\n chalk.green(\"Portal dev mode: \") +\n \"local content from \" +\n chalk.cyan(\"portal/\") +\n \" will be served\",\n );\n console.log(\n chalk.gray(\n \" Manifest requests intercepted at /api/app/manifest (+ /api/fluid_os/definitions/active)\",\n ),\n );\n console.log(\n chalk.gray(\" File changes in portal/ will trigger a page reload\"),\n );\n }\n\n console.log();\n console.log(chalk.bold(\"Starting development server...\"));\n console.log();\n\n // Fallback args for the degraded path (project's own `vite`).\n const viteArgs = [\"vite\"];\n if (options.port) viteArgs.push(\"--port\", String(options.port));\n if (options.host) viteArgs.push(\"--host\");\n\n // Start Vite programmatically and INJECT the portal dev plugins here, so\n // a portal never has to wire portalDevPlugin/backendDevPlugin into its own\n // vite.config — this command \"just serves the local portal/\". The user's\n // vite.config (react, tailwind, build output) is still loaded and merged\n // underneath. Falls back to spawning the project's `vite` if the\n // programmatic path fails, so behavior degrades rather than breaks.\n // Number.isFinite (not `|| 5173`) so an explicit `--port 0` (\"pick a free\n // port\") is honored rather than coerced back to the default.\n const parsedPort = Number(options.port);\n const port = Number.isFinite(parsedPort) ? parsedPort : 5173;\n\n try {\n const { createServer } = await import(\"vite\");\n const { portalDevPlugin } =\n await import(\"../vite-plugin/portal-dev-plugin.js\");\n const { backendDevPlugin } =\n await import(\"../vite-plugin/backend-dev-plugin.js\");\n // Serves this project's own widgets to preview hosts before they are\n // deployed, so iterating on a widget never requires publishing it.\n const { portalWidgetDevPlugin } =\n await import(\"../vite-plugin/portal-widget-dev-plugin.js\");\n\n // No inline `root` or `server.proxy` here: inline config takes\n // precedence over the project's vite.config, so setting them would\n // silently override a project's own root (e.g. root: \"web\") or a\n // local/staging /api proxy. portalDevPlugin's config() hook provides\n // the /api proxy fallback — resolved from the project's env, yielding\n // to any proxy the project already defines.\n const server = await createServer({\n configFile: viteConfigPath,\n envPrefix: [\"VITE_\", \"NEXT_PUBLIC_\"],\n plugins: [\n portalDevPlugin(),\n backendDevPlugin(),\n ...portalWidgetDevPlugin(),\n ],\n server: portalDevServerOptions(port, options.host === true, cwd),\n });\n\n await server.listen();\n server.printUrls();\n\n const shutdown = () => {\n void server.close().finally(() => process.exit(0));\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Keep the process alive until a signal tears the server down.\n await new Promise<never>(() => {});\n } catch (error) {\n console.warn(\n chalk.yellow(\n \"Could not start the enriched dev server; falling back to `vite`. \" +\n \"Local portal content requires portalDevPlugin in vite.config for the fallback path.\",\n ),\n error instanceof Error ? error.message : String(error),\n );\n try {\n await execa(\"pnpm\", viteArgs, { cwd, stdio: \"inherit\" });\n } catch (fallbackErr) {\n // execa v8 sets `signal` (not `code`) when killed by a signal. Treat\n // both SIGINT and SIGTERM as clean shutdowns, matching the\n // programmatic path above.\n const execaError = fallbackErr as { signal?: string };\n if (execaError.signal === \"SIGINT\" || execaError.signal === \"SIGTERM\") {\n return;\n }\n console.error(chalk.red(\"Development server exited with an error\"));\n process.exit(1);\n }\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\n await execa(\"pnpm\", [\"run\", \"build\"], {\n cwd,\n stdio: \"pipe\",\n });\n\n spinner.succeed(\"Build completed\");\n\n const outDir = options.outDir ?? \"dist\";\n console.log();\n console.log(`Output written to ${chalk.cyan(outDir)}/`);\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","import type { WidgetSchema } from \"../types/widget-schema\";\nimport type { ContainerWidgetBehavior } from \"./container-types\";\nimport type { WidgetManifest } from \"./widget-manifest\";\n\n/**\n * Registry mapping widget types to their container behaviors\n */\nexport type ContainerRegistry = Map<string, ContainerWidgetBehavior>;\n\n/**\n * Default container registry with LayoutWidget and ContainerWidget\n */\nexport const DEFAULT_CONTAINER_REGISTRY: ContainerRegistry = new Map([\n [\n \"LayoutWidget\",\n {\n isContainer: true,\n getChildren: (props) => (props.children as (WidgetSchema | null)[]) || [],\n setChildren: (props, children) => ({ ...props, children }),\n },\n ],\n [\n \"CardWidget\",\n {\n isContainer: true,\n getChildren: (props) => (props.children as (WidgetSchema | null)[]) || [],\n setChildren: (props, children) => ({ ...props, children }),\n },\n ],\n [\n \"ContainerWidget\",\n {\n isContainer: true,\n getChildren: (props) => (props.children as (WidgetSchema | null)[]) || [],\n setChildren: (props, children) => ({ ...props, children }),\n },\n ],\n]);\n\n/**\n * Check if a widget type is a container\n */\nexport function isContainerWidget(\n widgetType: string,\n containerRegistry: ContainerRegistry,\n): boolean {\n return containerRegistry.has(widgetType);\n}\n\n/**\n * Get children from a container widget\n */\nexport function getContainerChildren(\n widget: WidgetSchema,\n containerRegistry: ContainerRegistry,\n): (WidgetSchema | null)[] {\n const behavior = containerRegistry.get(widget.type);\n if (!behavior) return [];\n return behavior.getChildren(widget.props);\n}\n\n/**\n * Create updated widget with new children\n */\nexport function setContainerChildren(\n widget: WidgetSchema,\n children: (WidgetSchema | null)[],\n containerRegistry: ContainerRegistry,\n): WidgetSchema {\n const behavior = containerRegistry.get(widget.type);\n if (!behavior) return widget;\n\n return {\n ...widget,\n props: behavior.setChildren(widget.props, children),\n };\n}\n\n/**\n * Register a new container widget type\n */\nexport function createContainerRegistry(\n entries: [string, ContainerWidgetBehavior][] = [],\n): ContainerRegistry {\n return new Map([...DEFAULT_CONTAINER_REGISTRY, ...entries]);\n}\n\n/**\n * Creates a container registry from widget manifests.\n * Extracts container behaviors from manifests that define them,\n * then merges with defaults via createContainerRegistry().\n */\nexport function createContainerRegistryFromManifests(\n manifests: WidgetManifest[],\n): ContainerRegistry {\n const containerEntries: [string, ContainerWidgetBehavior][] = manifests\n .filter((m) => m.container != null)\n .map((m) => [m.type, m.container!]);\n return createContainerRegistry(containerEntries);\n}\n","/**\n * Cross-reference validation and change categorization utilities for the push command.\n *\n * Extracted into a standalone utility so that pure logic can be tested\n * without pulling in CLI dependencies (ora, chalk, prompts, etc.).\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join, basename, relative, sep } from \"node:path\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\n\nimport {\n DEFAULT_CONTAINER_REGISTRY,\n isContainerWidget,\n} from \"@fluid-app/portal-core/registries\";\nimport {\n validatePortalResource,\n type PortalResourceKind,\n} from \"@fluid-app/portal-definition-schema\";\n\nimport type { PortalMappings } from \"./mappings.js\";\nimport type { SnapshotDiff } from \"./snapshot.js\";\nimport type {\n LocalNavigation,\n LocalNavigationItem,\n LocalProfile,\n LocalScreen,\n} from \"./transform.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Categorize changed files by resource type. */\nexport interface CategorizedChanges {\n readonly screens: { new: string[]; changed: string[]; deleted: string[] };\n readonly themes: { new: string[]; changed: string[]; deleted: string[] };\n readonly navigations: { new: string[]; changed: string[]; deleted: string[] };\n readonly profiles: { new: string[]; changed: string[]; deleted: string[] };\n}\n\n/** A validation error found during cross-reference checking. */\nexport interface ValidationError {\n readonly file: string;\n readonly message: string;\n}\n\n/**\n * A non-blocking advisory. Same shape as {@link ValidationError}, kept distinct\n * so a caller cannot accidentally treat one as the other — warnings must never\n * fail a push.\n */\nexport interface ValidationWarning {\n readonly file: string;\n readonly message: string;\n}\n\nconst RESOURCE_DIRECTORIES = [\n [\"screen\", \"screens\"],\n [\"theme\", \"themes\"],\n [\"navigation\", \"navigations\"],\n [\"profile\", \"profiles\"],\n] as const satisfies readonly (readonly [PortalResourceKind, string])[];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Extract the slug from a file path (e.g., \"screens/home.json\" -> \"home\").\n */\nexport function slugFromPath(filePath: string): string {\n return basename(filePath, \".json\");\n}\n\n/**\n * Extract the resource type directory from a file path (e.g., \"screens/home.json\" -> \"screens\").\n */\nfunction resourceTypeFromPath(\n filePath: string,\n): \"screens\" | \"themes\" | \"navigations\" | \"profiles\" | null {\n const dir = filePath.split(\"/\")[0];\n if (\n dir === \"screens\" ||\n dir === \"themes\" ||\n dir === \"navigations\" ||\n dir === \"profiles\"\n ) {\n return dir;\n }\n return null;\n}\n\n/**\n * Read and parse a JSON file from the portal directory.\n */\nasync function readPortalFile<T>(\n portalDir: string,\n relativePath: string,\n): Promise<T> {\n const filePath = join(portalDir, relativePath);\n const content = await readFile(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n}\n\nfunction collectJsonFiles(\n directory: string,\n baseDirectory = directory,\n): string[] {\n const files: string[] = [];\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n const absolutePath = join(directory, entry.name);\n const isDirectory = entry.isSymbolicLink()\n ? statSync(absolutePath).isDirectory()\n : entry.isDirectory();\n if (isDirectory) {\n files.push(...collectJsonFiles(absolutePath, baseDirectory));\n } else if (entry.name.endsWith(\".json\")) {\n files.push(relative(baseDirectory, absolutePath).split(sep).join(\"/\"));\n }\n }\n return files.sort();\n}\n\n/** Validate every managed local portal file against the canonical schemas. */\nexport async function validatePortalSchemas(\n portalDir: string,\n): Promise<ValidationError[]> {\n const files: Array<{ kind: PortalResourceKind; file: string }> = [];\n const definitionPath = join(portalDir, \"definition.json\");\n if (existsSync(definitionPath)) {\n files.push({ kind: \"definition\", file: \"definition.json\" });\n } else {\n return [\n {\n file: \"definition.json\",\n message: \"File is required by the canonical portal schema.\",\n },\n ];\n }\n\n for (const [kind, directory] of RESOURCE_DIRECTORIES) {\n const absoluteDirectory = join(portalDir, directory);\n if (!existsSync(absoluteDirectory)) continue;\n for (const file of collectJsonFiles(absoluteDirectory)) {\n files.push({ kind, file: `${directory}/${file}` });\n }\n }\n\n const errors: ValidationError[] = [];\n for (const { kind, file } of files) {\n let value: unknown;\n try {\n value = await readPortalFile<unknown>(portalDir, file);\n } catch {\n errors.push({ file, message: \"File is not valid JSON.\" });\n continue;\n }\n\n const result = validatePortalResource(kind, value);\n if (!result.success) {\n for (const issue of result.issues) {\n errors.push({\n file,\n message: `${issue.path} ${issue.message}`,\n });\n }\n }\n }\n return errors;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Change categorization\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Categorize a snapshot diff into resource-type-specific change lists.\n */\nexport function categorizeChanges(diff: SnapshotDiff): CategorizedChanges {\n const result: CategorizedChanges = {\n screens: { new: [], changed: [], deleted: [] },\n themes: { new: [], changed: [], deleted: [] },\n navigations: { new: [], changed: [], deleted: [] },\n profiles: { new: [], changed: [], deleted: [] },\n };\n\n for (const file of diff.new) {\n const type = resourceTypeFromPath(file);\n if (type) result[type].new.push(file);\n }\n for (const file of diff.changed) {\n const type = resourceTypeFromPath(file);\n if (type) result[type].changed.push(file);\n }\n for (const file of diff.deleted) {\n const type = resourceTypeFromPath(file);\n if (type) result[type].deleted.push(file);\n }\n\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Screen widget nodes\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Duplicate IDs require tree-wide state and remain a semantic validation. */\nfunction collectWidgetNodeErrors(nodes: readonly unknown[]): string[] {\n return walkWidgetNodes(nodes, \"component_tree\", new Map());\n}\n\nfunction walkWidgetNodes(\n nodes: readonly unknown[],\n path: string,\n seenIds: Map<string, string>,\n): string[] {\n const errors: string[] = [];\n\n nodes.forEach((node, index) => {\n const at = `${path}[${index}]`;\n\n if (node === null || typeof node !== \"object\" || Array.isArray(node))\n return;\n\n const record = node as Record<string, unknown>;\n if (typeof record.id === \"string\" && record.id.trim().length > 0) {\n const firstClaim = seenIds.get(record.id);\n if (firstClaim === undefined) {\n seenIds.set(record.id, at);\n } else {\n errors.push(\n `${at} reuses the id ${JSON.stringify(record.id)}, already used by ${firstClaim}. Ids must be unique within a screen — the builder resolves a widget by id and takes the first match, so duplicates make select, delete and drag-drop act on the wrong widget.`,\n );\n }\n }\n\n const props = record.props;\n if (typeof props !== \"object\" || props === null || Array.isArray(props)) {\n return;\n }\n const children = (props as Record<string, unknown>).children;\n if (Array.isArray(children)) {\n errors.push(\n ...walkWidgetNodes(children, `${at}.props.children`, seenIds),\n );\n }\n });\n\n return errors;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cross-reference validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validate that all cross-references between local portal files are valid.\n *\n * Checks:\n * - Widget IDs remain unique within each changed screen\n * - Navigation items' \"screen\" slugs reference existing screen files or mappings\n * - Profile \"navigation\" and \"mobile_navigation\" slugs reference existing nav files or mappings\n * - Profile \"themes\" slugs reference existing theme files or mappings\n */\nexport async function validateCrossReferences(\n portalDir: string,\n mappings: PortalMappings,\n changes: CategorizedChanges,\n): Promise<ValidationError[]> {\n const errors: ValidationError[] = [];\n\n // Build sets of valid slugs (existing mappings + local files on disk)\n const validScreenSlugs = buildValidSlugsSet(portalDir, \"screens\", mappings);\n const validNavSlugs = buildValidSlugsSet(portalDir, \"navigations\", mappings);\n const validThemeSlugs = buildValidSlugsSet(portalDir, \"themes\", mappings);\n\n // Remove deleted resource slugs from valid sets\n for (const file of changes.screens.deleted) {\n validScreenSlugs.delete(slugFromPath(file));\n }\n for (const file of changes.navigations.deleted) {\n validNavSlugs.delete(slugFromPath(file));\n }\n for (const file of changes.themes.deleted) {\n validThemeSlugs.delete(slugFromPath(file));\n }\n\n const screenFilesToCheck = [\n ...changes.screens.new,\n ...changes.screens.changed,\n ];\n for (const file of screenFilesToCheck) {\n try {\n const screen = await readPortalFile<LocalScreen>(portalDir, file);\n if (Array.isArray(screen.component_tree)) {\n for (const error of collectWidgetNodeErrors(screen.component_tree)) {\n errors.push({ file, message: error });\n }\n }\n } catch {\n errors.push({ file, message: \"Failed to read screen file\" });\n }\n }\n\n // Validate navigation files (new + changed)\n const navFilesToCheck = [\n ...changes.navigations.new,\n ...changes.navigations.changed,\n ];\n for (const file of navFilesToCheck) {\n try {\n const nav = await readPortalFile<LocalNavigation>(portalDir, file);\n if (Array.isArray(nav.navigation_items)) {\n validateNavigationItems(\n nav.navigation_items,\n file,\n validScreenSlugs,\n errors,\n );\n }\n } catch {\n errors.push({ file, message: \"Failed to read navigation file\" });\n }\n }\n\n // Validate profile files (new + changed)\n const profileFilesToCheck = [\n ...changes.profiles.new,\n ...changes.profiles.changed,\n ];\n for (const file of profileFilesToCheck) {\n try {\n const profile = await readPortalFile<LocalProfile>(portalDir, file);\n validateProfileReferences(\n profile,\n file,\n validNavSlugs,\n validThemeSlugs,\n errors,\n );\n } catch {\n errors.push({ file, message: \"Failed to read profile file\" });\n }\n }\n\n if (changes.screens.deleted.length > 0) {\n const alreadyChecked = new Set(navFilesToCheck);\n for (const file of listResourceFiles(portalDir, \"navigations\")) {\n if (alreadyChecked.has(file)) continue;\n const nav = await readPortalFileOrNull<LocalNavigation>(portalDir, file);\n if (!nav || !Array.isArray(nav.navigation_items)) continue;\n validateNavigationItems(\n nav.navigation_items,\n file,\n validScreenSlugs,\n errors,\n );\n }\n }\n\n if (\n changes.navigations.deleted.length > 0 ||\n changes.themes.deleted.length > 0\n ) {\n const alreadyChecked = new Set(profileFilesToCheck);\n for (const file of listResourceFiles(portalDir, \"profiles\")) {\n if (alreadyChecked.has(file)) continue;\n const profile = await readPortalFileOrNull<LocalProfile>(portalDir, file);\n if (!profile) continue;\n validateProfileReferences(\n profile,\n file,\n validNavSlugs,\n validThemeSlugs,\n errors,\n );\n }\n }\n\n return errors;\n}\n\nfunction listResourceFiles(\n portalDir: string,\n resourceType: \"navigations\" | \"profiles\",\n): string[] {\n const directory = join(portalDir, resourceType);\n if (!existsSync(directory)) return [];\n try {\n return collectJsonFiles(directory).map((file) => `${resourceType}/${file}`);\n } catch {\n return [];\n }\n}\n\nasync function readPortalFileOrNull<T>(\n portalDir: string,\n relativePath: string,\n): Promise<T | null> {\n try {\n return await readPortalFile<T>(portalDir, relativePath);\n } catch {\n return null;\n }\n}\n\nfunction validateProfileReferences(\n profile: LocalProfile,\n file: string,\n validNavSlugs: Set<string>,\n validThemeSlugs: Set<string>,\n errors: ValidationError[],\n): void {\n if (profile.navigation && !validNavSlugs.has(profile.navigation)) {\n errors.push({\n file,\n message: `References navigation \"${profile.navigation}\" which does not exist`,\n });\n }\n\n if (\n profile.mobile_navigation &&\n !validNavSlugs.has(profile.mobile_navigation)\n ) {\n errors.push({\n file,\n message: `References mobile_navigation \"${profile.mobile_navigation}\" which does not exist`,\n });\n }\n\n for (const themeSlug of profile.themes ?? []) {\n if (!validThemeSlugs.has(themeSlug)) {\n errors.push({\n file,\n message: `References theme \"${themeSlug}\" which does not exist`,\n });\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Screen structure advisories\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Warns about screens the admin visual builder will not accept drops on.\n *\n * Only container widgets receive the builder's edit-mode child-management\n * callbacks (`onAddChild`, child select/delete/duplicate), so a screen whose\n * `component_tree` has no container at the top level renders perfectly — in\n * local preview and in the live portal — while offering no drop zones at all.\n * A human opening it in the builder cannot drag anything onto the page.\n *\n * That combination (correct everywhere you can look, broken in the one place\n * you cannot) is why this is worth reporting at push time. It is the most\n * likely defect in a hand- or agent-authored screen.\n *\n * Deliberately a WARNING, never an error. Screens without a container root are\n * valid definitions that render correctly, and a repository may hold ones that\n * predate this check; failing their push would break working portals to enforce\n * an authoring convention.\n *\n * Silent on anything it cannot parse. Malformed and unreadable screens are\n * already reported as hard errors by {@link validateCrossReferences}, and a\n * second complaint about a file that never parsed would be noise.\n */\nexport async function validateScreenStructure(\n portalDir: string,\n changes: CategorizedChanges,\n): Promise<ValidationWarning[]> {\n const warnings: ValidationWarning[] = [];\n const files = [...changes.screens.new, ...changes.screens.changed];\n\n for (const file of files) {\n let screen: LocalScreen;\n try {\n screen = await readPortalFile<LocalScreen>(portalDir, file);\n } catch {\n continue;\n }\n\n const tree = screen.component_tree;\n if (!Array.isArray(tree) || tree.length === 0) continue;\n\n // Multi-root is a hard failure raised by `validateCrossReferences`, which\n // exits before this runs. Skipped rather than re-reported: an advisory\n // about drop zones on a screen that is already being refused would only\n // compete with the real reason.\n if (tree.length !== 1) continue;\n\n const root = tree[0];\n const rootType =\n root && typeof root === \"object\"\n ? (root as { type?: unknown }).type\n : undefined;\n const hasContainerRoot =\n typeof rootType === \"string\" &&\n isContainerWidget(rootType, CONTAINER_REGISTRY);\n if (hasContainerRoot) continue;\n\n warnings.push({\n file,\n message:\n `No container at the top of \"component_tree\", so the admin builder will show no drop zones on this screen. ` +\n `Wrap the widgets in a ${CONTAINER_TYPE_LIST} node and move them into its \"props.children\".`,\n });\n }\n\n return warnings;\n}\n\n/**\n * Shared with the portal runtime so this check cannot drift from the behaviour\n * it predicts. `DEFAULT_CONTAINER_REGISTRY` is the same map the builder consults\n * via `isContainerWidget`.\n */\nconst CONTAINER_REGISTRY = DEFAULT_CONTAINER_REGISTRY;\n\n/** Rendered into the warning so it names the real options, not a stale list. */\nconst CONTAINER_TYPE_LIST = [...DEFAULT_CONTAINER_REGISTRY.keys()]\n .map((type) => `\"${type}\"`)\n .join(\" / \");\n\n/**\n * Build a set of valid slugs for a resource type by combining\n * existing mapping slugs with local file slugs on disk.\n */\nfunction buildValidSlugsSet(\n portalDir: string,\n resourceType: \"screens\" | \"themes\" | \"navigations\" | \"profiles\",\n mappings: PortalMappings,\n): Set<string> {\n const slugs = new Set<string>();\n\n // Add all slugs from mappings\n for (const slug of Object.keys(mappings[resourceType])) {\n slugs.add(slug);\n }\n\n // Add slugs from local files on disk\n const dir = join(portalDir, resourceType);\n if (existsSync(dir)) {\n try {\n const entries = readdirSync(dir);\n for (const entry of entries) {\n if (entry.endsWith(\".json\")) {\n slugs.add(basename(entry, \".json\"));\n }\n }\n } catch {\n // Directory doesn't exist or can't be read — skip\n }\n }\n\n return slugs;\n}\n\n/**\n * Recursively validate navigation item screen references.\n */\nfunction validateNavigationItems(\n items: LocalNavigationItem[],\n file: string,\n validScreenSlugs: Set<string>,\n errors: ValidationError[],\n): void {\n for (const item of items) {\n if (item.screen && !validScreenSlugs.has(item.screen)) {\n errors.push({\n file,\n message: `Navigation item \"${item.label ?? \"(unlabeled)\"}\" references screen \"${item.screen}\" which does not exist`,\n });\n }\n if (item.children && item.children.length > 0) {\n validateNavigationItems(item.children, file, validScreenSlugs, errors);\n }\n }\n}\n","/**\n * Portal git-credential adapter (the \"invisible git\" seam).\n *\n * Mints a short-lived (~1h) remote URL with an embedded installation\n * token for the portal's provisioned GitHub repo. The URL is used\n * immediately for one git op and discarded — NEVER persisted or logged.\n *\n * The backend endpoint (GitSync phase 006, portal adapter) mirrors the\n * widget contract exactly:\n *\n * POST <endpoint> -> 201 { git_credential: { remote_url, expires_at }, status, meta }\n * errors: 409 not_provisioned/not_connected, 502 vendor_error, 422 else\n * { error_message, errors: { type: [type] }, meta }\n *\n * The exact path is still being finalized as 006 lands, so it lives in\n * one constant here. Until the endpoint ships to prod a request 404s —\n * which we surface as `unavailable` so callers degrade gracefully\n * (warn + skip the git sync, proceed with the normal content push).\n */\n\n/** Minted credential — token embedded in `remote_url`. Treat as secret. */\nimport { ApiError } from \"@fluid-app/api-client-core\";\nimport { createFluidOsHeyApiClient } from \"@fluid-app/fluidos-api-client\";\nimport { fluidOsV0DefinitionGitCredentialCreate } from \"@fluid-app/fluidos-api-client/sdk\";\n\nexport interface PortalGitCredential {\n readonly remote_url: string;\n readonly expires_at: string;\n}\n\n/**\n * Outcome of a credential request. `unavailable` means \"no repo to sync\"\n * (endpoint missing / not provisioned / not connected). Its `retryable`\n * flag distinguishes backend states that resolve on their own from ones\n * that never will.\n *\n * Both `not_provisioned` and `not_connected` are retryable. The first\n * self-heals because the credential request itself triggers provisioning;\n * the second is the state a brand-new definition sits in for its first\n * few seconds, while the Sidekiq job that creates the GitHub repo runs.\n * (An earlier revision read `not_connected` as a durable \"this company's\n * Git integration isn't connected\" — that was a misdiagnosis, and it made\n * every newly created portal give up before its repo was ready.)\n *\n * `repoStatus` carries the underlying GitSync repo status when the API\n * reports one; `disconnected` is the one value a retry can never fix.\n * `conflictType` carries the raw 409 conflict type so callers can tailor\n * messaging. `error` is an unexpected transport or provider failure.\n */\nexport type GitCredentialOutcome =\n | { readonly status: \"ok\"; readonly credential: PortalGitCredential }\n | {\n readonly status: \"unavailable\";\n readonly reason: string;\n readonly retryable: boolean;\n readonly conflictType?: string;\n readonly repoStatus?: string;\n }\n | { readonly status: \"error\"; readonly reason: string };\n\n/**\n * Build the portal git-credential path for a definition id.\n *\n * Matches GitSync Phase 006 (fluid-commerce/fluid #19596): a member\n * route on the company-scoped definitions resource —\n * `POST /api/company/fluid_os/definitions/:id/git_credential`.\n */\nexport function portalGitCredentialPath(definitionId: number): string {\n return `/api/company/fluid_os/definitions/${definitionId}/git_credential`;\n}\n\nexport interface CreatePortalGitCredentialOptions {\n readonly baseUrl: string;\n readonly token: string;\n readonly definitionId: number;\n /** Injectable for tests; defaults to global fetch. */\n readonly fetchImpl?: typeof fetch;\n /** Deadline for one API request; defaults to 15 seconds. */\n readonly requestTimeoutMs?: number;\n}\n\nexport interface PortalGitCredentialRetryEvent {\n readonly attempt: number;\n readonly totalAttempts: number;\n readonly reason: string;\n}\n\nexport interface CreatePortalGitCredentialWithRetryOptions extends CreatePortalGitCredentialOptions {\n readonly retryDelaysMs?: readonly number[];\n readonly sleepImpl?: (delayMs: number) => Promise<void>;\n readonly onRetry?: (event: PortalGitCredentialRetryEvent) => void;\n}\n\n// A freshly-created definition's git repo is provisioned asynchronously by the\n// backend, so create-then-clone can race that provisioning. The retry is the\n// readiness wait — sized to about 55s so new portals usually clone with Git\n// history instead of falling back before the repository is ready.\nconst DEFAULT_RETRY_DELAYS_MS = [\n 500, 1_000, 2_000, 4_000, 8_000, 10_000, 15_000, 15_000,\n] as const;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 15_000;\n// Both 409 conflict types are worth waiting on. `not_provisioned` self-heals:\n// the credential request itself triggers provisioning. `not_connected` is the\n// state a brand-new definition presents — the backend inserts the repo row\n// synchronously during create (status `provisioning`) and does the GitHub work\n// in a Sidekiq job, which lands a few seconds later.\nconst RETRYABLE_CONFLICT_TYPES = new Set([\"not_provisioned\", \"not_connected\"]);\n// The one repo status a retry can never resolve: `disconnected` repos were\n// deliberately torn down and the backend refuses to re-adopt them. Absent on\n// backends that don't report the status yet — treat that as \"keep waiting\".\n// Mirrors DURABLE_REPO_STATUSES in mist-desktop's portal-clone-preflight.ts.\nconst DURABLE_REPO_STATUSES = new Set([\"disconnected\"]);\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction firstString(value: unknown): string | undefined {\n if (Array.isArray(value)) return nonEmptyString(value[0]);\n return nonEmptyString(value);\n}\n\nfunction apiErrorBody(error: ApiError): unknown {\n if (\n error.data !== null &&\n typeof error.data === \"object\" &&\n (\"error\" in error.data ||\n \"error_message\" in error.data ||\n \"errors\" in error.data)\n ) {\n return error.data;\n }\n return { error_message: error.message, errors: error.data };\n}\n\nfunction extractErrorDetails(\n body: unknown,\n fallback: string,\n): {\n readonly reason: string;\n readonly failureType?: string;\n readonly repoStatus?: string;\n} {\n if (!body || typeof body !== \"object\") return { reason: fallback };\n\n const envelope = body as {\n readonly error_message?: unknown;\n readonly error?: unknown;\n readonly errors?: unknown;\n };\n const error =\n envelope.error && typeof envelope.error === \"object\"\n ? (envelope.error as {\n readonly message?: unknown;\n readonly details?: unknown;\n })\n : undefined;\n const nestedDetails =\n error?.details && typeof error.details === \"object\"\n ? (error.details as {\n readonly type?: unknown;\n readonly repo_status?: unknown;\n })\n : undefined;\n const errors =\n envelope.errors && typeof envelope.errors === \"object\"\n ? (envelope.errors as {\n readonly type?: unknown;\n readonly repo_status?: unknown;\n })\n : undefined;\n const reason =\n nonEmptyString(envelope.error_message) ??\n nonEmptyString(error?.message) ??\n nonEmptyString(envelope.error) ??\n fallback;\n const failureType =\n firstString(errors?.type) ?? nonEmptyString(nestedDetails?.type);\n const repoStatus =\n firstString(errors?.repo_status) ??\n nonEmptyString(nestedDetails?.repo_status);\n\n return {\n reason,\n ...(failureType ? { failureType } : {}),\n ...(repoStatus ? { repoStatus } : {}),\n };\n}\n\nfunction sleep(delayMs: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n}\n\n/**\n * Retry clone-credential conflicts while the backend provisions or revives\n * the portal's repository. The first credential request is the trigger for\n * older definitions with no repo, so a single-shot clone would always fail\n * even though the repository becomes ready seconds later.\n */\nexport async function createPortalGitCredentialWithRetry(\n options: CreatePortalGitCredentialWithRetryOptions,\n): Promise<GitCredentialOutcome> {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;\n const sleepImpl = options.sleepImpl ?? sleep;\n const requestOptions: CreatePortalGitCredentialOptions = {\n baseUrl: options.baseUrl,\n token: options.token,\n definitionId: options.definitionId,\n ...(options.fetchImpl !== undefined\n ? { fetchImpl: options.fetchImpl }\n : {}),\n ...(options.requestTimeoutMs !== undefined\n ? { requestTimeoutMs: options.requestTimeoutMs }\n : {}),\n };\n let outcome = await createPortalGitCredential(requestOptions);\n\n for (const [index, delayMs] of retryDelaysMs.entries()) {\n if (outcome.status !== \"unavailable\" || !outcome.retryable) return outcome;\n\n options.onRetry?.({\n attempt: index + 2,\n totalAttempts: retryDelaysMs.length + 1,\n reason: outcome.reason,\n });\n await sleepImpl(delayMs);\n outcome = await createPortalGitCredential(requestOptions);\n }\n\n return outcome;\n}\n\n/**\n * Request a git credential for the portal definition's provisioned repo.\n *\n * Never throws — every failure maps to an `unavailable`/`error` outcome\n * so the invisible-git sync is strictly best-effort.\n */\nexport async function createPortalGitCredential(\n options: CreatePortalGitCredentialOptions,\n): Promise<GitCredentialOutcome> {\n const { baseUrl, token, definitionId } = options;\n const fetchImpl = options.fetchImpl ?? fetch;\n const requestTimeoutMs =\n options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const abortController = new AbortController();\n let requestTimedOut = false;\n const timeout = setTimeout(() => {\n requestTimedOut = true;\n abortController.abort();\n }, requestTimeoutMs);\n\n try {\n const client = createFluidOsHeyApiClient({\n baseUrl,\n fetch: fetchImpl,\n getAuthToken: () => token,\n });\n const response = await fluidOsV0DefinitionGitCredentialCreate({\n client,\n path: { id: definitionId },\n signal: abortController.signal,\n });\n\n const credential = response.git_credential;\n if (!credential?.remote_url) {\n return {\n status: \"error\",\n reason: \"git credential response missing remote_url\",\n };\n }\n\n return { status: \"ok\", credential };\n } catch (err) {\n if (requestTimedOut) {\n return {\n status: \"error\",\n reason: `git credential request timed out after ${requestTimeoutMs}ms`,\n };\n }\n\n if (err instanceof ApiError) {\n if (err.status === 404) {\n return {\n status: \"unavailable\",\n reason: \"git sync isn't available for portals on this Fluid API yet\",\n retryable: false,\n };\n }\n\n const details = extractErrorDetails(\n apiErrorBody(err),\n `git credential request failed (HTTP ${err.status})`,\n );\n if (err.status === 409) {\n const durable =\n details.repoStatus !== undefined &&\n DURABLE_REPO_STATUSES.has(details.repoStatus);\n return {\n status: \"unavailable\",\n reason: details.reason,\n retryable:\n !durable &&\n details.failureType !== undefined &&\n RETRYABLE_CONFLICT_TYPES.has(details.failureType),\n ...(details.failureType !== undefined\n ? { conflictType: details.failureType }\n : {}),\n ...(details.repoStatus !== undefined\n ? { repoStatus: details.repoStatus }\n : {}),\n };\n }\n return { status: \"error\", reason: details.reason };\n }\n\n return {\n status: \"error\",\n reason: err instanceof Error ? err.message : String(err),\n };\n } finally {\n clearTimeout(timeout);\n }\n}\n","/**\n * Invisible-git sync for `fluid portal push`.\n *\n * Before the normal content push, we best-effort mirror the local portal\n * project to its provisioned GitHub repo: fetch → fast-forward-pull if a\n * teammate pushed → auto-commit working-tree changes → push HEAD:main.\n * This mirrors `fluid mist push`, but is STRICTLY non-fatal: any problem\n * (no git, no repo, credential endpoint missing, diverged history,\n * network blip) degrades to a skip so the content push always proceeds.\n */\n\nimport type { CommitMessage } from \"@fluid-app/fluid-cli\";\nimport { gitSyncCommitSubject } from \"@fluid-app/fluid-cli\";\nimport {\n createPortalGitCredential,\n type CreatePortalGitCredentialOptions,\n} from \"../api/git-credential.js\";\nimport {\n ensureLocalStateExcluded,\n getOriginUrl,\n gitAdoptFetchHead,\n gitAheadBehindVsFetchHead,\n gitAutoCommit,\n gitFetch,\n gitInit,\n gitMergeFastForward,\n gitPush,\n gitRemoteBranchExists,\n hasCommits,\n hasCommonAncestorWithFetchHead,\n hasUncommittedChanges,\n isGitRepo,\n normalizedRepoIdentity,\n purgeLocalStateFromIndex,\n} from \"./git.js\";\n\n/** Subject for the index-only commit that untracks leaked Desktop state.\n * Kept distinct from the publish message so the history explains itself,\n * but in the canonical GitSync shape so it parses like every other\n * commit. Attributed to `Fluid` because it's housekeeping the CLI does\n * on its own initiative, not something the user asked for. */\nconst PURGE_COMMIT_MESSAGE = (): string =>\n gitSyncCommitSubject(\n \"Remove private Mist Desktop state from the portal repository\",\n null,\n );\n\nexport type PortalGitSyncResult =\n | { readonly status: \"skipped\"; readonly reason: string }\n | { readonly status: \"synced\"; readonly pushed: boolean }\n /** Local git state was already mutated (commit / fast-forward) and the\n * push then failed — the caller must NOT proceed with the content\n * push, or published artifacts drift from unsynced source. */\n | { readonly status: \"failed\"; readonly reason: string };\n\nexport interface PortalGitSyncOptions {\n readonly cwd: string;\n readonly baseUrl: string;\n readonly token: string;\n readonly definitionId: number;\n /** Literal subject, or a resolver handed the staged changes so it\n * can describe them (see `CommitMessage`). */\n readonly commitMessage: CommitMessage;\n readonly branch?: string;\n /** Injectable for tests. */\n readonly fetchImpl?: CreatePortalGitCredentialOptions[\"fetchImpl\"];\n}\n\n/**\n * Run the best-effort git sync. Returns a result describing what\n * happened; never throws. Callers surface `skipped` reasons as a dim\n * note and continue.\n */\nexport async function runPortalGitSync(\n options: PortalGitSyncOptions,\n): Promise<PortalGitSyncResult> {\n const { cwd, baseUrl, token, definitionId, commitMessage } = options;\n const branch = options.branch ?? \"main\";\n\n // Ensure a local repo exists — `fluid portal create` strips the\n // template's git history, so the first push initializes one and adopts\n // the provisioned remote's history below (the widget CLI's proven\n // first-push flow). A failed init falls back to the old skip.\n if (!(await isGitRepo(cwd)) && !(await gitInit(cwd))) {\n return { status: \"skipped\", reason: \"not a git repository\" };\n }\n\n // Scope the sync to the portal project before ANY tree inspection.\n // Mist Desktop writes its private per-machine state (`.mist-desktop/`:\n // chat transcripts, attachments, screenshots) into the project root it\n // opened. Left unexcluded it both dirties the cleanliness checks below\n // — producing a \"you have uncommitted changes, run git pull\" skip the\n // user can't resolve — and gets published wholesale by the auto-commit.\n await ensureLocalStateExcluded(cwd);\n\n // One credential for the whole sync — tokens live ~1h, far beyond a\n // push. Minting again mid-flow would open a degrade window AFTER the\n // auto-commit: a failure there reads as \"sync unavailable\" while the\n // fresh commit stays local and the content push carries on with\n // unsynced source. With a single mint, unavailability can only surface\n // before any local or remote mutation.\n const outcome = await createPortalGitCredential({\n baseUrl,\n token,\n definitionId,\n fetchImpl: options.fetchImpl,\n });\n if (outcome.status !== \"ok\") {\n return { status: \"skipped\", reason: outcome.reason };\n }\n const remoteUrl = outcome.credential.remote_url;\n\n // Identity guard: if this checkout already has an origin, it must be\n // the repo the credential was minted for. Without this, running\n // `fluid portal push` from an UNRELATED git repo would whole-tree\n // auto-commit that repo and push it to the portal's remote.\n const origin = await getOriginUrl(cwd);\n if (origin) {\n const originId = normalizedRepoIdentity(origin);\n const mintedId = normalizedRepoIdentity(remoteUrl);\n // Fail closed: an origin we can't normalize can't be verified, and\n // the downstream merge-base guard doesn't help when repos share\n // starter-template ancestry — skip rather than risk pushing another\n // project to the portal's remote.\n if (mintedId && originId !== mintedId) {\n return {\n status: \"skipped\",\n reason:\n `this directory's git origin (${originId ?? origin}) is not the ` +\n `portal's provisioned repository (${mintedId}) — are you in the ` +\n \"right project directory?\",\n };\n }\n }\n\n try {\n // Detect drift. When strictly behind with a CLEAN tree we can\n // fast-forward a teammate's commits in safely. We deliberately do NOT\n // stash-then-merge a dirty tree: a stash-pop conflict would leave the\n // checkout advanced with conflict markers while the content push\n // carried on. Instead, behind + dirty (and diverged) simply skip and\n // ask the user to reconcile in a terminal — the working tree is left\n // exactly as it was.\n // A freshly-initialized local repo (unborn HEAD) can't reconcile via\n // ahead/behind — and the backend provisions repos WITH an auto-init\n // or starter-template commit, so a fresh root commit would be\n // rejected as unrelated history. Adopt the remote history as the\n // local base instead; the auto-commit below then records the local\n // tree on top of it and the push fast-forwards.\n if (!(await hasCommits(cwd))) {\n if (await gitRemoteBranchExists(cwd, remoteUrl, branch)) {\n await gitFetch(cwd, remoteUrl, branch);\n await gitAdoptFetchHead(cwd);\n }\n // The adopted history may itself carry chats leaked by an older\n // CLI — purge before the (removal-suppressing) adoption commit.\n await purgeLocalStateFromIndex(cwd, PURGE_COMMIT_MESSAGE());\n if (await hasUncommittedChanges(cwd)) {\n // ADOPTION-ONLY semantics: stage new + modified files, never\n // removals. This directory may hold only a content subset (e.g.\n // populated by `fluid portal pull`, which syncs content but not\n // the repo), so a file absent locally is not a deletion — with\n // plain `add -A`, the first push would delete every remote-only\n // file from the provisioned repo. Local additions/edits win;\n // remote-only files are preserved on adoption. Deletions only\n // ever ship from full checkouts (the `add -A` path below).\n await gitAutoCommit(cwd, commitMessage, { ignoreRemovals: true });\n }\n return await pushCommitted(cwd, remoteUrl, branch);\n }\n\n await gitFetch(cwd, remoteUrl, branch);\n // Second identity guard, for checkouts without an origin: a repo\n // whose history shares no ancestor with the provisioned repo is some\n // OTHER project — never auto-commit and push it wholesale.\n if (!(await hasCommonAncestorWithFetchHead(cwd))) {\n return {\n status: \"skipped\",\n reason:\n \"this repository's history is unrelated to the portal's \" +\n \"provisioned repository — are you in the right project directory?\",\n };\n }\n const { ahead, behind } = await gitAheadBehindVsFetchHead(cwd);\n if (behind > 0 && ahead > 0) {\n return {\n status: \"skipped\",\n reason:\n \"local branch and origin have diverged — run `git pull --rebase` and retry\",\n };\n }\n if (behind > 0) {\n if (await hasUncommittedChanges(cwd)) {\n return {\n status: \"skipped\",\n reason:\n \"origin has newer commits and you have uncommitted changes — \" +\n \"run `git pull` and retry\",\n };\n }\n await gitMergeFastForward(cwd);\n }\n\n // Untrack chats leaked by an older CLI, UNCONDITIONALLY — a checkout\n // whose only problem is a tracked `.mist-desktop/` has a perfectly\n // clean working tree, so gating this on `hasUncommittedChanges`\n // below would skip it forever and leave the chats published. Runs\n // after every skip guard above, so a sync that bails still leaves\n // the repository untouched. No-op (and no commit) when nothing was\n // tracked, which is the overwhelmingly common case.\n await purgeLocalStateFromIndex(cwd, PURGE_COMMIT_MESSAGE());\n\n // Bundle working-tree edits into one commit so push has something to\n // send. No-op when the tree is already clean.\n if (await hasUncommittedChanges(cwd)) {\n await gitAutoCommit(cwd, commitMessage);\n }\n\n return await pushCommitted(cwd, remoteUrl, branch);\n } catch (err) {\n // Reconcile-phase failure (fetch / ls-remote / adopt / ff-merge /\n // commit). An ff-merge may already have advanced the checkout to\n // include a teammate's commits — that state is valid and self-heals,\n // but say so instead of pretending nothing happened.\n return {\n status: \"skipped\",\n reason: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * The push gets its own failure boundary: by this point the auto-commit\n * (and any fast-forward) has already mutated local git state. A push\n * failure here is FATAL to the content push (matching `fluid widget\n * push`): publishing artifacts whose source never reached the repo is\n * exactly the drift the invisible-git model exists to prevent. The\n * local commit is durable — rerunning push finds the branch ahead and\n * retries the sync.\n */\nasync function pushCommitted(\n cwd: string,\n remoteUrl: string,\n branch: string,\n): Promise<PortalGitSyncResult> {\n try {\n await gitPush(cwd, remoteUrl, branch);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n status: \"failed\",\n reason:\n \"your changes are committed locally but couldn't be pushed \" +\n `(${message}) — nothing was published; rerun \\`fluid portal push\\` ` +\n \"to retry\",\n };\n }\n return { status: \"synced\", pushed: true };\n}\n","/**\n * `GET /api/me` — the signed-in user, used to stamp portal git-sync\n * commits in the canonical GitSync format so history says who published.\n * See `@fluid-app/fluid-cli`'s git-sync/commit-subject.ts for the shape.\n *\n * Best-effort by contract: a failed, slow or malformed response resolves\n * to null and the commit is attributed to `Fluid` instead. Publishing is\n * essential; the stamp is not.\n */\n\nimport { gitSyncActorFromMe, resolveGitSyncActor } from \"@fluid-app/fluid-cli\";\nimport type { GitSyncActor } from \"@fluid-app/fluid-cli\";\n\nconst ME_TIMEOUT_MS = 5_000;\n\nexport async function fetchPortalActor(\n baseUrl: string,\n token: string,\n timeoutMs = ME_TIMEOUT_MS,\n): Promise<GitSyncActor | null> {\n // Served from the profile cache after the first push; the request\n // below only fires on a miss.\n return resolveGitSyncActor(() => requestActor(baseUrl, token, timeoutMs));\n}\n\nasync function requestActor(\n baseUrl: string,\n token: string,\n timeoutMs: number,\n): Promise<GitSyncActor | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const resp = await fetch(`${baseUrl.replace(/\\/+$/, \"\")}/api/me`, {\n headers: { Authorization: `Bearer ${token}`, Accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!resp.ok) return null;\n return gitSyncActorFromMe(await resp.json());\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n","import type {\n RemoteDomWidgetDefinition,\n RemoteDomWidgetPackageDescriptor,\n} from \"./remote-dom-widget-package\";\n\nexport const REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITIES = [\n \"networkAccess\",\n \"orders\",\n] as const;\n\nexport type RemoteWidgetGrantRequiredCapability =\n (typeof REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITIES)[number];\n\nexport const REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITY_VERSIONS = {\n networkAccess: \"1\",\n orders: \"1\",\n} as const satisfies Readonly<\n Record<RemoteWidgetGrantRequiredCapability, string>\n>;\n\nconst GRANT_REQUIRED_CAPABILITIES = new Set<string>(\n REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITIES,\n);\n\nexport interface RemoteWidgetCapabilityGrant {\n readonly packageId: string;\n readonly packageVersion: string;\n readonly capabilityVersion: string;\n}\n\nexport type RemoteWidgetCapabilityGrants = Readonly<\n Record<string, RemoteWidgetCapabilityGrant>\n>;\n\nexport function isRemoteWidgetGrantRequiredCapability(\n capability: string,\n): capability is RemoteWidgetGrantRequiredCapability {\n return GRANT_REQUIRED_CAPABILITIES.has(capability);\n}\n\nexport function createRemoteWidgetCapabilityGrant(\n remotePackage: Pick<\n RemoteDomWidgetPackageDescriptor,\n \"packageId\" | \"version\"\n >,\n widget: Pick<RemoteDomWidgetDefinition, \"capabilities\">,\n capability: string,\n): RemoteWidgetCapabilityGrant | undefined {\n if (!isRemoteWidgetGrantRequiredCapability(capability)) return undefined;\n const declaration = widget.capabilities.find(\n ({ name }) => name === capability,\n );\n const supportedVersion =\n REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITY_VERSIONS[capability];\n if (!declaration || declaration.version !== supportedVersion) {\n return undefined;\n }\n\n return {\n packageId: remotePackage.packageId,\n packageVersion: remotePackage.version,\n capabilityVersion: declaration.version,\n };\n}\n\nexport function createRemoteWidgetCapabilityGrants(\n remotePackage: Pick<\n RemoteDomWidgetPackageDescriptor,\n \"packageId\" | \"version\"\n >,\n widget: Pick<RemoteDomWidgetDefinition, \"capabilities\">,\n): RemoteWidgetCapabilityGrants | undefined {\n const grants = Object.fromEntries(\n REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITIES.flatMap((capability) => {\n const grant = createRemoteWidgetCapabilityGrant(\n remotePackage,\n widget,\n capability,\n );\n return grant ? [[capability, grant] as const] : [];\n }),\n );\n return Object.keys(grants).length === 0 ? undefined : grants;\n}\n\nexport function isExactRemoteWidgetCapabilityGrant(\n grant: RemoteWidgetCapabilityGrant | undefined,\n capability: string,\n remotePackage: Pick<\n RemoteDomWidgetPackageDescriptor,\n \"packageId\" | \"version\"\n >,\n widget: Pick<RemoteDomWidgetDefinition, \"capabilities\">,\n): boolean {\n const expected = createRemoteWidgetCapabilityGrant(\n remotePackage,\n widget,\n capability,\n );\n return (\n expected !== undefined &&\n grant?.packageId === expected.packageId &&\n grant.packageVersion === expected.packageVersion &&\n grant.capabilityVersion === expected.capabilityVersion\n );\n}\n\nexport function hasExactRemoteWidgetCapabilityGrants(\n grants: RemoteWidgetCapabilityGrants | undefined,\n remotePackage: Pick<\n RemoteDomWidgetPackageDescriptor,\n \"packageId\" | \"version\"\n >,\n widget: Pick<RemoteDomWidgetDefinition, \"capabilities\">,\n): boolean {\n const requiredDeclarations = widget.capabilities.filter(({ name }) =>\n isRemoteWidgetGrantRequiredCapability(name),\n );\n const expected = createRemoteWidgetCapabilityGrants(remotePackage, widget);\n if (requiredDeclarations.length === 0) return true;\n if (\n !expected ||\n Object.keys(expected).length !== requiredDeclarations.length\n ) {\n return false;\n }\n return Object.keys(expected).every((capability) =>\n isExactRemoteWidgetCapabilityGrant(\n grants?.[capability],\n capability,\n remotePackage,\n widget,\n ),\n );\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport {\n isRemoteDomWidgetPackageDescriptor,\n type RemoteDomWidgetPackageDescriptor,\n} from \"@fluid-app/portal-core/remote-dom-widget-package\";\nimport { createRemoteWidgetCapabilityGrant } from \"@fluid-app/portal-core/remote-widget-capability-grants\";\nimport { atomicWriteFile } from \"./atomic-write.js\";\nimport {\n collectWidgetCapabilityGrants,\n type SnapshotWidgetCapabilityGrant,\n type SnapshotWidgetCapabilityGrantMap,\n} from \"./snapshot.js\";\n\nexport interface WidgetCapabilityPushGrant extends SnapshotWidgetCapabilityGrant {\n readonly file: string;\n}\n\nexport function isWidgetCapabilityApprovedByPushFlags(\n capability: string,\n options: {\n readonly allowNetworkWidgets?: boolean;\n readonly allowWidgetCapabilities?: boolean;\n },\n): boolean {\n return (\n options.allowWidgetCapabilities === true ||\n (capability === \"networkAccess\" && options.allowNetworkWidgets === true)\n );\n}\n\nexport async function prepareWidgetCapabilityPush(options: {\n readonly portalDir: string;\n readonly screenFiles: readonly string[];\n readonly baseline: SnapshotWidgetCapabilityGrantMap | undefined;\n readonly catalogCandidates: readonly unknown[];\n}): Promise<readonly WidgetCapabilityPushGrant[]> {\n const catalog = options.catalogCandidates.filter(\n isRemoteDomWidgetPackageDescriptor,\n );\n const widgetCatalog = indexSensitiveWidgetCapabilities(catalog);\n const newlyEnabled: WidgetCapabilityPushGrant[] = [];\n\n for (const file of options.screenFiles) {\n const path = join(options.portalDir, file);\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as unknown;\n const current = collectWidgetCapabilityGrants(parsed);\n const previousByKey = new Map(\n (options.baseline?.[file] ?? []).map((grant) => [grantKey(grant), grant]),\n );\n for (const grant of current) {\n const expected = widgetCatalog.get(catalogKey(grant));\n if (!expected) {\n throw new Error(\n `Capability grant \"${grant.capability}\" for widget \"${grant.widgetType}\" in ${file} does not match the current widget catalog.`,\n );\n }\n const previous = previousByKey.get(grantKey(grant));\n const previouslyApproved =\n previous?.packageId === expected.packageId &&\n previous.packageVersion === expected.packageVersion &&\n previous.capabilityVersion === expected.capabilityVersion;\n if (!previouslyApproved && !sameGrant(grant, expected) && !previous) {\n throw new Error(\n `Capability grant \"${grant.capability}\" for widget \"${grant.widgetType}\" in ${file} is stale. Pull or approve the current widget package in the builder first.`,\n );\n }\n if (!previouslyApproved) {\n newlyEnabled.push({\n ...expected,\n widgetId: grant.widgetId,\n widgetType: grant.widgetType,\n file,\n });\n }\n }\n }\n\n return newlyEnabled;\n}\n\nexport async function persistApprovedWidgetCapabilityPushGrants(options: {\n readonly portalDir: string;\n readonly grants: readonly WidgetCapabilityPushGrant[];\n}): Promise<void> {\n const grantsByFile = new Map<string, WidgetCapabilityPushGrant[]>();\n for (const grant of options.grants) {\n const fileGrants = grantsByFile.get(grant.file) ?? [];\n fileGrants.push(grant);\n grantsByFile.set(grant.file, fileGrants);\n }\n for (const [file, grants] of grantsByFile) {\n const path = join(options.portalDir, file);\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as unknown;\n for (const grant of grants) {\n writeGrant(parsed, grant);\n }\n await atomicWriteFile(path, `${JSON.stringify(parsed, null, 2)}\\n`);\n }\n}\n\nfunction indexSensitiveWidgetCapabilities(\n packages: readonly RemoteDomWidgetPackageDescriptor[],\n): ReadonlyMap<string, SnapshotWidgetCapabilityGrant> {\n const capabilities = new Map<string, SnapshotWidgetCapabilityGrant>();\n for (const remotePackage of packages) {\n for (const widget of remotePackage.widgets) {\n for (const declaration of widget.capabilities) {\n const exactGrant = createRemoteWidgetCapabilityGrant(\n remotePackage,\n widget,\n declaration.name,\n );\n if (!exactGrant) continue;\n const grant: SnapshotWidgetCapabilityGrant = {\n capability: declaration.name,\n widgetId: \"\",\n widgetType: widget.type,\n ...exactGrant,\n };\n const key = catalogKey(grant);\n if (capabilities.has(key)) {\n throw new Error(\n `Widget type \"${widget.type}\" has multiple catalog entries for capability \"${declaration.name}\".`,\n );\n }\n capabilities.set(key, grant);\n }\n }\n }\n return capabilities;\n}\n\nfunction catalogKey(\n grant: Pick<SnapshotWidgetCapabilityGrant, \"widgetType\" | \"capability\">,\n): string {\n return `${grant.widgetType}\\0${grant.capability}`;\n}\n\nfunction grantKey(\n grant: Pick<\n SnapshotWidgetCapabilityGrant,\n \"widgetType\" | \"widgetId\" | \"packageId\" | \"capability\"\n >,\n): string {\n return `${grant.widgetType}\\0${grant.widgetId}\\0${grant.packageId}\\0${grant.capability}`;\n}\n\nfunction sameGrant(\n actual: SnapshotWidgetCapabilityGrant,\n expected: SnapshotWidgetCapabilityGrant,\n): boolean {\n return (\n actual.packageId === expected.packageId &&\n actual.packageVersion === expected.packageVersion &&\n actual.capabilityVersion === expected.capabilityVersion\n );\n}\n\nfunction writeGrant(\n value: unknown,\n grant: SnapshotWidgetCapabilityGrant,\n): boolean {\n if (!value || typeof value !== \"object\") return false;\n if (Array.isArray(value)) {\n return value.some((entry) => writeGrant(entry, grant));\n }\n const record = value as Record<string, unknown>;\n if (record.id === grant.widgetId && record.type === grant.widgetType) {\n const grants =\n record.capabilityGrants && typeof record.capabilityGrants === \"object\"\n ? (record.capabilityGrants as Record<string, unknown>)\n : {};\n record.capabilityGrants = {\n ...grants,\n [grant.capability]: {\n packageId: grant.packageId,\n packageVersion: grant.packageVersion,\n capabilityVersion: grant.capabilityVersion,\n },\n };\n return true;\n }\n return Object.values(record).some((entry) => writeGrant(entry, grant));\n}\n","/**\n * `fluid portal push` command\n *\n * Pushes local portal content changes to the Fluid OS API.\n * Detects changes since the last pull/push via snapshot diffing,\n * validates cross-references, and pushes resources in dependency order.\n */\n\nimport { Command } from \"commander\";\nimport type { ChangeEntry, PluginContext } from \"@fluid-app/fluid-cli\";\nimport {\n getAuthToken,\n getActiveProfile,\n gitSyncCommitSubject,\n subjectFragment,\n summarizeChanges,\n} from \"@fluid-app/fluid-cli\";\nimport type { FluidOsNavigationItem } from \"@fluid-app/fluidos-api-client/types\";\nimport type { PortalWidgetNode } from \"@fluid-app/portal-definition-schema\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { join } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport prompts from \"prompts\";\nimport {\n createFluidOsClient,\n fluidOsApi,\n toFluidOsDynamicObject,\n type FluidOsClient,\n} from \"../api/fluidos.js\";\n\nimport {\n readMappings,\n writeMappings,\n updateMapping,\n removeMapping,\n resolveSlugToId,\n} from \"../utils/mappings.js\";\nimport type { PortalMappings } from \"../utils/mappings.js\";\nimport {\n readSnapshot,\n diffAgainstSnapshot,\n writeSnapshot,\n computeFileHash,\n collectSnapshotWidgetCapabilityGrants,\n collectWidgetCapabilityGrants,\n selectLegacyNetworkWidgetGrants,\n} from \"../utils/snapshot.js\";\nimport type { SnapshotDiff } from \"../utils/snapshot.js\";\nimport type {\n LocalScreen,\n LocalTheme,\n LocalNavigation,\n LocalNavigationItem,\n LocalProfile,\n} from \"../utils/transform.js\";\nimport {\n buildIdToSlugMap,\n transformNavigationItems,\n} from \"../utils/transform.js\";\nimport {\n categorizeChanges,\n validateCrossReferences,\n validatePortalSchemas,\n validateScreenStructure,\n slugFromPath,\n} from \"../utils/push-validation.js\";\nimport type { CategorizedChanges } from \"../utils/push-validation.js\";\nimport { atomicWriteFile } from \"../utils/atomic-write.js\";\nimport { runPortalGitSync } from \"../lib/git-sync.js\";\nimport { fetchPortalActor } from \"../api/me.js\";\nimport {\n isWidgetCapabilityApprovedByPushFlags,\n prepareWidgetCapabilityPush,\n persistApprovedWidgetCapabilityPushGrants,\n} from \"../utils/widget-capability-grants.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Derived from the generated FluidOSNavigationItem but with `id` optional\n * (new items don't have one yet) and `label`/`position` required (needed\n * for create/update payloads).\n */\ntype NavigationSyncItem = Omit<\n FluidOsNavigationItem,\n \"id\" | \"label\" | \"position\" | \"children\"\n> & {\n id?: number;\n label: string;\n position: number;\n children?: NavigationSyncItem[];\n parent_id?: number | null;\n};\n\ntype ApiNavigationItem = FluidOsNavigationItem;\n\ntype FlattenedNavigationItem = Omit<NavigationSyncItem, \"children\"> & {\n /** Pre-order index of the item's structural parent in the local tree. */\n readonly treeParentIndex?: number;\n};\n\ninterface NavigationItemCreateShape {\n label: string;\n position: number;\n icon: string | undefined;\n screen_id: number | undefined;\n slug: string | undefined;\n source: ApiNavigationItem[\"source\"];\n parent_id: number | null;\n}\n\nclass NavigationRemoteStateUnknownError extends Error {\n override readonly name = \"NavigationRemoteStateUnknownError\";\n}\n\ninterface PushOptions {\n yes?: boolean;\n activate?: boolean;\n /** Subject for the invisible-git auto-commit. Overrides the derived\n * change summary — an intent is something no diff can recover. */\n message?: string;\n allowNetworkWidgets?: boolean;\n allowWidgetCapabilities?: boolean;\n}\n\n/** Result of a single push operation. */\ninterface PushResult {\n readonly file: string;\n readonly action: \"created\" | \"updated\" | \"deleted\";\n readonly success: boolean;\n readonly error?: string;\n /**\n * All intended remote mutations completed, so snapshot the current local\n * file even though post-push reconciliation failed. This prevents a retry\n * from replaying non-idempotent creates; the error directs the user to pull.\n */\n readonly advanceSnapshot?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PORTAL_DIR = \"portal\";\nconst PORTAL_SYNC_DIR = \".portal-sync\";\n\n/**\n * Convert the local array-form component_tree back to the object\n * the API expects. The pull command normalizes the API object into\n * an array for local convenience; this reverses that transformation.\n */\nfunction toApiComponentTree(\n tree: readonly PortalWidgetNode[],\n): Record<string, unknown> | null {\n if (tree.length === 0) return null;\n if (tree.length === 1) return { ...tree[0]! };\n // Fallback: wrap multiple roots in a container (shouldn't happen in practice)\n return { children: tree };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create an authenticated FluidOsClient using the stored CLI profile.\n */\nfunction createClient(): FluidOsClient {\n const token = getAuthToken();\n if (!token) {\n const profile = getActiveProfile();\n if (!profile) {\n throw new Error(\n \"Not logged in. Run \" + chalk.cyan(\"fluid login\") + \" first.\",\n );\n }\n throw new Error(\n \"No auth token found for profile \" +\n chalk.cyan(profile.name) +\n \". Run \" +\n chalk.cyan(\"fluid login\") +\n \" to re-authenticate.\",\n );\n }\n\n const baseUrl = process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n\n return createFluidOsClient({\n baseUrl,\n token,\n });\n}\n\nasync function createAndActivateVersion(\n client: FluidOsClient,\n definitionId: number,\n): Promise<string> {\n const result = await fluidOsApi.createVersion(client, definitionId);\n const versionId = result.version?.id;\n if (!versionId) {\n throw new Error(\"the version API returned no version ID\");\n }\n await fluidOsApi.updateVersion(client, definitionId, versionId, {\n version: { active: true },\n });\n return String(versionId);\n}\n\nasync function activateDraft(\n definitionId: number,\n existingClient?: FluidOsClient,\n): Promise<boolean> {\n const spinner = ora(\"Creating and activating a live version...\");\n spinner.start();\n try {\n const versionId = await createAndActivateVersion(\n existingClient ?? createClient(),\n definitionId,\n );\n spinner.succeed(`Live version ${versionId} activated`);\n return true;\n } catch (err) {\n spinner.fail(\"Could not activate a live version\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exitCode = 1;\n return false;\n }\n}\n\n/**\n * Extract an enriched error message from a caught value.\n * Includes structured API error data when available.\n */\nfunction enrichedErrorMessage(err: unknown): string {\n let msg = err instanceof Error ? err.message : String(err);\n if (err && typeof err === \"object\" && \"data\" in err) {\n msg += ` — ${JSON.stringify((err as { data: unknown }).data)}`;\n }\n return msg;\n}\n\nfunction navigationPushFailure(\n err: unknown,\n remoteChangesComplete: boolean,\n): Pick<PushResult, \"error\" | \"advanceSnapshot\"> {\n const error = enrichedErrorMessage(err);\n if (err instanceof NavigationRemoteStateUnknownError) {\n return { advanceSnapshot: true, error };\n }\n if (!remoteChangesComplete) return { error };\n\n return {\n advanceSnapshot: true,\n error: `Remote changes were applied before the push failed: ${error}. Run fluid portal pull before editing or pushing again.`,\n };\n}\n\n/**\n * Best-effort \"invisible git\" sync: commit the local project and push it\n * to the portal's provisioned GitHub repo before the content push.\n * Degradable failures (no git, endpoint missing, diverged) are reported\n * as a dim note and the content push proceeds. Returns false — with a\n * clean error, never a thrown stack — when local git state was mutated\n * but the push failed, so the caller aborts the content push.\n */\nasync function runInvisibleGitSync(\n cwd: string,\n definitionId: number,\n definitionName: string,\n explicitMessage?: string,\n): Promise<boolean> {\n const token = getAuthToken();\n if (!token) return true; // content push reports the auth problem clearly.\n const baseUrl = process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n // Canonical GitSync subject — same shape as mist / theme / widget, so\n // one `git log` across a company's repos reads as one system and every\n // commit says who made it. Actor lookup is best-effort: an unreachable\n // /api/me falls back to a `Fluid:` system commit rather than blocking\n // the publish.\n //\n // Precedence for the action: an explicit `-m` states the INTENT, which\n // no diff can recover; otherwise describe what actually changed; the\n // portal-name constant is the last resort for an unreadable diff.\n // Start the identity lookup but DON'T await it here: the git\n // reconcile below (credential mint, fetch, fast-forward) is the slow\n // part, and awaiting up front would add /api/me's latency to the push\n // serially. The resolver awaits it at commit time instead, by which\n // point it has almost always resolved. It is bounded (5s) and\n // best-effort — a publish must never hang describing itself.\n const actorPromise = fetchPortalActor(baseUrl, token);\n const fallback = `Publish portal ${subjectFragment(definitionName)}`;\n const explicit = explicitMessage?.trim();\n const commitMessage = async (\n changes: readonly ChangeEntry[],\n ): Promise<string> =>\n gitSyncCommitSubject(\n explicit || summarizeChanges(changes) || fallback,\n await actorPromise,\n );\n\n const spinner = ora(\"Syncing project to git…\").start();\n const result = await runPortalGitSync({\n cwd,\n baseUrl,\n token,\n definitionId,\n commitMessage,\n });\n if (result.status === \"synced\") {\n spinner.succeed(\"Synced project to git\");\n } else if (result.status === \"failed\") {\n // Local git state was mutated but the push didn't land — abort\n // before the content push so published artifacts can't drift from\n // unsynced source (same contract as `fluid widget push`).\n spinner.fail(\"Git sync failed\");\n // Clean message, no stack trace: this is a user-facing outcome, not\n // a crash.\n console.error(chalk.red(result.reason));\n console.log();\n return false;\n } else {\n spinner.info(chalk.gray(`Skipped git sync — ${result.reason}`));\n }\n console.log();\n return true;\n}\n\n/**\n * Read and parse a JSON file from the portal directory.\n */\nasync function readPortalFile<T>(\n portalDir: string,\n relativePath: string,\n): Promise<T> {\n const filePath = join(portalDir, relativePath);\n const content = await readFile(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Resource push functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Push screen changes to the API.\n */\nasync function pushScreens(\n client: FluidOsClient,\n defId: number,\n portalDir: string,\n changes: CategorizedChanges[\"screens\"],\n mappings: PortalMappings,\n): Promise<{ results: PushResult[]; mappings: PortalMappings }> {\n const results: PushResult[] = [];\n let currentMappings = mappings;\n\n // Create new screens\n for (const file of changes.new) {\n const slug = slugFromPath(file);\n try {\n const local = await readPortalFile<LocalScreen>(portalDir, file);\n const response = await fluidOsApi.createScreen(client, defId, {\n screen: {\n name: local.name,\n slug,\n component_tree: toFluidOsDynamicObject(\n toApiComponentTree(local.component_tree),\n ),\n },\n });\n const newId = response.screen?.id;\n if (newId != null) {\n currentMappings = updateMapping(\n currentMappings,\n \"screens\",\n slug,\n newId,\n );\n }\n results.push({ file, action: \"created\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"created\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Update changed screens\n for (const file of changes.changed) {\n const slug = slugFromPath(file);\n const screenId = resolveSlugToId(currentMappings, \"screens\", slug);\n if (screenId == null) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: `No mapping found for screen slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n const local = await readPortalFile<LocalScreen>(portalDir, file);\n await fluidOsApi.updateScreen(client, defId, screenId, {\n screen: {\n name: local.name,\n slug,\n component_tree: toFluidOsDynamicObject(\n toApiComponentTree(local.component_tree),\n ),\n },\n });\n results.push({ file, action: \"updated\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Delete screens\n for (const file of changes.deleted) {\n const slug = slugFromPath(file);\n const screenId = resolveSlugToId(currentMappings, \"screens\", slug);\n if (screenId == null) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: `No mapping found for screen slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n await fluidOsApi.deleteScreen(client, defId, screenId);\n currentMappings = removeMapping(currentMappings, \"screens\", slug);\n results.push({ file, action: \"deleted\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n return { results, mappings: currentMappings };\n}\n\n/**\n * Push theme changes to the API.\n */\nasync function pushThemes(\n client: FluidOsClient,\n defId: number,\n portalDir: string,\n changes: CategorizedChanges[\"themes\"],\n mappings: PortalMappings,\n): Promise<{ results: PushResult[]; mappings: PortalMappings }> {\n const results: PushResult[] = [];\n let currentMappings = mappings;\n\n // Create new themes\n for (const file of changes.new) {\n const slug = slugFromPath(file);\n try {\n const local = await readPortalFile<LocalTheme>(portalDir, file);\n const response = await fluidOsApi.createTheme(client, defId, {\n theme: {\n name: local.name,\n active: local.active,\n config: toFluidOsDynamicObject({ ...local.config }),\n },\n });\n const newId = response.theme?.id;\n if (newId != null) {\n currentMappings = updateMapping(currentMappings, \"themes\", slug, newId);\n }\n results.push({ file, action: \"created\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"created\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Update changed themes\n for (const file of changes.changed) {\n const slug = slugFromPath(file);\n const themeId = resolveSlugToId(currentMappings, \"themes\", slug);\n if (themeId == null) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: `No mapping found for theme slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n const local = await readPortalFile<LocalTheme>(portalDir, file);\n await fluidOsApi.updateTheme(client, defId, themeId, {\n theme: {\n name: local.name,\n active: local.active,\n config: toFluidOsDynamicObject({ ...local.config }),\n },\n });\n results.push({ file, action: \"updated\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Delete themes\n for (const file of changes.deleted) {\n const slug = slugFromPath(file);\n const themeId = resolveSlugToId(currentMappings, \"themes\", slug);\n if (themeId == null) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: `No mapping found for theme slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n await fluidOsApi.deleteTheme(client, defId, themeId);\n currentMappings = removeMapping(currentMappings, \"themes\", slug);\n results.push({ file, action: \"deleted\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n return { results, mappings: currentMappings };\n}\n\n/**\n * Resolve screen slug references to screen IDs in navigation items.\n * Returns a new tree with `screen_id` instead of `screen` slug,\n * shaped to match the FluidOSNavigationItemSyncItem schema.\n */\nfunction resolveNavigationItemScreenIds(\n items: LocalNavigationItem[],\n mappings: PortalMappings,\n): NavigationSyncItem[] {\n return items.map((item) => {\n const screenId = item.screen\n ? (resolveSlugToId(mappings, \"screens\", item.screen) ?? undefined)\n : undefined;\n const result: NavigationSyncItem = {\n ...(item.id ? { id: item.id } : {}),\n label: item.label ?? \"\",\n position: item.position ?? 0,\n icon: item.icon,\n screen_id: screenId ?? null,\n slug: item.slug,\n source: (item.source as \"user\" | \"system\" | \"code\") ?? \"user\",\n parent_id: item.parent_id,\n children: resolveNavigationItemScreenIds(item.children ?? [], mappings),\n };\n return result;\n });\n}\n\n/**\n * Flatten a tree of navigation sync items into a flat list.\n * The API reconciliation logic requires a flat list to correctly\n * compare against the flat server response.\n */\nfunction flattenNavigationItems(\n items: readonly NavigationSyncItem[],\n treeParentIndex?: number,\n flat: FlattenedNavigationItem[] = [],\n): FlattenedNavigationItem[] {\n for (const item of items) {\n const { children, ...rest } = item;\n const itemIndex = flat.length;\n flat.push({ ...rest, treeParentIndex });\n if (children && children.length > 0) {\n flattenNavigationItems(children, itemIndex, flat);\n }\n }\n return flat;\n}\n\n/**\n * The navigation-items index returns root items with descendants nested under\n * `children`. Flatten descendants first so removals cannot orphan or revisit a\n * child when its parent is also deleted.\n */\nfunction flattenServerNavigationItems(\n items: readonly ApiNavigationItem[],\n flat: ApiNavigationItem[] = [],\n): ApiNavigationItem[] {\n for (const item of items) {\n flattenServerNavigationItems(item.children ?? [], flat);\n flat.push(item);\n }\n return flat;\n}\n\nfunction resolveNavigationItemParentId(\n item: FlattenedNavigationItem,\n serverIdByItemIndex: ReadonlyMap<number, number>,\n): number | null {\n if (item.treeParentIndex != null) {\n const serverParentId = serverIdByItemIndex.get(item.treeParentIndex);\n if (serverParentId == null) {\n throw new Error(\n \"Failed to resolve the server ID for a nested navigation item's parent\",\n );\n }\n return serverParentId;\n }\n\n // The canonical local format is a tree. A pulled child retains its previous\n // parent_id as server metadata, so structural root placement must clear that\n // stale relationship instead of silently preserving it.\n return null;\n}\n\nasync function resolveCreatedNavigationItemId(\n client: FluidOsClient,\n defId: number,\n navigationId: number,\n responseId: number | undefined,\n knownServerIds: Set<number>,\n requestedItem: NavigationItemCreateShape,\n file: string,\n): Promise<number> {\n if (responseId != null) {\n knownServerIds.add(responseId);\n return responseId;\n }\n\n // A successful response must include the new ID. If that contract is ever\n // violated, recover only when a fresh authoritative list has exactly one\n // previously unseen item matching the request; concurrent writes make any\n // other result unsafe to guess from.\n let response: Awaited<ReturnType<typeof fluidOsApi.listNavigationItems>>;\n try {\n response = await fluidOsApi.listNavigationItems(\n client,\n defId,\n navigationId,\n );\n } catch (recoveryError) {\n throw new NavigationRemoteStateUnknownError(\n `Remote navigation state is unknown because item creation succeeded without returning its required ID, and the recovery read failed: ${enrichedErrorMessage(recoveryError)}. Back up ${file}, run fluid portal pull, then reapply pending edits before pushing again.`,\n );\n }\n const unseenItems = flattenServerNavigationItems(\n response.navigation_items ?? [],\n ).filter((item) => !knownServerIds.has(item.id));\n const matchingItems = unseenItems.filter(\n (item) =>\n (item.label ?? \"\") === requestedItem.label &&\n (item.position ?? 0) === requestedItem.position &&\n (item.icon ?? undefined) === requestedItem.icon &&\n (item.screen_id ?? undefined) === requestedItem.screen_id &&\n (item.slug ?? undefined) === requestedItem.slug &&\n (item.source ?? \"user\") === (requestedItem.source ?? \"user\") &&\n (item.parent_id ?? null) === (requestedItem.parent_id ?? null),\n );\n\n if (unseenItems.length !== 1 || matchingItems.length !== 1) {\n throw new NavigationRemoteStateUnknownError(\n `Remote navigation state is unknown because item creation succeeded without returning its required ID, and recovery found ${matchingItems.length} matching items among ${unseenItems.length} new server items. Back up ${file}, run fluid portal pull, then reapply pending edits before pushing again.`,\n );\n }\n\n const recoveredId = matchingItems[0]?.id;\n if (recoveredId == null) {\n throw new NavigationRemoteStateUnknownError(\n `Remote navigation state is unknown because item creation succeeded without returning its required ID. Back up ${file}, run fluid portal pull, then reapply pending edits before pushing again.`,\n );\n }\n knownServerIds.add(recoveredId);\n return recoveredId;\n}\n\nfunction assignNavigationItemId(\n navigation: LocalNavigation,\n targetIndex: number,\n serverId: number,\n): LocalNavigation {\n // Resolved API items and local items use the same pre-order traversal. Keep\n // the rest of the desired tree intact so a partial push can retry safely.\n let currentIndex = 0;\n let previousId: number | undefined;\n\n function assign(items: LocalNavigationItem[]): LocalNavigationItem[] {\n return items.map((item) => {\n const itemIndex = currentIndex++;\n const children = assign(item.children ?? []);\n if (itemIndex !== targetIndex) return { ...item, children };\n\n previousId = item.id;\n return { ...item, id: serverId, children };\n });\n }\n\n function rewriteParentIds(\n items: LocalNavigationItem[],\n ): LocalNavigationItem[] {\n return items.map((item) => ({\n ...item,\n parent_id: item.parent_id === previousId ? serverId : item.parent_id,\n children: rewriteParentIds(item.children ?? []),\n }));\n }\n\n const navigationItems = assign(navigation.navigation_items);\n return {\n ...navigation,\n navigation_items:\n previousId == null || previousId === serverId\n ? navigationItems\n : rewriteParentIds(navigationItems),\n };\n}\n\nasync function persistCreatedNavigationItemId(\n client: FluidOsClient,\n defId: number,\n navigationId: number,\n portalDir: string,\n file: string,\n navigation: LocalNavigation,\n itemIndex: number,\n serverId: number,\n): Promise<LocalNavigation> {\n const updated = assignNavigationItemId(navigation, itemIndex, serverId);\n try {\n await atomicWriteFile(\n join(portalDir, file),\n JSON.stringify(updated, null, 2) + \"\\n\",\n );\n } catch (persistError) {\n try {\n await fluidOsApi.deleteNavigationItem(\n client,\n defId,\n navigationId,\n serverId,\n );\n } catch (rollbackError) {\n throw new NavigationRemoteStateUnknownError(\n `Failed to persist navigation item ${serverId} locally: ${enrichedErrorMessage(persistError)}. Automatic rollback also failed: ${enrichedErrorMessage(rollbackError)}. Back up ${file}, run fluid portal pull, then reapply pending edits before pushing again.`,\n );\n }\n throw persistError;\n }\n return updated;\n}\n\n/**\n * Replace a pushed navigation file with the authoritative server tree.\n * New navigation items receive their IDs from the server, so the local file\n * must be reconciled before the push snapshot hashes it.\n */\nasync function reconcileNavigationFile(\n client: FluidOsClient,\n defId: number,\n navId: number,\n portalDir: string,\n file: string,\n local: LocalNavigation,\n mappings: PortalMappings,\n): Promise<void> {\n const response = await fluidOsApi.listNavigationItems(client, defId, navId);\n const reconciled: LocalNavigation = {\n ...local,\n navigation_items: transformNavigationItems(\n response.navigation_items ?? [],\n buildIdToSlugMap(mappings.screens),\n ),\n };\n\n const filePath = join(portalDir, file);\n await atomicWriteFile(filePath, JSON.stringify(reconciled, null, 2) + \"\\n\");\n}\n\n/**\n * Push navigation changes to the API.\n */\nexport async function pushNavigations(\n client: FluidOsClient,\n defId: number,\n portalDir: string,\n changes: CategorizedChanges[\"navigations\"],\n mappings: PortalMappings,\n): Promise<{ results: PushResult[]; mappings: PortalMappings }> {\n const results: PushResult[] = [];\n let currentMappings = mappings;\n // A prior create may have persisted its navigation mapping before a later\n // item failed. Treat that still-snapshot-new file as an update on retry so\n // the remote navigation itself is not created twice.\n const newFiles = changes.new.filter(\n (file) =>\n resolveSlugToId(currentMappings, \"navigations\", slugFromPath(file)) ==\n null,\n );\n const changedFiles = [\n ...changes.changed,\n ...changes.new.filter(\n (file) =>\n resolveSlugToId(currentMappings, \"navigations\", slugFromPath(file)) !=\n null,\n ),\n ];\n\n // Create new navigations\n for (const file of newFiles) {\n const slug = slugFromPath(file);\n let remoteChangesComplete = false;\n try {\n let local = await readPortalFile<LocalNavigation>(portalDir, file);\n const response = await fluidOsApi.createNavigation(client, defId, {\n navigation: {\n name: local.name,\n platform: local.platform as \"web\" | \"mobile\",\n },\n });\n const newId = response.navigation?.id;\n if (newId != null) {\n currentMappings = updateMapping(\n currentMappings,\n \"navigations\",\n slug,\n newId,\n );\n // Create navigation items individually (flatten tree for API).\n // Track each tree item's resolved server ID so descendants reference\n // their structural parent's authoritative ID.\n const resolvedItems = flattenNavigationItems(\n resolveNavigationItemScreenIds(\n local.navigation_items,\n currentMappings,\n ),\n );\n const serverIdByItemIndex = new Map<number, number>();\n const knownServerIds = new Set<number>();\n\n for (const [itemIndex, item] of resolvedItems.entries()) {\n const resolvedParentId = resolveNavigationItemParentId(\n item,\n serverIdByItemIndex,\n );\n\n const createBody: NavigationItemCreateShape = {\n label: item.label ?? \"\",\n position: item.position ?? 0,\n icon: item.icon ?? undefined,\n screen_id: item.screen_id ?? undefined,\n slug: item.slug ?? undefined,\n source: item.source ?? undefined,\n parent_id: resolvedParentId,\n };\n const created = await fluidOsApi.createNavigationItem(\n client,\n defId,\n newId,\n {\n navigation_item: createBody,\n },\n );\n\n const createdId = await resolveCreatedNavigationItemId(\n client,\n defId,\n newId,\n created.navigation_item?.id,\n knownServerIds,\n createBody,\n file,\n );\n local = await persistCreatedNavigationItemId(\n client,\n defId,\n newId,\n portalDir,\n file,\n local,\n itemIndex,\n createdId,\n );\n serverIdByItemIndex.set(itemIndex, createdId);\n }\n\n remoteChangesComplete = true;\n await reconcileNavigationFile(\n client,\n defId,\n newId,\n portalDir,\n file,\n local,\n currentMappings,\n );\n }\n results.push({ file, action: \"created\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"created\",\n success: false,\n ...navigationPushFailure(err, remoteChangesComplete),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Update changed navigations\n for (const file of changedFiles) {\n const slug = slugFromPath(file);\n const navId = resolveSlugToId(currentMappings, \"navigations\", slug);\n if (navId == null) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: `No mapping found for navigation slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n let remoteChangesComplete = false;\n try {\n let local = await readPortalFile<LocalNavigation>(portalDir, file);\n\n // Update navigation metadata\n await fluidOsApi.updateNavigation(client, defId, navId, {\n navigation: {\n name: local.name,\n platform: local.platform as \"web\" | \"mobile\",\n },\n });\n\n // Reconcile navigation items via individual CRUD operations.\n // The bulk sync endpoint is unreliable, so we diff local vs server\n // and issue create/update/delete calls like the admin builder does.\n // Flatten the tree since the API returns/expects a flat list.\n const resolvedItems = flattenNavigationItems(\n resolveNavigationItemScreenIds(local.navigation_items, currentMappings),\n );\n\n const serverResponse = await fluidOsApi.listNavigationItems(\n client,\n defId,\n navId,\n );\n const serverItems = flattenServerNavigationItems(\n serverResponse.navigation_items ?? [],\n );\n const serverById = new Map(serverItems.map((s) => [s.id, s]));\n const knownServerIds = new Set(serverById.keys());\n const localIds = new Set(\n resolvedItems.filter((i) => i.id).map((i) => i.id),\n );\n\n // Create or update local items.\n // Track each tree item's resolved server ID so descendants reference\n // their structural parent's authoritative ID.\n const serverIdByItemIndex = new Map<number, number>();\n\n for (const [itemIndex, item] of resolvedItems.entries()) {\n const resolvedParentId = resolveNavigationItemParentId(\n item,\n serverIdByItemIndex,\n );\n\n const body = {\n label: item.label,\n position: item.position,\n icon: item.icon ?? undefined,\n screen_id: item.screen_id ?? undefined,\n slug: item.slug ?? undefined,\n source: item.source ?? undefined,\n parent_id: resolvedParentId,\n };\n\n if (item.id && serverById.has(item.id)) {\n // Update existing\n await fluidOsApi.updateNavigationItem(client, defId, navId, item.id, {\n navigation_item: body,\n });\n serverIdByItemIndex.set(itemIndex, item.id);\n } else {\n // Create new\n const createBody: NavigationItemCreateShape = {\n ...body,\n label: body.label ?? \"\",\n position: body.position ?? 0,\n };\n const created = await fluidOsApi.createNavigationItem(\n client,\n defId,\n navId,\n {\n navigation_item: createBody,\n },\n );\n\n const createdId = await resolveCreatedNavigationItemId(\n client,\n defId,\n navId,\n created.navigation_item?.id,\n knownServerIds,\n createBody,\n file,\n );\n local = await persistCreatedNavigationItemId(\n client,\n defId,\n navId,\n portalDir,\n file,\n local,\n itemIndex,\n createdId,\n );\n serverIdByItemIndex.set(itemIndex, createdId);\n }\n }\n\n // Move retained descendants before deleting removed ancestors. Rails\n // cascades navigation-item children, so deleting first could destroy an\n // item that the local tree intends to keep under a new parent.\n for (const serverItem of serverItems) {\n if (!localIds.has(serverItem.id)) {\n await fluidOsApi.deleteNavigationItem(\n client,\n defId,\n navId,\n serverItem.id,\n );\n }\n }\n\n remoteChangesComplete = true;\n await reconcileNavigationFile(\n client,\n defId,\n navId,\n portalDir,\n file,\n local,\n currentMappings,\n );\n\n results.push({ file, action: \"updated\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n ...navigationPushFailure(err, remoteChangesComplete),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Delete navigations\n for (const file of changes.deleted) {\n const slug = slugFromPath(file);\n const navId = resolveSlugToId(currentMappings, \"navigations\", slug);\n if (navId == null) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: `No mapping found for navigation slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n await fluidOsApi.deleteNavigation(client, defId, navId);\n currentMappings = removeMapping(currentMappings, \"navigations\", slug);\n results.push({ file, action: \"deleted\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n return { results, mappings: currentMappings };\n}\n\n/**\n * Push profile changes to the API.\n */\nasync function pushProfiles(\n client: FluidOsClient,\n defId: number,\n portalDir: string,\n changes: CategorizedChanges[\"profiles\"],\n mappings: PortalMappings,\n): Promise<{ results: PushResult[]; mappings: PortalMappings }> {\n const results: PushResult[] = [];\n let currentMappings = mappings;\n\n // Create new profiles\n for (const file of changes.new) {\n const slug = slugFromPath(file);\n try {\n const local = await readPortalFile<LocalProfile>(portalDir, file);\n const body = resolveProfileBody(local, currentMappings);\n const response = await fluidOsApi.createProfile(client, defId, {\n profile: body,\n });\n const newId = response.profile?.id;\n if (newId != null) {\n currentMappings = updateMapping(\n currentMappings,\n \"profiles\",\n slug,\n newId,\n );\n }\n results.push({ file, action: \"created\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"created\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Update changed profiles\n for (const file of changes.changed) {\n const slug = slugFromPath(file);\n const profileId = resolveSlugToId(currentMappings, \"profiles\", slug);\n if (profileId == null) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: `No mapping found for profile slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n const local = await readPortalFile<LocalProfile>(portalDir, file);\n const body = resolveProfileBody(local, currentMappings);\n await fluidOsApi.updateProfile(client, defId, profileId, {\n profile: body,\n });\n results.push({ file, action: \"updated\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Delete profiles\n for (const file of changes.deleted) {\n const slug = slugFromPath(file);\n const profileId = resolveSlugToId(currentMappings, \"profiles\", slug);\n if (profileId == null) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: `No mapping found for profile slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n await fluidOsApi.deleteProfile(client, defId, profileId);\n currentMappings = removeMapping(currentMappings, \"profiles\", slug);\n results.push({ file, action: \"deleted\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n return { results, mappings: currentMappings };\n}\n\n/** Typed profile body for API requests (create and update share the same shape). */\ninterface ProfileBody {\n name: string;\n default?: boolean;\n navigation_id?: number;\n mobile_navigation_id?: number;\n theme_ids?: number[];\n permissions?: {\n countries?: number[];\n ranks?: number[];\n roles?: string[];\n platform?: string[];\n };\n}\n\n/**\n * Resolve profile slug references to API IDs for create/update request body.\n */\nfunction resolveProfileBody(\n local: LocalProfile,\n mappings: PortalMappings,\n): ProfileBody {\n const body: ProfileBody = {\n name: local.name,\n default: local.default,\n permissions: local.permissions,\n };\n\n if (local.navigation) {\n const navId = resolveSlugToId(mappings, \"navigations\", local.navigation);\n if (navId != null) {\n body.navigation_id = navId;\n }\n }\n\n if (local.mobile_navigation) {\n const mobileNavId = resolveSlugToId(\n mappings,\n \"navigations\",\n local.mobile_navigation,\n );\n if (mobileNavId != null) {\n body.mobile_navigation_id = mobileNavId;\n }\n }\n\n const themeIds = local.themes\n .map((slug) => resolveSlugToId(mappings, \"themes\", slug))\n .filter((id): id is number => id != null);\n body.theme_ids = themeIds;\n\n return body;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Print helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction printChangesSummary(diff: SnapshotDiff, definitionName: string): void {\n console.log(\n chalk.blue(\"Changes to push for \") +\n chalk.white.bold(`\"${definitionName}\"`) +\n chalk.blue(\":\"),\n );\n console.log();\n\n if (diff.new.length > 0) {\n console.log(chalk.green(\" New: \") + diff.new.join(\", \"));\n }\n if (diff.changed.length > 0) {\n console.log(chalk.yellow(\" Changed: \") + diff.changed.join(\", \"));\n }\n if (diff.deleted.length > 0) {\n console.log(chalk.red(\" Deleted: \") + diff.deleted.join(\", \"));\n }\n console.log();\n}\n\nfunction printPushReport(results: PushResult[], skippedPhases: string[]): void {\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (succeeded.length > 0) {\n console.log(chalk.green.bold(\"Succeeded:\"));\n for (const r of succeeded) {\n console.log(chalk.green(\" \" + r.action + \": \") + r.file);\n }\n }\n\n if (failed.length > 0) {\n console.log();\n console.log(chalk.red.bold(\"Failed:\"));\n for (const r of failed) {\n console.log(\n chalk.red(\" \" + r.action + \": \") +\n r.file +\n chalk.gray(\" — \" + (r.error ?? \"Unknown error\")),\n );\n }\n }\n\n if (skippedPhases.length > 0) {\n console.log();\n console.log(chalk.yellow.bold(\"Skipped phases:\"));\n for (const phase of skippedPhases) {\n console.log(chalk.yellow(\" \" + phase));\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Command\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const pushCommand: Command = new Command(\"push\")\n .description(\"Push local portal content changes to the Fluid OS API\")\n .option(\"--yes\", \"Skip confirmation prompt\")\n .option(\n \"--allow-network-widgets\",\n \"Approve widgets that newly request external network access\",\n )\n .option(\n \"--allow-widget-capabilities\",\n \"Approve widgets that newly request sensitive capabilities\",\n )\n .option(\n \"--activate\",\n \"Create and activate a live version after a successful push\",\n )\n .option(\n \"-m, --message <message>\",\n \"Commit message for the git auto-commit (default: a summary of what changed)\",\n )\n .action(async (options: PushOptions) => {\n const cwd = process.cwd();\n const portalDir = join(cwd, PORTAL_DIR);\n const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Portal Push\"));\n console.log();\n\n // ── Check for portal directory ─────────────────────────────────────\n if (!existsSync(portalDir)) {\n console.log(\n chalk.red(\"Error:\") +\n \" No portal/ directory found. Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" first.\",\n );\n console.log();\n process.exit(1);\n }\n\n // ── Read snapshot ──────────────────────────────────────────────────\n const snapshot = await readSnapshot(portalSyncDir);\n if (!snapshot) {\n console.log(\n chalk.red(\"Error:\") +\n \" No snapshot found in .portal-sync/. Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" first.\",\n );\n console.log();\n process.exit(1);\n }\n\n // ── Read mappings ──────────────────────────────────────────────────\n const mappings = await readMappings(portalSyncDir);\n if (!mappings) {\n console.log(\n chalk.red(\"Error:\") +\n \" No mappings found in .portal-sync/. Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" first.\",\n );\n console.log();\n process.exit(1);\n }\n\n const definitionId = snapshot.definition_id;\n const definitionName = snapshot.definition;\n\n console.log(\n chalk.gray(\"Definition: \") +\n chalk.white(definitionName) +\n chalk.gray(` (ID: ${definitionId})`),\n );\n console.log();\n\n // ── Invisible git sync (before the content push) ────────────────────\n // Mirror the local project to its provisioned GitHub repo. Degradable\n // problems — no git / no repo / endpoint missing (404) / diverged\n // history — degrade to a dim note and the content push proceeds. The\n // one fatal case is a rejected push AFTER local git state was mutated:\n // publishing then would ship content whose source never reached the\n // repo, so we stop here (clean error already printed).\n if (\n !(await runInvisibleGitSync(\n cwd,\n definitionId,\n definitionName,\n options.message,\n ))\n ) {\n process.exitCode = 1;\n return;\n }\n\n // ── Detect changes ─────────────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Detecting changes...\");\n\n const diff = await diffAgainstSnapshot(portalDir, snapshot);\n const totalChanges =\n diff.new.length + diff.changed.length + diff.deleted.length;\n\n if (totalChanges === 0) {\n spinner.succeed(\"Nothing to push.\");\n console.log();\n if (options.activate) {\n await activateDraft(definitionId);\n console.log();\n }\n return;\n }\n\n spinner.succeed(`Found ${totalChanges} change(s)`);\n console.log();\n\n // ── Show changes and confirm ───────────────────────────────────────\n printChangesSummary(diff, definitionName);\n\n if (!options.yes) {\n const { confirmed } = await prompts({\n type: \"confirm\",\n name: \"confirmed\",\n message: `Push ${totalChanges} change(s) to Fluid OS?`,\n initial: false,\n });\n\n if (!confirmed) {\n console.log();\n console.log(chalk.gray(\"Push cancelled.\"));\n console.log();\n return;\n }\n console.log();\n }\n\n // ── Categorize changes ─────────────────────────────────────────────\n const changes = categorizeChanges(diff);\n\n // ── Cross-reference validation ─────────────────────────────────────\n spinner.start(\"Validating portal files...\");\n const schemaErrors = await validatePortalSchemas(portalDir);\n const validationErrors =\n schemaErrors.length > 0\n ? schemaErrors\n : await validateCrossReferences(portalDir, mappings, changes);\n\n if (validationErrors.length > 0) {\n spinner.fail(\"Portal file validation failed\");\n console.log();\n for (const err of validationErrors) {\n console.log(chalk.red(\" \" + err.file + \": \") + err.message);\n }\n console.log();\n process.exit(1);\n }\n spinner.succeed(\"Portal files valid\");\n\n // Advisory only — these screens push and render fine, but the admin\n // builder will not offer drop zones on them. Reported after the pass\n // message so it reads as a note rather than a failure.\n const structureWarnings = await validateScreenStructure(portalDir, changes);\n if (structureWarnings.length > 0) {\n console.log();\n for (const warning of structureWarnings) {\n console.log(\n chalk.yellow(\" warning \") +\n chalk.white(warning.file + \": \") +\n warning.message,\n );\n }\n console.log();\n }\n\n // ── Authenticate ───────────────────────────────────────────────────\n spinner.start(\"Authenticating...\");\n\n let client: FluidOsClient;\n try {\n client = createClient();\n spinner.succeed(\"Authenticated\");\n } catch (err) {\n spinner.fail(\"Authentication failed\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Dedicated sensitive-capability approval ───────────────────────\n const changedScreenFiles = [\n ...changes.screens.new,\n ...changes.screens.changed,\n ];\n const hasWidgetCapabilityGrants = (\n await Promise.all(\n changedScreenFiles.map(async (file) =>\n collectWidgetCapabilityGrants(\n JSON.parse(\n await readFile(join(portalDir, file), \"utf8\"),\n ) as unknown,\n ),\n ),\n )\n ).some((grants) => grants.length > 0);\n if (hasWidgetCapabilityGrants) {\n try {\n const catalog = await fluidOsApi.listWidgetPackages(client);\n const newlyEnabled = await prepareWidgetCapabilityPush({\n portalDir,\n screenFiles: changedScreenFiles,\n baseline: snapshot.widget_capability_grants,\n catalogCandidates: catalog.packages,\n });\n if (newlyEnabled.length > 0) {\n console.log();\n console.log(chalk.yellow.bold(\"Widgets requesting capabilities:\"));\n for (const grant of newlyEnabled) {\n console.log(\n ` ${chalk.white(grant.widgetType)} ${chalk.yellow(grant.capability)} ${chalk.cyan(`${grant.packageId}@${grant.packageVersion}`)} ${chalk.gray(`${grant.file} (${grant.widgetId})`)}`,\n );\n }\n console.log();\n\n const needsApproval = newlyEnabled.some(\n ({ capability }) =>\n !isWidgetCapabilityApprovedByPushFlags(capability, options),\n );\n if (needsApproval) {\n if (!process.stdin.isTTY) {\n throw new Error(\n \"New sensitive widget capabilities require --allow-widget-capabilities in a non-interactive session. --yes does not approve capabilities; --allow-network-widgets approves only networkAccess.\",\n );\n }\n const { approved } = await prompts({\n type: \"confirm\",\n name: \"approved\",\n message:\n \"Allow all listed widget capabilities for these package versions?\",\n initial: false,\n });\n if (!approved) {\n console.log();\n console.log(chalk.gray(\"Push cancelled.\"));\n console.log();\n return;\n }\n console.log();\n }\n await persistApprovedWidgetCapabilityPushGrants({\n portalDir,\n grants: newlyEnabled,\n });\n }\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.exitCode = 1;\n return;\n }\n }\n\n // ── Phase 1: Screens + Themes (parallel) ───────────────────────────\n const allResults: PushResult[] = [];\n const skippedPhases: string[] = [];\n let currentMappings = mappings;\n let aborted = false;\n\n const hasScreenChanges =\n changes.screens.new.length > 0 ||\n changes.screens.changed.length > 0 ||\n changes.screens.deleted.length > 0;\n const hasThemeChanges =\n changes.themes.new.length > 0 ||\n changes.themes.changed.length > 0 ||\n changes.themes.deleted.length > 0;\n\n if (hasScreenChanges || hasThemeChanges) {\n spinner.start(\"Phase 1: Pushing screens and themes...\");\n\n const phase1Tasks: Promise<{\n results: PushResult[];\n mappings: PortalMappings;\n }>[] = [];\n\n if (hasScreenChanges) {\n phase1Tasks.push(\n pushScreens(\n client,\n definitionId,\n portalDir,\n changes.screens,\n currentMappings,\n ),\n );\n }\n if (hasThemeChanges) {\n phase1Tasks.push(\n pushThemes(\n client,\n definitionId,\n portalDir,\n changes.themes,\n currentMappings,\n ),\n );\n }\n\n const phase1Results = await Promise.all(phase1Tasks);\n\n // Merge mappings from both parallel operations\n // Only apply the resource type each operation actually owns\n for (const result of phase1Results) {\n allResults.push(...result.results);\n }\n if (hasScreenChanges) {\n currentMappings = {\n ...currentMappings,\n screens: phase1Results[0]!.mappings.screens,\n };\n }\n if (hasThemeChanges) {\n const idx = hasScreenChanges ? 1 : 0;\n currentMappings = {\n ...currentMappings,\n themes: phase1Results[idx]!.mappings.themes,\n };\n }\n\n const phase1Failed = phase1Results.some((r) =>\n r.results.some((res) => !res.success),\n );\n\n if (phase1Failed) {\n spinner.fail(\"Phase 1 failed\");\n aborted = true;\n skippedPhases.push(\"Phase 2: Navigations\", \"Phase 3: Profiles\");\n } else {\n spinner.succeed(\"Phase 1 complete\");\n }\n }\n\n // ── Phase 2: Navigations ───────────────────────────────────────────\n const hasNavChanges =\n changes.navigations.new.length > 0 ||\n changes.navigations.changed.length > 0 ||\n changes.navigations.deleted.length > 0;\n\n if (!aborted && hasNavChanges) {\n spinner.start(\"Phase 2: Pushing navigations...\");\n\n const navResult = await pushNavigations(\n client,\n definitionId,\n portalDir,\n changes.navigations,\n currentMappings,\n );\n\n allResults.push(...navResult.results);\n currentMappings = {\n ...currentMappings,\n navigations: navResult.mappings.navigations,\n };\n\n const phase2Failed = navResult.results.some((r) => !r.success);\n if (phase2Failed) {\n spinner.fail(\"Phase 2 failed\");\n aborted = true;\n skippedPhases.push(\"Phase 3: Profiles\");\n } else {\n spinner.succeed(\"Phase 2 complete\");\n }\n } else if (aborted && hasNavChanges) {\n // Already marked as skipped above\n }\n\n // ── Phase 3: Profiles ──────────────────────────────────────────────\n const hasProfileChanges =\n changes.profiles.new.length > 0 ||\n changes.profiles.changed.length > 0 ||\n changes.profiles.deleted.length > 0;\n\n if (!aborted && hasProfileChanges) {\n spinner.start(\"Phase 3: Pushing profiles...\");\n\n const profileResult = await pushProfiles(\n client,\n definitionId,\n portalDir,\n changes.profiles,\n currentMappings,\n );\n\n allResults.push(...profileResult.results);\n currentMappings = {\n ...currentMappings,\n profiles: profileResult.mappings.profiles,\n };\n\n const phase3Failed = profileResult.results.some((r) => !r.success);\n if (phase3Failed) {\n spinner.fail(\"Phase 3 failed\");\n } else {\n spinner.succeed(\"Phase 3 complete\");\n }\n }\n\n // ── Update mappings ────────────────────────────────────────────────\n await writeMappings(portalSyncDir, currentMappings);\n\n // ── Update snapshot for successfully pushed files ───────────────────\n const filesToSnapshot = new Set(\n allResults\n .filter((r) => r.success || r.advanceSnapshot)\n .map((r) => r.file),\n );\n\n if (filesToSnapshot.size > 0) {\n // Only advance snapshot entries for successfully pushed files;\n // also advance a fully-applied mutation or unknown remote state so an\n // immediate retry cannot replay a create whose response omitted its ID.\n // Other skipped/failed entries retain their original hashes.\n const updatedHashes = { ...snapshot.files };\n for (const file of filesToSnapshot) {\n const fullPath = join(portalDir, file);\n if (existsSync(fullPath)) {\n updatedHashes[file] = await computeFileHash(fullPath);\n } else {\n // deleted file — remove from snapshot\n delete updatedHashes[file];\n }\n }\n const updatedCapabilityGrants = {\n ...snapshot.widget_capability_grants,\n };\n const currentCapabilityGrants =\n await collectSnapshotWidgetCapabilityGrants(\n portalDir,\n [...filesToSnapshot].filter(\n (file) =>\n file.startsWith(\"screens/\") &&\n file.endsWith(\".json\") &&\n existsSync(join(portalDir, file)),\n ),\n );\n for (const file of filesToSnapshot) {\n if (!file.startsWith(\"screens/\") || !file.endsWith(\".json\")) continue;\n const grants = currentCapabilityGrants[file];\n if (grants && grants.length > 0) updatedCapabilityGrants[file] = grants;\n else delete updatedCapabilityGrants[file];\n }\n await writeSnapshot(portalSyncDir, {\n ...snapshot,\n files: updatedHashes,\n widget_capability_grants: updatedCapabilityGrants,\n network_widget_grants: selectLegacyNetworkWidgetGrants(\n updatedCapabilityGrants,\n ),\n });\n }\n\n // ── Report ─────────────────────────────────────────────────────────\n console.log();\n const allSucceeded = allResults.every((r) => r.success);\n\n if (allSucceeded && !aborted) {\n console.log(chalk.green.bold(\"Push complete!\"));\n } else {\n console.log(chalk.yellow.bold(\"Push completed with issues:\"));\n }\n console.log();\n printPushReport(allResults, skippedPhases);\n console.log();\n\n if (!allSucceeded || aborted) {\n process.exitCode = 1;\n return;\n }\n if (options.activate) {\n await activateDraft(definitionId, client);\n console.log();\n }\n });\n\nexport function registerPushCommand(ctx: PluginContext): void {\n ctx.program.addCommand(pushCommand);\n}\n","/**\n * `fluid portal clone [app]` — mints a fresh git credential for a Fluid\n * OS portal definition and clones its provisioned GitHub repo into\n * `./<slug>/`. Mirrors `fluid mist clone` (credential → clone), but\n * against the portal git-credential endpoint.\n *\n * When the credential endpoint reports that no repository is available\n * (the repo was torn down, provisioning failed, it is still not ready\n * after the retry budget, or the endpoint isn't deployed), clone falls\n * back to scaffolding the portal locally from the bundled starter\n * template instead of failing. Local files plus API sync\n * (`fluid portal push`/`pull`) are a fully functional portal — Git is\n * an optional sync channel, not a prerequisite — so the user still\n * lands in a working project and Git history can attach later via\n * `fluid portal push`, which adopts the remote's history.\n */\n\nimport { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { getAuthToken, getActiveProfile } from \"@fluid-app/fluid-cli\";\nimport {\n createFluidOsClient,\n fluidOsApi,\n type FluidOsClient,\n} from \"../api/fluidos.js\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { deriveSlug } from \"../utils/mappings.js\";\nimport { createPortalGitCredentialWithRetry } from \"../api/git-credential.js\";\nimport {\n cleanGitRemoteUrl,\n ensureGitAvailable,\n excludeLocalStateFromGit,\n getOriginUrl,\n gitClone,\n isGitRepo,\n} from \"../lib/git.js\";\nimport { fileExists } from \"../utils/file-system.js\";\nimport {\n PORTAL_SCAFFOLD_PENDING_MARKER,\n scaffoldPortalProjectIntoExistingDirectory,\n} from \"./create.js\";\n\ninterface CloneOptions {\n app?: string;\n dir?: string;\n id?: string;\n}\n\nfunction getBaseUrl(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nasync function removeIncompleteCheckout(\n targetDir: string,\n): Promise<string | null> {\n try {\n await rm(targetDir, { recursive: true, force: true });\n return null;\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n\nfunction createClient(token: string): FluidOsClient {\n return createFluidOsClient({\n baseUrl: getBaseUrl(),\n token,\n });\n}\n\n/**\n * Human message for the git-sync-unavailable fallback, split by conflict\n * type. `not_connected` is a durable state (the company's Git integration\n * isn't connected), so it must not claim that waiting will fix anything;\n * `not_provisioned` genuinely may resolve later, so it says so.\n */\nexport function gitSyncUnavailableNotice(outcome: {\n readonly reason: string;\n readonly conflictType?: string;\n readonly repoStatus?: string;\n}): string {\n const attachHint = `Creating the portal locally from the starter template so you can start now — ${chalk.cyan(\"fluid portal push\")}/${chalk.cyan(\"pull\")} sync content without Git, and Git history can attach later with ${chalk.cyan(\"fluid portal push\")}.`;\n\n if (outcome.repoStatus === \"disconnected\") {\n return `The portal's Git repository was disconnected, so there is nothing to clone. ${attachHint}`;\n }\n if (outcome.repoStatus === \"error\") {\n return `The portal's Git repository failed to provision. ${attachHint}`;\n }\n if (outcome.conflictType === \"not_connected\") {\n // Reached only after the retry budget is exhausted — the repo was still\n // being provisioned the whole time, which is unusual but not an error.\n return (\n \"The portal's Git repository is still being provisioned — it didn't come online in time. \" +\n attachHint\n );\n }\n if (outcome.conflictType === \"not_provisioned\") {\n return `The portal's Git repository isn't provisioned yet. ${attachHint}`;\n }\n return `Git sync isn't available (${outcome.reason}). ${attachHint}`;\n}\n\n/**\n * A git-less fallback scaffold that failed or was interrupted leaves the\n * pending marker behind with no `.git`. Only that exact shape is safe to\n * resume — anything else is an unrelated pre-existing directory.\n */\nexport async function canResumeGitlessPortalScaffold(\n targetDir: string,\n): Promise<boolean> {\n return (\n (await fileExists(join(targetDir, PORTAL_SCAFFOLD_PENDING_MARKER))) &&\n !existsSync(join(targetDir, \".git\"))\n );\n}\n\nexport async function canResumePortalScaffold(\n targetDir: string,\n credentialRemoteUrl: string,\n): Promise<boolean> {\n if (\n !(await fileExists(join(targetDir, PORTAL_SCAFFOLD_PENDING_MARKER))) ||\n !(await isGitRepo(targetDir))\n ) {\n return false;\n }\n const origin = await getOriginUrl(targetDir);\n if (!origin) return false;\n const expected = cleanGitRemoteUrl(credentialRemoteUrl);\n return origin.replace(/\\/$/, \"\") === expected.replace(/\\/$/, \"\");\n}\n\ninterface ResolvedDefinition {\n id: number;\n name: string;\n}\n\ninterface DefinitionSummary {\n id?: number;\n name?: string | null;\n}\n\nexport function resolveDefinition(\n definitions: readonly DefinitionSummary[],\n appName?: string,\n rawDefinitionId?: string,\n): ResolvedDefinition | null {\n if (rawDefinitionId !== undefined) {\n if (!/^[1-9]\\d*$/.test(rawDefinitionId)) {\n throw new Error(\"--id must be a positive integer\");\n }\n const definitionId = Number.parseInt(rawDefinitionId, 10);\n const match = definitions.find(\n (definition) => definition.id === definitionId,\n );\n if (!match) {\n throw new Error(`No definition found with id ${definitionId}.`);\n }\n return {\n id: definitionId,\n name: match.name ?? `portal-${definitionId}`,\n };\n }\n\n if (!appName) return null;\n const match = definitions.find(\n (definition) => definition.name?.toLowerCase() === appName.toLowerCase(),\n );\n if (!match || match.id == null) {\n const available = definitions\n .map((definition) => chalk.cyan(definition.name ?? \"(unnamed)\"))\n .join(\", \");\n throw new Error(\n `No definition found matching \"${appName}\". Available: ${available}`,\n );\n }\n return { id: match.id, name: match.name ?? `portal-${match.id}` };\n}\n\n/** Select a definition by --app name, else interactively. */\nasync function selectDefinition(\n client: FluidOsClient,\n appName?: string,\n rawDefinitionId?: string,\n): Promise<ResolvedDefinition> {\n const definitions: DefinitionSummary[] = [];\n let page = 1;\n while (true) {\n const response = await fluidOsApi.listDefinitions(client, {\n page,\n per_page: 500,\n });\n const batch = response.definitions ?? [];\n definitions.push(...batch);\n const totalPages = response.meta?.total_pages ?? 1;\n if (page >= totalPages || batch.length === 0) break;\n page++;\n }\n\n if (definitions.length === 0) {\n throw new Error(\n \"No Fluid OS definitions found. Create one in the admin dashboard first.\",\n );\n }\n\n const resolved = resolveDefinition(definitions, appName, rawDefinitionId);\n if (resolved) return resolved;\n\n const { definitionId } = await prompts({\n type: \"select\",\n name: \"definitionId\",\n message: \"Select a Fluid OS definition to clone\",\n choices: definitions\n .filter((d) => d.id != null)\n .map((d) => ({ title: d.name ?? \"(unnamed)\", value: d.id })),\n });\n if (definitionId == null) throw new Error(\"No definition selected.\");\n const selected = definitions.find((d) => d.id === definitionId);\n if (!selected || selected.id == null) {\n throw new Error(\"Selected definition not found.\");\n }\n return { id: selected.id, name: selected.name ?? `portal-${selected.id}` };\n}\n\nexport const cloneCommand: Command = new Command(\"clone\")\n .description(\n \"Clone a portal definition's provisioned GitHub repo (mints a fresh token per call)\",\n )\n .argument(\"[app]\", \"Definition name (prompts to select when omitted)\")\n .option(\"--app <name>\", \"Definition name (same as the positional argument)\")\n .option(\"--id <id>\", \"Definition id (avoids ambiguity when names repeat)\")\n .option(\"--dir <dir>\", \"Target directory (defaults to ./<slug>/)\")\n .action(async (appArg: string | undefined, options: CloneOptions) => {\n try {\n const token = getAuthToken();\n if (!token) {\n const profile = getActiveProfile();\n throw new Error(\n profile\n ? `No auth token for profile ${chalk.cyan(profile.name)}. Run ${chalk.cyan(\"fluid login\")}.`\n : `Not logged in. Run ${chalk.cyan(\"fluid login\")} first.`,\n );\n }\n const client = createClient(token);\n\n const spinner = ora(\"Resolving definition…\").start();\n const definition = await selectDefinition(\n client,\n options.app ?? appArg,\n options.id,\n );\n\n spinner.text = \"Minting git credential…\";\n const outcome = await createPortalGitCredentialWithRetry({\n baseUrl: getBaseUrl(),\n token,\n definitionId: definition.id,\n onRetry: ({ attempt, totalAttempts }) => {\n spinner.text = `Preparing Git repository… retrying (${attempt}/${totalAttempts})`;\n },\n });\n if (outcome.status === \"error\") {\n spinner.warn(`Can't clone — ${outcome.reason}`);\n process.exit(1);\n }\n\n const slug = deriveSlug(definition.name);\n const targetDir = resolve(options.dir ?? slug);\n\n if (outcome.status === \"unavailable\") {\n // No repository to clone — not a transient failure worth a scary\n // exit. Local files + API sync are a fully working portal, so\n // scaffold the bundled starter into the target directory instead\n // and let Git attach later if/when it becomes available.\n spinner.info(gitSyncUnavailableNotice(outcome));\n const resumeGitless =\n existsSync(targetDir) &&\n (await canResumeGitlessPortalScaffold(targetDir));\n if (existsSync(targetDir) && !resumeGitless) {\n spinner.fail(\n `Directory ${targetDir} already exists. Pass --dir or remove it first.`,\n );\n process.exit(1);\n }\n if (!resumeGitless) {\n await mkdir(dirname(targetDir), { recursive: true });\n try {\n // Reserve the exact destination (same race-closing pattern as\n // the clone path below).\n await mkdir(targetDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n spinner.fail(\n `Directory ${targetDir} was created by another process. Choose a different --dir or retry after it finishes.`,\n );\n process.exit(1);\n }\n throw error;\n }\n }\n try {\n spinner.text = resumeGitless\n ? \"Resuming portal scaffold…\"\n : \"Scaffolding portal from the starter template…\";\n await scaffoldPortalProjectIntoExistingDirectory(targetDir, {\n name: slug,\n installDeps: false,\n selectedPages: [],\n profileName: getActiveProfile()?.name ?? \"\",\n });\n } catch (err) {\n spinner.fail(\n `Git sync is unavailable and the local scaffold failed at ${targetDir}. Fix the reported path or permission conflict and retry.`,\n );\n throw err;\n }\n spinner.succeed(\n `Created ${chalk.cyan(targetDir)} from the starter template (no Git history yet)`,\n );\n console.log(chalk.dim(` cd ${targetDir}`));\n console.log(\n chalk.dim(` ${chalk.cyan(\"fluid portal pull\")} to sync content`),\n );\n return;\n }\n\n await ensureGitAvailable();\n const resumeScaffold =\n existsSync(targetDir) &&\n (await canResumePortalScaffold(\n targetDir,\n outcome.credential.remote_url,\n ));\n if (existsSync(targetDir) && !resumeScaffold) {\n spinner.fail(\n `Directory ${targetDir} already exists. Pass --dir or remove it first.`,\n );\n process.exit(1);\n }\n\n if (!resumeScaffold) {\n await mkdir(dirname(targetDir), { recursive: true });\n try {\n // Reserve the exact destination after the friendly pre-check. This\n // closes the race where two clone commands both saw a missing path\n // and one later deleted the other's checkout during failure cleanup.\n await mkdir(targetDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n spinner.fail(\n `Directory ${targetDir} was created by another process. Choose a different --dir or retry after it finishes.`,\n );\n process.exit(1);\n }\n throw error;\n }\n\n spinner.text = `Cloning into ${slug}/…`;\n try {\n // Token-bearing URL is passed inline — never written to .git/config.\n await gitClone(outcome.credential.remote_url, targetDir);\n } catch (err) {\n const cleanupError = await removeIncompleteCheckout(targetDir);\n spinner.fail(\n cleanupError\n ? `git clone failed, and Fluid could not remove the incomplete checkout: ${cleanupError}`\n : \"git clone failed; removed the incomplete checkout\",\n );\n throw err;\n }\n\n // Scope the fresh checkout to portal content up front, so even a\n // hand-run `git add -A` (before the user's first `fluid portal\n // push`) can't sweep in Mist Desktop's private state. The sync\n // re-applies this on every push, which is what heals clones made\n // before this existed.\n excludeLocalStateFromGit(targetDir);\n }\n\n if (\n resumeScaffold ||\n !(await fileExists(join(targetDir, \"package.json\")))\n ) {\n try {\n spinner.text = resumeScaffold\n ? \"Resuming runnable portal scaffold…\"\n : \"Adding runnable portal scaffold…\";\n await scaffoldPortalProjectIntoExistingDirectory(targetDir, {\n name: slug,\n installDeps: false,\n selectedPages: [],\n profileName: getActiveProfile()?.name ?? \"\",\n });\n } catch (err) {\n spinner.fail(\n `Repository cloned at ${targetDir}, but the runnable scaffold failed. The provisioned Git history and repository files were preserved; fix the reported path or permission conflict and retry the scaffold.`,\n );\n throw err;\n }\n }\n spinner.succeed(`Cloned into ${chalk.cyan(targetDir)}`);\n console.log(chalk.dim(` cd ${targetDir}`));\n console.log(\n chalk.dim(` ${chalk.cyan(\"fluid portal pull\")} to sync content`),\n );\n } catch (error) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (error instanceof Error ? error.message : String(error)),\n );\n process.exit(1);\n }\n });\n\nexport function registerCloneCommand(ctx: PluginContext): void {\n ctx.program.addCommand(cloneCommand);\n}\n","import { findProjectConfig, readConfig } from \"@fluid-app/fluid-cli\";\nimport {\n publishWidgetPackage,\n type PublishedWidgetPackage,\n type WidgetPackageOwner,\n type WidgetPackageOwnerKind,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nimport {\n createFluidOsHeyApiClient,\n type FluidOsHeyApiClient,\n} from \"@fluid-app/fluidos-api-client\";\nimport chalk from \"chalk\";\nimport { getPortalCliVersion } from \"../utils/file-system.js\";\n\nexport interface PublishWidgetRuntimeArtifactsOptions {\n readonly projectDir: string;\n readonly outDir: string;\n readonly buildOwner: WidgetPackageOwnerKind;\n readonly uploadOwner: WidgetPackageOwner;\n readonly dryRun?: boolean;\n readonly client?: FluidOsHeyApiClient;\n /** Publish as this version rather than the config's. */\n readonly version?: string;\n}\n\nexport type PublishWidgetRuntimeArtifactsResult = PublishedWidgetPackage;\n\nexport async function publishWidgetRuntimeArtifacts(\n options: PublishWidgetRuntimeArtifactsOptions,\n): Promise<PublishWidgetRuntimeArtifactsResult> {\n const isDryRun = options.dryRun === true;\n const client = options.client;\n\n if (!isDryRun && !client) {\n throw new Error(\"A FluidOS API client is required when dryRun is false.\");\n }\n\n const cliVersion = await getPortalCliVersion();\n\n // Errors propagate exactly as thrown. This used to re-wrap any error carrying\n // `details` into a plain `Error` with the details appended to the message —\n // which discarded `code` and `diagnostics` on precisely the failures that had\n // the most to say about themselves. Callers that want the details in their\n // output call `printWidgetPublishError`; callers that want to act on the\n // cause still have it.\n if (isDryRun) {\n return await publishWidgetPackage({\n projectDir: options.projectDir,\n outDir: options.outDir,\n owner: options.buildOwner,\n uploadOwner: options.uploadOwner,\n cliVersion,\n dryRun: true,\n });\n }\n\n if (!client) {\n throw new Error(\"A FluidOS API client is required when dryRun is false.\");\n }\n\n return await publishWidgetPackage({\n projectDir: options.projectDir,\n outDir: options.outDir,\n owner: options.buildOwner,\n uploadOwner: options.uploadOwner,\n cliVersion,\n ...(options.version ? { version: options.version } : {}),\n client,\n });\n}\n\n/**\n * Prints a publish failure with its `details` block, which is where the\n * validation tooling names the offending widget and field.\n */\nexport function printWidgetPublishError(error: unknown): void {\n console.error(\n chalk.red(\"Error:\") +\n \" \" +\n (error instanceof Error ? error.message : String(error)),\n );\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"details\" in error &&\n typeof error.details === \"string\" &&\n error.details.length > 0\n ) {\n console.error(error.details);\n }\n}\n\nconst DEFAULT_API_BASE = \"https://api.fluid.app\";\n\n/**\n * Builds the client used to publish widget artifacts.\n *\n * `FLUID_TOKEN` / `FLUID_API_TOKEN` win over any stored profile, matching the\n * resolution order the rest of the CLI documents and implements (see\n * `getAuthToken` in `@fluid-app/fluid-cli`). This path used to read only\n * `~/.fluid/config.json`, which made it the one command a caller could not\n * point at a chosen company: a host that spawns the CLI would publish to\n * whichever profile the terminal happened to have active, and shipping a\n * company's widgets to the wrong tenant is not a failure they could detect from\n * the output.\n *\n * With the env token set, the base URL comes from `FLUID_API_BASE` rather than\n * the stored profile's — a token issued elsewhere has no relationship to the\n * host that profile points at.\n */\nexport function createAuthenticatedWidgetPackageClient(): FluidOsHeyApiClient {\n const envToken =\n process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"] ?? \"\";\n if (envToken.length > 0) {\n return createFluidOsHeyApiClient({\n baseUrl: process.env[\"FLUID_API_BASE\"] ?? DEFAULT_API_BASE,\n getAuthToken: () => envToken,\n });\n }\n\n const { profile, profileName, token } = resolveAuthenticatedProfile();\n if (!token) {\n if (!profileName) {\n throw new Error(\n \"Not logged in. Run \" + chalk.cyan(\"fluid login\") + \" first.\",\n );\n }\n throw new Error(\n \"No auth token found for profile \" +\n chalk.cyan(profileName) +\n \". Run \" +\n chalk.cyan(\"fluid login\") +\n \" to re-authenticate.\",\n );\n }\n\n return createFluidOsHeyApiClient({\n baseUrl:\n profile?.baseUrl ?? process.env[\"FLUID_API_BASE\"] ?? DEFAULT_API_BASE,\n getAuthToken: () => token,\n });\n}\n\ntype ProfileWithBaseUrl = ReturnType<typeof readConfig>[\"profiles\"][string] & {\n readonly baseUrl?: string;\n};\n\nfunction resolveAuthenticatedProfile(): {\n readonly profile?: ProfileWithBaseUrl;\n readonly profileName?: string;\n readonly token?: string;\n} {\n const config = readConfig();\n const projectConfig = findProjectConfig(process.cwd());\n\n if (projectConfig?.profile) {\n const profile = config.profiles[projectConfig.profile] as\n | ProfileWithBaseUrl\n | undefined;\n if (profile) {\n return {\n profile,\n profileName: projectConfig.profile,\n token: profile.token,\n };\n }\n }\n\n if (!config.activeProfile) return {};\n const profile = config.profiles[config.activeProfile] as\n | ProfileWithBaseUrl\n | undefined;\n if (!profile) return {};\n\n return {\n profile,\n profileName: config.activeProfile,\n token: profile.token,\n };\n}\n\nexport function printWidgetPublishSummary(options: {\n readonly title: string;\n readonly ownerLabel: string;\n readonly environment?: string;\n readonly outDir: string;\n readonly packageId: string;\n readonly version: string;\n readonly artifactCount: number;\n}): void {\n console.log();\n console.log(chalk.green.bold(`${options.title} complete`));\n console.log();\n console.log(chalk.gray(\"Owner: \") + chalk.white(options.ownerLabel));\n if (options.environment) {\n console.log(chalk.gray(\"Environment: \") + chalk.white(options.environment));\n }\n console.log(chalk.gray(\"Package: \") + chalk.white(options.packageId));\n console.log(chalk.gray(\"Version: \") + chalk.white(options.version));\n console.log(chalk.gray(\"Output: \") + chalk.cyan(options.outDir));\n console.log(\n chalk.gray(\"Artifacts: \") + chalk.white(String(options.artifactCount)),\n );\n console.log();\n}\n\nexport function printRuntimeOnlyNotice(): void {\n console.log(\n chalk.yellow(\n \"This publishes widget runtime artifacts only; it does not deploy the hosted portal shell.\",\n ),\n );\n console.log();\n}\n\nexport function printDryRunSessionPayload(sessionPayload: unknown): void {\n console.log(chalk.bold(\"Dry-run upload session payload:\"));\n console.log(JSON.stringify(sessionPayload, null, 2));\n console.log();\n}\n","/**\n * Pure helper functions for widget scaffolding.\n * Extracted from widget-create command for testability.\n */\n\n/**\n * Convert kebab-case name to PascalCase widget type.\n * e.g., \"stock-ticker\" → \"StockTickerWidget\"\n */\nexport function toWidgetType(name: string): string {\n const pascal = name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\"\");\n return pascal.endsWith(\"Widget\") ? pascal : `${pascal}Widget`;\n}\n\n/**\n * Derive the component name from a widget type.\n * Strips the trailing \"Widget\" suffix, but only if the result is non-empty\n * and starts with a letter (not a digit).\n * e.g., \"StockTickerWidget\" → \"StockTicker\"\n */\nexport function toComponentName(widgetType: string): string {\n if (widgetType === \"Widget\") return \"Widget\";\n const stripped = widgetType.replace(/Widget$/, \"\");\n return stripped || widgetType;\n}\n\n/**\n * Convert kebab-case name to a display name.\n * e.g., \"stock-ticker\" → \"Stock Ticker\"\n */\nexport function toDisplayName(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \");\n}\n\n/**\n * Convert kebab-case to camelCase.\n * e.g., \"stock-ticker\" → \"stockTicker\"\n */\nexport function toCamelCase(name: string): string {\n return name.replace(/-./g, (x) => x.charAt(1).toUpperCase());\n}\n\n/**\n * Validate a widget name for scaffold.\n * Must be kebab-case, no trailing/consecutive dashes, no bare \"widget\".\n */\nexport function validateWidgetName(name: string): string | null {\n if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name)) {\n return \"Widget name must be kebab-case with no trailing or consecutive dashes (e.g., stock-ticker).\";\n }\n if (name === \"widget\") {\n return 'Widget name \"widget\" is reserved. Use a more descriptive name (e.g., custom-widget).';\n }\n return null;\n}\n\n/**\n * Insert an import line after the last import in a source file.\n * Skips if the import already exists (prevents duplicates on re-scaffold).\n * Returns null if no imports exist in the source.\n */\nexport function insertImport(\n source: string,\n importLine: string,\n): string | null {\n // Skip if import already exists\n if (source.includes(importLine)) return source;\n\n // Find the last real import statement (starts at column 0, not inside comments)\n const importPattern = /^import\\s/gm;\n let lastImportIndex = -1;\n let match: RegExpExecArray | null;\n while ((match = importPattern.exec(source)) !== null) {\n lastImportIndex = match.index;\n }\n if (lastImportIndex === -1) return null;\n\n const lineEnd = source.indexOf(\"\\n\", lastImportIndex);\n if (lineEnd === -1) {\n // Last import is on the final line with no trailing newline\n return source + \"\\n\" + importLine + \"\\n\";\n }\n\n return (\n source.slice(0, lineEnd + 1) + importLine + \"\\n\" + source.slice(lineEnd + 1)\n );\n}\n\n/** Insert a widget definition into a defineWidgetPackage widgets array. */\nexport function insertIntoWidgetPackage(\n source: string,\n exportName: string,\n): string | null {\n const packageStart = source.lastIndexOf(\"defineWidgetPackage(\");\n if (packageStart === -1) return null;\n\n const packageSource = source.slice(packageStart);\n const pattern = /(widgets\\s*:\\s*)\\[([^\\]]*)\\]/g;\n let lastMatch: RegExpExecArray | null = null;\n let m: RegExpExecArray | null;\n while ((m = pattern.exec(packageSource)) !== null) {\n lastMatch = m;\n }\n\n if (!lastMatch) return null;\n\n const fullMatch = lastMatch[0];\n const declaration = lastMatch[1]!;\n const inner = lastMatch[2]!;\n const matchStart = packageStart + lastMatch.index;\n\n const lines = inner.split(\"\\n\");\n\n // Collect existing real entries (stripped of commas for comparison)\n const existingEntries: string[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith(\"//\")) {\n existingEntries.push(trimmed.replace(/,$/, \"\"));\n }\n }\n\n // Skip if already registered\n if (existingEntries.includes(exportName)) {\n return source;\n }\n\n // Preserve comment lines\n const commentLines = lines\n .filter((line) => line.trim().startsWith(\"//\"))\n .map((line) => line.trimEnd());\n\n const commentBlock =\n commentLines.length > 0 ? \"\\n\" + commentLines.join(\"\\n\") : \"\";\n\n // Build entry lines with consistent 2-space indentation\n const allEntries = [...existingEntries, exportName];\n const entryBlock = allEntries.map((e) => ` ${e},`).join(\"\\n\");\n\n const replacement = `${declaration}[${commentBlock}\\n${entryBlock}\\n]`;\n\n return (\n source.slice(0, matchStart) +\n replacement +\n source.slice(matchStart + fullMatch.length)\n );\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport path from \"node:path\";\nimport {\n readCompanyWidgetPackages,\n type CompanyWidgetPackage,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nimport type { WidgetCreateOptions } from \"../types.js\";\nimport { createAuthenticatedWidgetPackageClient } from \"./widget-package-publish.js\";\nimport {\n insertImport,\n insertIntoWidgetPackage,\n toCamelCase,\n toComponentName,\n toDisplayName,\n toWidgetType,\n validateWidgetName,\n} from \"../utils/widget-helpers.js\";\n\nconst WIDGET_CONFIG_PATH = path.join(\"src\", \"widgets.config.ts\");\n\nconst createSubcommand = new Command(\"create\")\n .description(\"Scaffold a new Remote DOM widget\")\n .argument(\"<name>\", \"Widget name in kebab-case (e.g., stock-ticker)\")\n .option(\n \"-c, --category <category>\",\n \"Widget category for palette grouping\",\n \"components\",\n )\n .action(async (name: string, options: WidgetCreateOptions) => {\n const cwd = process.cwd();\n const packageJsonPath = path.join(cwd, \"package.json\");\n const configPath = path.join(cwd, WIDGET_CONFIG_PATH);\n const widgetDir = path.join(cwd, \"src\", \"widgets\", name);\n\n let stylesAppended = false;\n let resolvedPackageKey: string | null = null;\n\n const validationError = validateWidgetName(name);\n if (validationError) {\n console.error(chalk.red(`Error: ${validationError}`));\n process.exit(1);\n }\n\n if (!(await fs.pathExists(packageJsonPath))) {\n console.error(chalk.red(\"Error: No package.json found.\"));\n console.error(\n chalk.yellow(\"Run this command from a Fluid portal project root.\"),\n );\n process.exit(1);\n }\n\n if (await fs.pathExists(widgetDir)) {\n console.error(\n chalk.red(\n `Error: Widget directory already exists: src/widgets/${name}`,\n ),\n );\n process.exit(1);\n }\n\n const widgetType = toWidgetType(name);\n const componentName = toComponentName(widgetType);\n const camelName = toCamelCase(name);\n const exportName = camelName.endsWith(\"Widget\")\n ? camelName\n : `${camelName}Widget`;\n const displayName = toDisplayName(name);\n const category = options.category ?? \"components\";\n const importLine = `import { ${exportName} } from \"./widgets/${name}\";`;\n\n try {\n await fs.ensureDir(widgetDir);\n await fs.writeFile(\n path.join(widgetDir, \"index.tsx\"),\n createWidgetSource({\n name,\n widgetType,\n componentName,\n exportName,\n displayName,\n category,\n }),\n \"utf-8\",\n );\n // Rules go in the PROJECT-ROOT stylesheet, never a co-located one, and\n // the widget module never imports CSS. Both halves matter:\n //\n // • An `import \"./styles.css\"` inside a widget is fatal in dev. Vite\n // answers it with a browser style-injector that pulls in\n // `/@vite/client`, which evaluates `class ErrorOverlay extends\n // HTMLElement` at module top level. There is no HTMLElement in a Web\n // Worker, so it throws while evaluating the package source and NO\n // widget in the package registers — one widget's import takes down\n // every widget in the project.\n //\n // • Only the root file is delivered anyway. Dev serves the descriptor's\n // `cssUrls`, which default to `/styles.css`; the build entry imports\n // `<projectDir>/styles.css`. A co-located file reaches the shadow root\n // on neither path.\n //\n // A stylesheet is still the ONLY way to style a widget — the host\n // sanitizer drops `style` attributes and bans `<style>`.\n await appendWidgetStyles(cwd, name);\n stylesAppended = true;\n\n if (await fs.pathExists(configPath)) {\n const configSource = await fs.readFile(configPath, \"utf-8\");\n if (isEmptyWidgetConfig(configSource)) {\n resolvedPackageKey = await resolveCompanyPackageKey();\n await fs.writeFile(\n configPath,\n createWidgetConfigSource(\n importLine,\n exportName,\n resolvedPackageKey ?? undefined,\n ),\n \"utf-8\",\n );\n } else {\n const withImport = insertImport(configSource, importLine);\n const updated = withImport\n ? insertIntoWidgetPackage(withImport, exportName)\n : null;\n if (!updated) {\n throw new Error(\n `${WIDGET_CONFIG_PATH} must export the starter widgetPackages placeholder or contain a defineWidgetPackage({ widgets: [...] }) declaration.`,\n );\n }\n await fs.writeFile(configPath, updated, \"utf-8\");\n }\n } else {\n resolvedPackageKey = await resolveCompanyPackageKey();\n await fs.writeFile(\n configPath,\n createWidgetConfigSource(\n importLine,\n exportName,\n resolvedPackageKey ?? undefined,\n ),\n \"utf-8\",\n );\n }\n } catch (error) {\n await fs.remove(widgetDir).catch(() => {});\n // The stylesheet append happens before the config update, so a config\n // failure used to leave `.<name> { … }` rules in the project-root sheet\n // for a widget directory that had just been deleted. Cosmetic rather than\n // corrupting — re-running is idempotent — but a rollback that only undoes\n // half of what it did is the kind of thing someone later has to work out\n // from first principles.\n if (stylesAppended) {\n await removeWidgetStyles(cwd, name).catch(() => {});\n }\n console.error(chalk.red(\"Error: Failed to scaffold Remote DOM widget.\"));\n console.error(error);\n process.exit(1);\n }\n\n console.log();\n console.log(\n chalk.green(\"Created widget:\") + ` src/widgets/${name}/index.tsx`,\n );\n console.log(\n chalk.green(\"Registered\") + ` in ${chalk.cyan(WIDGET_CONFIG_PATH)}`,\n );\n if (resolvedPackageKey) {\n console.log(\n chalk.green(\"Package key:\") +\n ` ${chalk.cyan(resolvedPackageKey)} (this company's existing widget package)`,\n );\n }\n console.log();\n console.log(chalk.yellow(\"Next steps:\"));\n console.log(` 1. Edit ${chalk.cyan(`src/widgets/${name}/index.tsx`)}`);\n console.log(\n ` Styles live in ${chalk.cyan(\"styles.css\")} at the project root — widgets must not import CSS.`,\n );\n console.log(\n ` 2. Run ${chalk.cyan(\"fluid portal deploy --dry-run\")} to verify the package`,\n );\n });\n\nexport function createWidgetSource(options: {\n readonly name: string;\n readonly widgetType: string;\n readonly componentName: string;\n readonly exportName: string;\n readonly displayName: string;\n readonly category: string;\n}): string {\n return `import { defineWidget } from \"@fluid-app/portal-sdk/widgets/worker\";\n\ninterface ${options.widgetType}Props {\n readonly title?: string;\n}\n\n// Styling: a stylesheet is the ONLY way to style a widget. The host sanitizes\n// every Remote DOM mutation before it reaches the shadow root, so a\n// \\`style={{ ... }}\\` prop is silently dropped and \\`<style>\\` is a banned tag.\n// Give elements classNames and put the rules in the project-root styles.css\n// (this widget's rules were appended there when it was scaffolded).\n//\n// Do NOT add \\`import \"./styles.css\"\\` here. In dev, Vite answers a CSS import\n// with a browser style-injector that loads \\`/@vite/client\\`, which touches\n// HTMLElement — absent in a Web Worker — and throws while the package is being\n// evaluated, taking down every widget in the project, not just this one.\nfunction ${options.componentName}({ title = ${JSON.stringify(options.displayName)} }: ${options.widgetType}Props) {\n return (\n <section className=\"${options.name}\">\n <h3 className=\"${options.name}__title\">{title}</h3>\n <p className=\"${options.name}__body\">\n Edit this widget in src/widgets/${options.name}/index.tsx\n </p>\n </section>\n );\n}\n\nexport const ${options.exportName} = defineWidget({\n name: ${JSON.stringify(options.componentName)},\n component: ${options.componentName},\n displayName: ${JSON.stringify(options.displayName)},\n description: ${JSON.stringify(`A custom ${options.displayName.toLowerCase()} widget`)},\n icon: \"puzzle-piece\",\n category: ${JSON.stringify(options.category)},\n propertySchema: {\n fields: [{ key: \"title\", label: \"Title\", type: \"text\" }],\n },\n defaultProps: {\n title: ${JSON.stringify(options.displayName)},\n },\n});\n`;\n}\n\n/**\n * Appends a scaffolded widget's rules to the project-root `styles.css`,\n * creating the file when a project has none yet.\n *\n * The root stylesheet is the single sheet the tooling delivers — dev serves it\n * as the descriptor's `cssUrls`, and the build entry imports it — so every\n * widget in a project shares it. Appending (rather than overwriting) keeps\n * earlier widgets' rules intact; re-scaffolding a name that is already present\n * is a no-op rather than a duplicate block.\n */\n/**\n * Removes a widget's appended rules from the project-root `styles.css`.\n *\n * Only used to undo a failed scaffold — `appendWidgetStyles` runs before the\n * config update, so a config failure would otherwise strand rules for a widget\n * directory that has just been deleted. Matches the exact block the append\n * writes and leaves everything else, including a project's own rules and other\n * widgets', untouched.\n */\nexport async function removeWidgetStyles(\n projectDir: string,\n name: string,\n): Promise<void> {\n const stylesPath = path.join(projectDir, \"styles.css\");\n if (!(await fs.pathExists(stylesPath))) return;\n const existing = await fs.readFile(stylesPath, \"utf-8\");\n const block = createWidgetStylesSource(name);\n if (!existing.includes(block)) return;\n const withoutBlock = existing.replace(block, \"\");\n await fs.writeFile(\n stylesPath,\n withoutBlock.replace(/\\n{3,}/gu, \"\\n\\n\"),\n \"utf-8\",\n );\n}\n\nexport async function appendWidgetStyles(\n projectDir: string,\n name: string,\n): Promise<void> {\n const stylesPath = path.join(projectDir, \"styles.css\");\n const block = createWidgetStylesSource(name);\n\n if (!(await fs.pathExists(stylesPath))) {\n await fs.writeFile(stylesPath, `${ROOT_STYLES_HEADER}\\n${block}`, \"utf-8\");\n return;\n }\n\n const existing = await fs.readFile(stylesPath, \"utf-8\");\n if (existing.includes(`.${name} {`)) return;\n const separator = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n await fs.appendFile(stylesPath, `${separator}${block}`, \"utf-8\");\n}\n\nconst ROOT_STYLES_HEADER = `/* Runtime styles for this portal's widgets.\n *\n * One sheet per project: dev serves it to the widget shadow root as the\n * package descriptor's cssUrls, and the package build imports it. Widget\n * modules must NOT import CSS themselves — in dev that pulls \\`/@vite/client\\`\n * into the Web Worker, which throws and stops every widget in the project from\n * registering.\n */\n`;\n\n/**\n * Starter rules for a scaffolded widget.\n *\n * Colors, type, and radii come from the host portal's semantic CSS variables so\n * the widget matches whatever portal it lands on, in both light and dark,\n * without a second hand-written palette. Each `var()` carries a fallback so the\n * widget stays legible in a host that has not adopted the portal theme.\n *\n * Selectors are prefixed with the widget name so nothing leaks into the host.\n */\nfunction createWidgetStylesSource(name: string): string {\n return `.${name} {\n padding: 24px;\n border: 1px solid var(--border, #e5e7eb);\n border-radius: var(--radius-lg, 12px);\n background: var(--card, #ffffff);\n color: var(--card-foreground, #111827);\n font-family: var(--font-body, ui-sans-serif, system-ui, sans-serif);\n}\n\n.${name}__title {\n margin: 0 0 8px;\n color: var(--card-foreground, #111827);\n font-family: var(--font-header, inherit);\n font-size: 1.25rem;\n font-weight: 700;\n}\n\n.${name}__body {\n margin: 0;\n color: var(--muted-foreground, #6b7280);\n}\n`;\n}\n\nfunction isEmptyWidgetConfig(source: string): boolean {\n return /export\\s+const\\s+widgetPackages\\s*=\\s*\\[\\]\\s+as\\s+const\\s*;/u.test(\n source,\n );\n}\n\n/**\n * The key written when the company's real one cannot be resolved.\n *\n * A guess, and one that fails for any company that has already published — a\n * company has exactly ONE widget package and its key is fixed by whichever\n * project published first, so this value is only ever correct by accident.\n */\nconst PLACEHOLDER_PACKAGE_KEY = \"portal-widgets\";\n\nexport function createWidgetConfigSource(\n importLine: string,\n exportName: string,\n packageKey: string = PLACEHOLDER_PACKAGE_KEY,\n): string {\n return `import { defineWidgetPackage } from \"@fluid-app/portal-sdk/widgets/worker\";\n${importLine}\n\nexport const widgetPackage = defineWidgetPackage({\n scope: \"company\",\n packageStableId: ${JSON.stringify(packageKey)},\n version: \"0.0.0-dev\",\n widgets: [${exportName}],\n});\n`;\n}\n\n/**\n * Reads the company's existing widget package key, for a config being CREATED.\n *\n * Scaffolding is the one moment this can be done safely. A widget's type is\n * `company.<key>.<Name>` and those strings are written into screen JSON, so\n * changing the key later orphans every screen using it — which is why\n * `fluid portal deploy` reports a mismatch rather than rewriting it. At\n * `widget create` no screen references the type yet, so resolving it here\n * prevents the mismatch instead of explaining it afterwards.\n *\n * Best-effort by design: this command made no network calls before, and a\n * scaffold must not fail because a lookup did. Every uncertainty — no token, no\n * network, a company that has never published, a slow answer — falls back to the\n * placeholder, which is exactly today's behaviour.\n *\n * The deadline is deliberate here, unlike the SDK's dev-server probe where a\n * timeout discarded a real answer. The fallback IS the correct result for an\n * offline scaffold, so bounding the wait costs nothing and stops a hung API\n * from wedging a local command.\n */\nasync function resolveCompanyPackageKey(): Promise<string | null> {\n try {\n const client = createAuthenticatedWidgetPackageClient();\n const packages = await Promise.race([\n readCompanyWidgetPackages(client),\n new Promise<CompanyWidgetPackage[]>((resolve) => {\n setTimeout(() => resolve([]), PACKAGE_KEY_LOOKUP_TIMEOUT_MS);\n }),\n ]);\n return packages[0]?.key ?? null;\n } catch {\n // Not logged in, or the CLI has no profile for this project. Neither is a\n // reason to refuse to scaffold a widget.\n return null;\n }\n}\n\nconst PACKAGE_KEY_LOOKUP_TIMEOUT_MS = 5000;\n\nexport const widgetCommand: Command = new Command(\"widget\")\n .description(\"Manage Remote DOM portal widgets\")\n .addCommand(createSubcommand);\n\nexport function registerWidgetCommand(ctx: PluginContext): void {\n ctx.program.addCommand(widgetCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport type { FluidOsHeyApiClient } from \"@fluid-app/fluidos-api-client\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport type { PortalDeployOptions } from \"../types.js\";\nimport {\n loadAndValidateWidgetPackage,\n preflightCompanyPackage,\n type CompanyPackagePreflight,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nimport {\n createAuthenticatedWidgetPackageClient,\n printRuntimeOnlyNotice,\n printDryRunSessionPayload,\n printWidgetPublishSummary,\n publishWidgetRuntimeArtifacts,\n} from \"./widget-package-publish.js\";\n\nconst DEFAULT_OUT_DIR = \".fluid/widget-dist\";\nconst DEFAULT_ENVIRONMENT = \"production\";\n\nexport const deployCommand: Command = new Command(\"deploy\")\n .description(\"Publish company-owned portal widget runtime artifacts\")\n .option(\n \"-e, --environment <name>\",\n \"Target environment label for output reporting\",\n DEFAULT_ENVIRONMENT,\n )\n .option(\n \"-o, --out-dir <dir>\",\n \"Widget artifact output directory\",\n DEFAULT_OUT_DIR,\n )\n .option(\"--dry-run\", \"Build and validate upload payload without publishing\")\n .action(async (options: PortalDeployOptions) => {\n const environment = options.environment ?? DEFAULT_ENVIRONMENT;\n const outDir = options.outDir ?? DEFAULT_OUT_DIR;\n const dryRun = options.dryRun === true;\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Portal Deploy\"));\n console.log();\n printRuntimeOnlyNotice();\n console.log(chalk.gray(\"Environment: \") + chalk.white(environment));\n console.log(chalk.gray(\"Output: \") + chalk.cyan(outDir));\n if (dryRun)\n console.log(chalk.yellow(\"Dry run: no upload will be created.\"));\n console.log();\n\n const spinner = ora(\"Building company-owned widget package...\").start();\n const result = await runPortalDeploy({\n projectDir: process.cwd(),\n outDir,\n environment,\n dryRun,\n onProgress: (label) => {\n spinner.text = label;\n },\n });\n\n if (!result.ok) {\n spinner.fail(\"Portal deploy failed\");\n console.log();\n if (result.code === \"PACKAGE_KEY_MISMATCH\") {\n console.log(chalk.red(\"Package key does not match this company.\"));\n } else {\n console.error(chalk.red(\"Error:\") + \" \" + result.message);\n }\n if (result.hint) {\n console.log();\n console.log(result.hint);\n }\n console.log();\n process.exit(1);\n }\n\n spinner.succeed(\"Built widget runtime artifacts\");\n\n if (dryRun) {\n console.log(\n chalk.yellow(\"Dry run complete — upload session was not requested.\"),\n );\n console.log();\n if (result.sessionPayload !== undefined) {\n printDryRunSessionPayload(result.sessionPayload);\n }\n } else {\n console.log(chalk.green(\"Published widget package version.\"));\n }\n\n if (result.requestedVersion) {\n console.log(\n chalk.yellow(\n `Version ${result.requestedVersion} is already published for this company, so this went out as ${result.version}.`,\n ),\n );\n console.log(\n chalk.gray(\n \"Every portal project in a company shares one widget package, so its versions are shared too.\",\n ),\n );\n console.log();\n }\n\n printWidgetPublishSummary({\n title: dryRun ? \"Portal deploy dry run\" : \"Portal deploy\",\n ownerLabel: \"company\",\n environment,\n outDir,\n packageId: result.packageId,\n version: result.version,\n artifactCount: result.artifactCount,\n });\n });\n\nexport interface RunPortalDeployOptions {\n readonly projectDir: string;\n readonly outDir: string;\n readonly environment: string;\n readonly dryRun: boolean;\n /** Lets an interactive caller keep a spinner honest during a multi-second run. */\n readonly onProgress?: (label: string) => void;\n}\n\n/** What a deploy produced, for this command's own rendering. */\nexport type RunPortalDeployResult =\n | {\n readonly ok: true;\n readonly packageId: string;\n readonly version: string;\n readonly artifactCount: number;\n /** Set when the config's version was taken and the publish moved past it. */\n readonly requestedVersion?: string;\n readonly sessionPayload?: unknown;\n }\n | {\n readonly ok: false;\n readonly code: string;\n readonly message: string;\n readonly hint?: string;\n };\n\n/**\n * The whole deploy, as one function returning a result.\n *\n * A result rather than a throw because two of its outcomes — a key that belongs\n * to another package, a version already published — are refusals with a fix to\n * show, not exceptions.\n */\nexport async function runPortalDeploy(\n options: RunPortalDeployOptions,\n): Promise<RunPortalDeployResult> {\n let client: FluidOsHeyApiClient | undefined;\n if (!options.dryRun) {\n try {\n client = createAuthenticatedWidgetPackageClient();\n } catch (error) {\n return {\n ok: false,\n code: \"NOT_AUTHENTICATED\",\n message: \"Not signed in to Fluid, so the widgets were not published.\",\n ...(error instanceof Error ? { hint: error.message } : {}),\n };\n }\n }\n\n // Preflight before building. Two failures live here, and both are refusals\n // whose fix is a line in a TypeScript file that the error names nowhere:\n // `422 Package key does not match this widget package` and `422 Package\n // version already exists`. Asking first turns both into something actionable.\n //\n // Skipped on --dry-run: that path never contacts the server, and requiring\n // auth to validate a local build would make the flag useless offline.\n let preflight: CompanyPackagePreflight = {};\n if (client) {\n options.onProgress?.(\"Checking this company's widget package...\");\n preflight = await preflightCompanyPackage({\n projectDir: options.projectDir,\n client,\n readConfiguredPackage: async (dir) => {\n const { validated } = await loadAndValidateWidgetPackage({\n projectDir: dir,\n owner: \"company\",\n });\n return { packageId: validated.packageId, version: validated.version };\n },\n });\n if (preflight.mismatch) {\n return {\n ok: false,\n code: \"PACKAGE_KEY_MISMATCH\",\n message: \"This project publishes under a different package key.\",\n hint: preflight.mismatch,\n };\n }\n }\n\n options.onProgress?.(\"Building company-owned widget package...\");\n try {\n const result = await publishWidgetRuntimeArtifacts({\n projectDir: options.projectDir,\n outDir: options.outDir,\n buildOwner: \"company\",\n uploadOwner: { kind: \"company\" },\n dryRun: options.dryRun,\n ...(preflight.versionOverride\n ? { version: preflight.versionOverride }\n : {}),\n ...(client ? { client } : {}),\n });\n\n return {\n ok: true,\n packageId: result.build.packageId,\n version: result.build.version,\n artifactCount: result.request.artifacts.length,\n // Reported rather than done quietly. The config still says the old\n // version, and a publish that silently disagreed with the file would be\n // its own small mystery later.\n ...(preflight.versionOverride && preflight.configuredVersion\n ? { requestedVersion: preflight.configuredVersion }\n : {}),\n sessionPayload: result.publish.sessionPayload,\n };\n } catch (error) {\n return {\n ok: false,\n code:\n error instanceof Error && \"code\" in error\n ? String((error as { code: unknown }).code)\n : \"PUBLISH_FAILED\",\n message:\n error instanceof Error\n ? error.message\n : \"Publishing the widgets failed.\",\n ...(error instanceof Error &&\n \"details\" in error &&\n typeof error.details === \"string\" &&\n error.details.length > 0\n ? { hint: error.details }\n : {}),\n };\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 { existsSync, readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Severity levels for drift diagnostics.\n * - ERROR: Security-critical or breaking drift (e.g., missing CSP)\n * - WARN: Stale config that may cause subtle issues\n * - INFO: Cosmetic or non-functional differences\n */\nexport type Severity = \"error\" | \"warn\" | \"info\";\n\nexport interface Diagnostic {\n file: string;\n severity: Severity;\n message: string;\n}\n\n/** Files that are managed by the SDK and should match the canonical template. */\nconst INFRASTRUCTURE_FILES = [\n \"index.html\",\n \"tsconfig.json\",\n \"vite.config.ts\",\n \".oxlintrc.json\",\n] as const;\n\n/** Entry files with expected content patterns after Phase 2 absorption. */\nconst ENTRY_CHECKS = [\n {\n file: \"src/main.tsx\",\n expectedPattern: \"createPortal\",\n hint: 'main.tsx should use createPortal() from @fluid-app/portal-sdk. Run \"fluid create\" to see the latest template.',\n },\n {\n file: \"src/index.css\",\n expectedPattern: \"@fluid-app/portal-sdk/globals.css\",\n hint: \"index.css should import @fluid-app/portal-sdk/globals.css instead of inline theme tokens.\",\n },\n] as const;\n\n/** Files that should NOT exist after Phase 2 (absorbed into SDK). */\nconst REMOVED_FILES = [\n {\n file: \"src/App.tsx\",\n hint: \"App.tsx is no longer needed — createPortal() handles the AppShell wiring internally.\",\n },\n {\n file: \"src/fluid.config.ts\",\n hint: \"fluid.config.ts is no longer needed — pass config overrides to createPortal() directly.\",\n },\n] as const;\n\nfunction readFileOrNull(filePath: string): string | null {\n try {\n return readFileSync(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nconst _currentFile = fileURLToPath(import.meta.url);\nconst _currentDir = dirname(_currentFile);\n\nfunction findTemplateDir(): string | null {\n // Walk up from current file to find package root (works in both dist/ and src/)\n let dir = _currentDir;\n while (!existsSync(join(dir, \"package.json\"))) {\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n const templateDir = join(dir, \"templates\");\n if (existsSync(join(templateDir, \"base\"))) return templateDir;\n return null;\n}\n\nexport function checkInfrastructureDrift(\n cwd: string,\n templateDir: string,\n): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n for (const file of INFRASTRUCTURE_FILES) {\n const portalPath = join(cwd, file);\n const templatePath = join(templateDir, \"base\", file);\n\n if (!existsSync(portalPath)) {\n diagnostics.push({\n file,\n severity: \"warn\",\n message: `Missing file: ${file}`,\n });\n continue;\n }\n\n if (!existsSync(templatePath)) {\n // Template doesn't have this file — skip\n continue;\n }\n\n const portalContent = readFileOrNull(portalPath);\n const templateContent = readFileOrNull(templatePath);\n\n if (portalContent !== null && templateContent !== null) {\n if (portalContent.trim() !== templateContent.trim()) {\n diagnostics.push({\n file,\n severity: \"warn\",\n message: `Content differs from canonical template. Review and update if needed.`,\n });\n }\n }\n }\n\n return diagnostics;\n}\n\nexport function checkEntryPatterns(cwd: string): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n for (const check of ENTRY_CHECKS) {\n const filePath = join(cwd, check.file);\n const content = readFileOrNull(filePath);\n\n if (content === null) {\n diagnostics.push({\n file: check.file,\n severity: \"error\",\n message: `Missing file. ${check.hint}`,\n });\n continue;\n }\n\n if (!content.includes(check.expectedPattern)) {\n diagnostics.push({\n file: check.file,\n severity: \"warn\",\n message: `Does not contain expected pattern \"${check.expectedPattern}\". ${check.hint}`,\n });\n }\n }\n\n return diagnostics;\n}\n\nexport function checkRemovedFiles(cwd: string): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n for (const check of REMOVED_FILES) {\n if (existsSync(join(cwd, check.file))) {\n diagnostics.push({\n file: check.file,\n severity: \"info\",\n message: `File can be removed. ${check.hint}`,\n });\n }\n }\n\n return diagnostics;\n}\n\nfunction formatDiagnostic(d: Diagnostic): string {\n const icon =\n d.severity === \"error\"\n ? chalk.red(\"ERROR\")\n : d.severity === \"warn\"\n ? chalk.yellow(\" WARN\")\n : chalk.blue(\" INFO\");\n\n return ` ${icon} ${chalk.bold(d.file)}\\n ${chalk.dim(d.message)}`;\n}\n\nexport const doctorCommand: Command = new Command(\"doctor\")\n .description(\n \"Check portal for scaffold drift and report stale infrastructure files\",\n )\n .action(async () => {\n const cwd = process.cwd();\n\n // Validate we're in a portal 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 portal project directory\"),\n );\n process.exit(1);\n }\n\n let packageJson: {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n try {\n packageJson = JSON.parse(readFileSync(packageJsonPath, \"utf-8\"));\n } catch {\n console.error(chalk.red(\"Error: Could not parse package.json\"));\n console.error(chalk.yellow(\"Ensure package.json contains valid JSON\"));\n process.exit(1);\n }\n const deps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n };\n\n if (!deps[\"@fluid-app/portal-sdk\"]) {\n console.error(\n chalk.red(\"Error: @fluid-app/portal-sdk not found in dependencies\"),\n );\n console.error(\n chalk.yellow(\n \"This command must be run from a Fluid portal project directory\",\n ),\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold(\"Fluid Portal Doctor\"));\n console.log(chalk.dim(\"Checking for scaffold drift...\\n\"));\n\n const diagnostics: Diagnostic[] = [];\n\n // Check entry file patterns\n diagnostics.push(...checkEntryPatterns(cwd));\n\n // Check for files that should be removed\n diagnostics.push(...checkRemovedFiles(cwd));\n\n // Check infrastructure drift against templates (if available)\n const templateDir = findTemplateDir();\n if (templateDir) {\n diagnostics.push(...checkInfrastructureDrift(cwd, templateDir));\n } else {\n console.log(\n chalk.dim(\n \" (Skipping infrastructure diff — template files not available)\\n\",\n ),\n );\n }\n\n // Report results\n if (diagnostics.length === 0) {\n console.log(chalk.green(\" All clear — no drift detected.\\n\"));\n return;\n }\n\n const errors = diagnostics.filter((d) => d.severity === \"error\");\n const warns = diagnostics.filter((d) => d.severity === \"warn\");\n const infos = diagnostics.filter((d) => d.severity === \"info\");\n\n for (const d of [...errors, ...warns, ...infos]) {\n console.log(formatDiagnostic(d));\n console.log();\n }\n\n // Summary\n const parts: string[] = [];\n if (errors.length > 0) parts.push(chalk.red(`${errors.length} error(s)`));\n if (warns.length > 0)\n parts.push(chalk.yellow(`${warns.length} warning(s)`));\n if (infos.length > 0) parts.push(chalk.blue(`${infos.length} info`));\n console.log(` ${parts.join(\", \")}\\n`);\n\n if (errors.length > 0) {\n process.exit(1);\n }\n });\n\nexport function registerDoctorCommand(ctx: PluginContext): void {\n ctx.program.addCommand(doctorCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport { createFluidOsClient, fluidOsApi } from \"../api/fluidos.js\";\nimport prompts from \"prompts\";\nimport path from \"node:path\";\nimport { readMappings } from \"../utils/mappings.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PORTAL_SYNC_DIR = \".portal-sync\";\n\nfunction getApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nfunction requireToken(): string {\n const token = getAuthToken();\n if (!token) {\n console.error(\n chalk.red(\"Error:\") +\n \" Not logged in. Run \" +\n chalk.cyan(\"`fluid login`\") +\n \" first.\",\n );\n process.exit(1);\n }\n return token;\n}\n\nfunction createClient(token: string) {\n return createFluidOsClient({\n baseUrl: getApiBase(),\n token,\n });\n}\n\nasync function requireDefinitionId(): Promise<number> {\n const cwd = process.cwd();\n const mappings = await readMappings(path.join(cwd, PORTAL_SYNC_DIR));\n if (!mappings) {\n console.error(\n chalk.red(\"Error:\") +\n \" No definition pulled. Run \" +\n chalk.cyan(\"`fluid portal pull`\") +\n \" first.\",\n );\n process.exit(1);\n }\n return mappings.definition.id;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Subcommands\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst createVersionCommand = new Command(\"create\")\n .description(\"Create a new version (snapshot) of the portal definition\")\n .option(\"--activate\", \"Activate the version immediately after creation\")\n .action(async (options: { activate?: boolean }) => {\n const token = requireToken();\n const client = createClient(token);\n const definitionId = await requireDefinitionId();\n\n console.log();\n console.log(chalk.bold(\"Creating version...\"));\n\n let result;\n try {\n result = await fluidOsApi.createVersion(client, definitionId);\n } catch (err) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to create version — \" +\n (err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n\n const version = result.version;\n\n if (!version?.id) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to create version — unexpected response.\",\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.green(\"Version created successfully.\"));\n console.log();\n console.log(chalk.gray(\"Version ID: \") + chalk.white(version.id));\n\n let active = version.active ?? false;\n\n if (options.activate) {\n console.log(\"Activating version...\");\n try {\n await fluidOsApi.updateVersion(client, definitionId, version.id, {\n version: { active: true },\n });\n } catch (err) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to activate version — \" +\n (err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n active = true;\n }\n\n console.log(\n chalk.gray(\"Active: \") +\n (active ? chalk.green(\"yes\") : chalk.gray(\"no\")),\n );\n console.log();\n });\n\nconst listVersionCommand = new Command(\"list\")\n .description(\"List all versions of the portal definition\")\n .action(async () => {\n const token = requireToken();\n const client = createClient(token);\n const definitionId = await requireDefinitionId();\n\n let result;\n try {\n result = await fluidOsApi.listVersions(client, definitionId);\n } catch (err) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to list versions — \" +\n (err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n\n const versions = result.version;\n\n if (!Array.isArray(versions) || versions.length === 0) {\n console.log();\n console.log(chalk.yellow(\"No versions found.\"));\n console.log(\n \"Run \" +\n chalk.cyan(\"`fluid portal version create`\") +\n \" to publish a version.\",\n );\n console.log();\n return;\n }\n\n console.log();\n\n // Print table header\n const COL_ID = \"Version ID\".padEnd(36);\n const COL_ACT = \"Active\".padEnd(6);\n const COL_PUB = \"Published\";\n console.log(chalk.gray(` ${COL_ID} ${COL_ACT} ${COL_PUB}`));\n\n for (const v of versions) {\n const id = String(v.id).padEnd(36);\n const active = v.active ? chalk.green(\"\\u2713\") + \" \" : \" \";\n const published = v.published_at\n ? new Date(v.published_at).toLocaleString()\n : \"\\u2014\";\n console.log(` ${id} ${active} ${published}`);\n }\n\n console.log();\n });\n\nconst activateVersionCommand = new Command(\"activate\")\n .description(\"Activate a specific version, making it the live version\")\n .argument(\"<version-id>\", \"The version ID to activate\")\n .option(\"-y, --yes\", \"Skip confirmation prompt\")\n .action(async (versionId: string, options: { yes?: boolean }) => {\n const numericVersionId = Number(versionId);\n if (!Number.isSafeInteger(numericVersionId) || numericVersionId < 1) {\n console.error(\n chalk.red(\"Error:\") + \" Version ID must be a positive integer.\",\n );\n process.exit(1);\n }\n\n const token = requireToken();\n const client = createClient(token);\n const definitionId = await requireDefinitionId();\n\n if (!options.yes) {\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: `Activate version ${versionId}? This will make it the live version.`,\n initial: false,\n });\n\n if (!confirm) {\n console.log(chalk.yellow(\"Aborted.\"));\n return;\n }\n }\n\n console.log();\n console.log(chalk.bold(\"Activating version...\"));\n\n try {\n await fluidOsApi.updateVersion(client, definitionId, numericVersionId, {\n version: { active: true },\n });\n } catch (err) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to activate version — \" +\n (err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.green(\"Version \" + versionId + \" is now active.\"));\n console.log();\n });\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Version command group\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const versionCommand: Command = new Command(\"version\")\n .description(\"Manage portal definition versions\")\n .addCommand(createVersionCommand)\n .addCommand(listVersionCommand)\n .addCommand(activateVersionCommand);\n\nexport function registerVersionCommand(ctx: PluginContext): void {\n ctx.program.addCommand(versionCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport type { WidgetPublishOptions } from \"../types.js\";\nimport {\n createAuthenticatedWidgetPackageClient,\n printDryRunSessionPayload,\n printWidgetPublishError,\n printWidgetPublishSummary,\n printRuntimeOnlyNotice,\n publishWidgetRuntimeArtifacts,\n} from \"./widget-package-publish.js\";\n\nconst DEFAULT_OUT_DIR = \".fluid/widget-dist\";\n\nconst publishSubcommand = new Command(\"publish\")\n .description(\"Publish droplet-owned widget runtime artifacts\")\n .requiredOption(\n \"--droplet <uuid>\",\n \"Droplet UUID that owns the widget package\",\n )\n .option(\n \"-o, --out-dir <dir>\",\n \"Widget artifact output directory\",\n DEFAULT_OUT_DIR,\n )\n .option(\"--dry-run\", \"Build and validate upload payload without publishing\")\n .action(async (options: WidgetPublishOptions) => {\n const outDir = options.outDir ?? DEFAULT_OUT_DIR;\n const dryRun = options.dryRun === true;\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Widget Publish\"));\n console.log();\n printRuntimeOnlyNotice();\n console.log(chalk.gray(\"Droplet: \") + chalk.white(options.droplet));\n console.log(chalk.gray(\"Output: \") + chalk.cyan(outDir));\n if (dryRun)\n console.log(chalk.yellow(\"Dry run: no upload will be created.\"));\n console.log();\n\n const spinner = ora(\"Building droplet-owned widget package...\").start();\n\n try {\n const result = await publishWidgetRuntimeArtifacts({\n projectDir: process.cwd(),\n outDir,\n buildOwner: \"droplet\",\n uploadOwner: { kind: \"droplet\", uuid: options.droplet },\n dryRun,\n ...(dryRun ? {} : { client: createAuthenticatedWidgetPackageClient() }),\n });\n\n spinner.succeed(\"Built widget runtime artifacts\");\n\n if (dryRun) {\n console.log(\n chalk.yellow(\"Dry run complete — upload session was not requested.\"),\n );\n console.log();\n printDryRunSessionPayload(result.publish.sessionPayload);\n } else {\n console.log(chalk.green(\"Published widget package version.\"));\n }\n\n printWidgetPublishSummary({\n title: dryRun ? \"Widget publish dry run\" : \"Widget publish\",\n ownerLabel: `droplet ${options.droplet}`,\n outDir,\n packageId: result.build.packageId,\n version: result.build.version,\n artifactCount: result.request.artifacts.length,\n });\n } catch (err) {\n spinner.fail(\"Widget publish failed\");\n printWidgetPublishError(err);\n process.exit(1);\n }\n });\n\nexport const topLevelWidgetCommand: Command = new Command(\"widget\")\n .description(\"Publish Fluid widget packages\")\n .addCommand(publishSubcommand);\n\nexport function registerTopLevelWidgetCommand(ctx: PluginContext): void {\n ctx.program.addCommand(topLevelWidgetCommand);\n}\n","/**\n * @fluid-app/fluid-cli-portal\n *\n * Fluid CLI plugin for building portal applications.\n * Auto-discovered by @fluid-app/fluid-cli via the fluid-cli-* naming convention.\n */\n\nimport { Command } from \"commander\";\nimport type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { createCommand } from \"./commands/create.js\";\nimport { devCommand } from \"./commands/dev.js\";\nimport { buildCommand } from \"./commands/build.js\";\nimport { pullCommand } from \"./commands/pull.js\";\nimport { pushCommand } from \"./commands/push.js\";\nimport { cloneCommand } from \"./commands/clone.js\";\nimport { widgetCommand } from \"./commands/widget-create.js\";\nimport { deployCommand } from \"./commands/deploy.js\";\nimport { doctorCommand } from \"./commands/doctor.js\";\nimport { versionCommand } from \"./commands/version.js\";\n\nconst plugin: FluidPlugin = {\n name: \"fluid-cli-portal\",\n version: \"0.1.0\",\n async register(ctx: PluginContext) {\n const portal = new Command(\"portal\").description(\n \"Build and develop portal applications\",\n );\n\n portal.addCommand(createCommand);\n portal.addCommand(devCommand);\n portal.addCommand(buildCommand);\n portal.addCommand(pullCommand);\n portal.addCommand(pushCommand);\n portal.addCommand(cloneCommand);\n portal.addCommand(deployCommand);\n portal.addCommand(widgetCommand);\n portal.addCommand(doctorCommand);\n portal.addCommand(versionCommand);\n\n ctx.program.addCommand(portal);\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 TemplateVariables,\n SelectedPageTemplate,\n TemplateName,\n WidgetCreateOptions,\n PortalDeployOptions,\n WidgetPublishOptions,\n} from \"./types.js\";\n\nexport { TEMPLATES } 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\nexport {\n scaffoldFromGitHub,\n rewriteProjectName,\n writeFluidrcProfile,\n TEMPLATE_REPO_URL,\n CANONICAL_PROJECT_NAME,\n type ScaffoldFromGitHubOptions,\n} from \"./utils/scaffold-github.js\";\n\nexport {\n GitError,\n gitMissingError,\n isGitAvailable,\n ensureGitAvailable,\n gitClone,\n gitInit,\n isGitRepo,\n getOriginUrl,\n hasUncommittedChanges,\n gitFetch,\n gitAheadBehindVsFetchHead,\n gitMergeFastForward,\n gitAutoCommit,\n gitPush,\n} from \"./lib/git.js\";\n\nexport {\n createPortalGitCredential,\n portalGitCredentialPath,\n type PortalGitCredential,\n type GitCredentialOutcome,\n type CreatePortalGitCredentialOptions,\n} from \"./api/git-credential.js\";\n\nexport {\n runPortalGitSync,\n type PortalGitSyncResult,\n type PortalGitSyncOptions,\n} from \"./lib/git-sync.js\";\n\nexport { cloneCommand } from \"./commands/clone.js\";\n\n// Expose the standalone Command for create-portal-app\nexport {\n createCommand,\n createPortalProject,\n type CreatePortalProjectDependencies,\n} from \"./commands/create.js\";\nexport { pullCommand } from \"./commands/pull.js\";\nexport { pushCommand } from \"./commands/push.js\";\nexport { widgetCommand } from \"./commands/widget-create.js\";\nexport { topLevelWidgetCommand } from \"./commands/widget-publish.js\";\nexport { deployCommand } from \"./commands/deploy.js\";\n\n// The `fluid portal deploy --json` contract.\n//\n// Hosts that publish widgets (Mist) spawn that command and read one of these\n// off stdout. They must NOT import the publish library and run it in-process:\n// the publish path imports Vite, which drags chokidar and the native `fsevents`\n// binary into whatever bundles it — fatal for an Electron main bundle — and the\n// build has to resolve the target project's own toolchain anyway.\n//\n// These are types and small pure helpers. A consumer that only uses\n// `import type` links none of this package into its bundle.\nexport { publishWidgetRuntimeArtifacts } from \"./commands/widget-package-publish.js\";\nexport type { PublishWidgetRuntimeArtifactsOptions } from \"./commands/widget-package-publish.js\";\nexport {\n formatPackageKeyMismatch,\n preflightCompanyPackage,\n readCompanyWidgetPackage,\n resolvePublishVersion,\n type CompanyPackagePreflight,\n type CompanyWidgetPackage,\n type ConfiguredWidgetPackage,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nexport { versionCommand } from \"./commands/version.js\";\nexport { doctorCommand } from \"./commands/doctor.js\";\n\nexport {\n readMappings,\n writeMappings,\n deriveSlug,\n resolveSlugToId,\n resolveIdToSlug,\n updateMapping,\n removeMapping,\n type PortalMappings,\n type DefinitionMapping,\n type MappedResourceType,\n} from \"./utils/mappings.js\";\n\nexport {\n computeFileHash,\n readSnapshot,\n writeSnapshot,\n diffAgainstSnapshot,\n buildSnapshot,\n type Snapshot,\n type SnapshotDiff,\n type FileHash,\n type FileHashMap,\n} from \"./utils/snapshot.js\";\n\nexport {\n transformScreen,\n deriveScreenSlug,\n transformTheme,\n transformNavigation,\n transformNavigationItems,\n transformProfile,\n buildIdToSlugMap,\n buildNavigationIdToSlugMap,\n buildThemeIdToSlugMap,\n type LocalScreen,\n type LocalTheme,\n type LocalNavigation,\n type LocalNavigationItem,\n type LocalProfile,\n} from \"./utils/transform.js\";\n\nexport {\n categorizeChanges,\n validateCrossReferences,\n validatePortalSchemas,\n slugFromPath,\n type CategorizedChanges,\n type ValidationError,\n} from \"./utils/push-validation.js\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["_currentDir","PORTAL_DIR","readPortalFile","PORTAL_SYNC_DIR","createClient","createClient","DEFAULT_OUT_DIR"],"sources":["../src/types.ts","../src/utils/prompts.ts","../src/utils/file-system.ts","../src/utils/package-manager.ts","../src/lib/git.ts","../src/utils/scaffold-github.ts","../src/commands/create.ts","../src/commands/dev.ts","../src/commands/build.ts","../../../portal/core/src/registries/container-registry.ts","../src/utils/push-validation.ts","../src/api/git-credential.ts","../src/lib/git-sync.ts","../src/api/me.ts","../../../portal/core/src/remote-widget-capability-grants.ts","../src/utils/widget-capability-grants.ts","../src/commands/push.ts","../src/commands/clone.ts","../src/commands/widget-package-publish.ts","../src/utils/widget-helpers.ts","../src/commands/widget-create.ts","../src/commands/deploy.ts","../src/commands/doctor.ts","../src/commands/version.ts","../src/commands/widget-publish.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} as const;\n\n/**\n * Union type of valid template names\n */\nexport type TemplateName = (typeof TEMPLATES)[keyof typeof TEMPLATES];\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 /** Whether to install dependencies after scaffolding */\n readonly installDeps: boolean;\n /** Selected optional page templates to include */\n readonly selectedPages: readonly SelectedPageTemplate[];\n /** CLI profile name for .fluidrc (empty string if none selected) */\n readonly profileName: string;\n}\n\n/**\n * Options for the create command (from CLI arguments)\n */\nexport interface CreateOptions {\n /** Create only the scaffold and skip dependency installation */\n readonly skipInstall?: boolean;\n /** Directory to create the project in (defaults to cwd) */\n readonly outputDir?: string;\n /** Use local monorepo packages via link: paths instead of npm versions */\n readonly local?: boolean;\n /** Explicitly use the GitHub starter instead of the bundled template. */\n readonly remoteTemplate?: 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 widget create command\n */\nexport interface WidgetCreateOptions {\n /** Category for palette grouping */\n readonly category?: string;\n}\n\n/**\n * Options for the portal deploy command\n */\nexport interface PortalDeployOptions {\n readonly environment?: string;\n readonly outDir?: string;\n readonly dryRun?: boolean;\n}\n\n/**\n * Options for the top-level widget publish command\n */\nexport interface WidgetPublishOptions {\n readonly droplet: string;\n readonly outDir?: string;\n readonly dryRun?: 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 /** portal-core link used only for --local scaffolds */\n readonly localCoreVersion?: string;\n /** CLI package version (versioned independently from the SDK) */\n readonly cliVersion: string;\n /** Portal CLI plugin version (versioned independently from CLI core) */\n readonly portalCliVersion: 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 /** CLI profile name for .fluidrc */\n readonly profileName: string;\n}\n","import { getActiveProfile, listProfileNames } from \"@fluid-app/fluid-cli\";\nimport prompts from \"prompts\";\nimport {\n type ProjectConfig,\n type CreateOptions,\n type SelectedPageTemplate,\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 // 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 // CLI profile for .fluidrc\n const existingProfiles = listProfileNames();\n if (existingProfiles.length > 0) {\n const active = getActiveProfile();\n questions.push({\n type: \"select\",\n name: \"profileName\",\n message: \"CLI profile for this project (.fluidrc)\",\n choices: existingProfiles.map((name) => ({\n title: name === active?.name ? `${name} (active)` : name,\n value: name,\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 // Non-interactive mode: if stdin is not a TTY and there are remaining\n // prompts, return safe defaults instead of hanging on interactive input.\n if (!process.stdin.isTTY && questions.length > 0) {\n return {\n name: projectName,\n installDeps: false,\n selectedPages: [],\n profileName: getActiveProfile()?.name ?? \"\",\n } satisfies ProjectConfig;\n }\n\n // Fast-path: all options provided via CLI flags, no prompts needed\n if (questions.length === 0) {\n return {\n name: projectName,\n installDeps: options.skipInstall ? false : true,\n selectedPages: [],\n profileName: getActiveProfile()?.name ?? \"\",\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 // Parse selected pages\n const selectedPages: readonly SelectedPageTemplate[] =\n response.selectedPages ?? [];\n\n return {\n name: projectName,\n installDeps: options.skipInstall ? false : (response.installDeps ?? true),\n selectedPages,\n profileName:\n (response.profileName as string | undefined) ??\n getActiveProfile()?.name ??\n \"\",\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, resolve } 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 _currentFile = fileURLToPath(import.meta.url);\nconst _currentDir = dirname(_currentFile);\n\ndeclare const __FLUID_PORTAL_SDK_VERSION__: string | undefined;\ndeclare const __FLUID_CLI_CORE_VERSION__: string | undefined;\ndeclare const __FLUID_CLI_PORTAL_VERSION__: string | undefined;\n\nconst EMBEDDED_PORTAL_SDK_VERSION =\n typeof __FLUID_PORTAL_SDK_VERSION__ === \"string\"\n ? __FLUID_PORTAL_SDK_VERSION__\n : undefined;\nconst EMBEDDED_CLI_CORE_VERSION =\n typeof __FLUID_CLI_CORE_VERSION__ === \"string\"\n ? __FLUID_CLI_CORE_VERSION__\n : undefined;\nconst EMBEDDED_CLI_PORTAL_VERSION =\n typeof __FLUID_CLI_PORTAL_VERSION__ === \"string\"\n ? __FLUID_CLI_PORTAL_VERSION__\n : undefined;\n\n/**\n * Find the package root by walking up from the current directory 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 = _currentDir;\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 */\ninterface TemplateSkillFile {\n readonly relativePath: string;\n readonly content: string;\n}\n\nasync function getSharedTemplateSkillFiles(): Promise<TemplateSkillFile[]> {\n const packageRoot = findFluidCliPackageRoot();\n const skillsRoot = join(packageRoot, \"template-skills\");\n const files = await getFiles(skillsRoot);\n\n return Promise.all(\n files.map(async (file) => ({\n relativePath: join(\"skills\", file),\n content: await readFile(join(skillsRoot, file), \"utf-8\"),\n })),\n );\n}\n\nfunction findFluidCliPackageRoot(): string {\n const workspacePackageRoot = join(findPackageRoot(), \"..\", \"core\");\n if (existsSync(join(workspacePackageRoot, \"template-skills\"))) {\n return workspacePackageRoot;\n }\n\n let dir = dirname(fileURLToPath(import.meta.resolve(\"@fluid-app/fluid-cli\")));\n while (!existsSync(join(dir, \"package.json\"))) {\n const parent = dirname(dir);\n if (parent === dir)\n throw new Error(\"Could not find Fluid CLI package root\");\n dir = parent;\n }\n return dir;\n}\n\nasync function writeOutputFiles(\n targetPath: string,\n outputFiles: readonly string[],\n content: string,\n): Promise<void> {\n for (const outputFile of outputFiles) {\n const destPath = join(targetPath, outputFile);\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n await writeFile(destPath, content, \"utf-8\");\n }\n}\n\nfunction getOutputFilenames(filename: string): string[] {\n const outputFilename = getOutputFilename(filename);\n const normalized = outputFilename.replace(/\\\\/g, \"/\");\n\n if (normalized === \"AGENTS.md\") return [\"AGENTS.md\", \"CLAUDE.md\"];\n\n if (normalized.startsWith(\"skills/\")) {\n return [join(\".agents\", outputFilename), join(\".claude\", outputFilename)];\n }\n\n return [outputFilename];\n}\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 outputFiles = getOutputFilenames(file);\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\n for (const outputFile of outputFiles) {\n const destPath = join(targetPath, outputFile);\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n await writeFile(destPath, processed, \"utf-8\");\n }\n }\n\n for (const skillFile of await getSharedTemplateSkillFiles()) {\n await writeOutputFiles(\n targetPath,\n getOutputFilenames(skillFile.relativePath),\n skillFile.content,\n );\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 exact SDK version embedded by the build, or from the workspace\n * package while running source directly.\n */\nexport async function getSdkVersion(): Promise<string> {\n return getTemplatePackageVersion(\n [\"portal\", \"sdk\"],\n EMBEDDED_PORTAL_SDK_VERSION,\n );\n}\n\n/**\n * Builds an absolute pnpm link: specifier for local scaffolds.\n *\n * link: keeps the dependency pointed at the workspace package source instead of\n * packing only package.json \"files\" entries like file: does. That lets Vite's\n * development export resolve the SDK source and its workspace dependencies. An\n * absolute path also remains valid when the operating system canonicalizes the\n * generated app path, such as macOS resolving /tmp to /private/tmp.\n */\nexport function getLocalPackageLinkVersion(packagePath: string): string {\n const linkPath = resolve(packagePath).replace(/\\\\/g, \"/\");\n return `link:${linkPath}`;\n}\n\n/**\n * Reads the exact CLI core version embedded by the build, or from the\n * workspace package while running source directly.\n *\n * This is separate from getSdkVersion because the CLI and portal SDK\n * are versioned independently.\n */\nexport async function getCliVersion(): Promise<string> {\n return getTemplatePackageVersion([\"cli\", \"core\"], EMBEDDED_CLI_CORE_VERSION);\n}\n\n/** Reads the independently versioned portal CLI plugin version. */\nexport async function getPortalCliVersion(): Promise<string> {\n return getTemplatePackageVersion(\n [\"cli\", \"portal\"],\n EMBEDDED_CLI_PORTAL_VERSION,\n );\n}\n\nconst NPM_REGISTRY_URL = \"https://registry.npmjs.org\";\nconst NPM_REGISTRY_TIMEOUT_MS = 5_000;\n\n/**\n * Clamp a scaffold dependency pin to a version that `pnpm install` can\n * actually resolve from the registry.\n *\n * The versions above are whatever this CLI build carries — embedded at build\n * time, or read from the workspace when running from source. Both run ahead\n * of npm whenever the monorepo holds a release bump that has not been\n * published yet (routine for anyone running the CLI from a checkout, e.g.\n * Mist in development), and a scaffold pinned to that version fails to\n * install. Only a definitive registry 404 for the exact version downgrades\n * the pin to the registry's `latest`; a network failure or any other registry\n * response keeps the original version, so offline scaffolds behave exactly as\n * before.\n *\n * Scaffold pins are the ONLY sound callsite: consumers that report which\n * version is running (e.g. widget-package-publish) must not route through\n * this — it would silently misreport an unpublished build as the older\n * published one.\n */\nexport async function resolveInstallableTemplateVersion(\n packageName: string,\n version: string,\n): Promise<string> {\n try {\n const exact = await fetch(\n `${NPM_REGISTRY_URL}/${packageName}/${encodeURIComponent(version)}`,\n { signal: AbortSignal.timeout(NPM_REGISTRY_TIMEOUT_MS) },\n );\n if (exact.status !== 404) return version;\n\n const latest = await fetch(`${NPM_REGISTRY_URL}/${packageName}/latest`, {\n signal: AbortSignal.timeout(NPM_REGISTRY_TIMEOUT_MS),\n });\n if (!latest.ok) return version;\n const pkg = (await latest.json()) as { version?: unknown };\n if (typeof pkg.version !== \"string\") return version;\n\n console.warn(\n `${packageName}@${version} is not published yet; the scaffold pins the latest published version ${pkg.version} instead.`,\n );\n return pkg.version;\n } catch {\n return version;\n }\n}\n\nasync function getTemplatePackageVersion(\n packagePathSegments: readonly string[],\n embeddedVersion: string | undefined,\n): Promise<string> {\n if (embeddedVersion) return embeddedVersion;\n\n try {\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const packageJsonPath = join(\n packagesRoot,\n ...packagePathSegments,\n \"package.json\",\n );\n\n const content = await readFile(packageJsonPath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: unknown };\n if (typeof pkg.version !== \"string\") {\n throw new Error(`Missing version in ${packageJsonPath}`);\n }\n return pkg.version;\n } catch (error) {\n throw new Error(\n `Could not resolve the exact template package version for ${packagePathSegments.join(\"/\")}`,\n { cause: error },\n );\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 outputFiles = getOutputFilenames(file);\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\n for (const outputFile of outputFiles) {\n const destPath = join(targetPath, outputFile);\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n await writeFile(destPath, processed, \"utf-8\");\n }\n }\n\n for (const skillFile of await getSharedTemplateSkillFiles()) {\n await writeOutputFiles(\n targetPath,\n getOutputFilenames(skillFile.relativePath),\n skillFile.content,\n );\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","/**\n * Git wrapper for the portal CLI — shells out to the system `git` via\n * execa. We deliberately do NOT bundle git or use a pure-JS impl; users\n * are expected to have it installed (the same stance as `fluid mist`).\n *\n * Security: a token-bearing `remote_url` (minted by the portal\n * `git_credential` endpoint) must never be logged, persisted, *or*\n * exposed in the process table. The token is stripped from the URL and\n * handed to git through a GIT_ASKPASS script via env vars\n * (FLUID_PORTAL_GIT_USER / FLUID_PORTAL_GIT_PASS) — so it never appears\n * in argv (visible to `ps aux` / `/proc/<pid>/cmdline`) and is never\n * written to `.git/config`.\n *\n * This mirrors `packages/cli/mist/src/lib/git.ts`. Kept portal-local for\n * now; if the widget/mist tracks extract a shared git lib we should\n * consume it instead (see overnight/track2-portal.md).\n */\n\nimport {\n parseNameStatusZ,\n type ChangeEntry,\n type CommitMessage,\n} from \"@fluid-app/fluid-cli\";\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { randomUUID } from \"node:crypto\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { execa, type ExecaError } from \"execa\";\n\nexport class GitError extends Error {\n readonly exitCode: number;\n\n constructor(message: string, exitCode = 1) {\n super(message);\n this.name = \"GitError\";\n this.exitCode = exitCode;\n }\n}\n\n/**\n * execa throws ENOENT when the git binary itself is missing — the single\n * most common failure on non-developer machines. Translate it into an\n * actionable message. Returns null for every other failure so call sites\n * keep their specific messages.\n */\nexport function gitMissingError(err: unknown): GitError | null {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code !== \"ENOENT\") return null;\n return new GitError(\n \"Git isn't installed (or isn't on your PATH), so this command can't run.\\n\" +\n \" Install it, then try again:\\n\" +\n \" macOS: xcode-select --install (or `brew install git`)\\n\" +\n \" Windows: https://git-scm.com/download/win\\n\" +\n \" Linux: use your package manager, e.g. `apt install git`\",\n 127,\n );\n}\n\n/**\n * Is `git` available on this machine? Cheap, never throws — used to\n * decide whether to take the GitHub-template path or fall back to the\n * bundled scaffold.\n */\nexport async function isGitAvailable(): Promise<boolean> {\n try {\n await execa(\"git\", [\"--version\"]);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Confirm git is installed, throwing the friendly message otherwise. */\nexport async function ensureGitAvailable(): Promise<void> {\n if (!(await isGitAvailable())) {\n throw new GitError(\n \"Git isn't installed (or isn't on your PATH), so this command can't run.\\n\" +\n \" Install it from https://git-scm.com/downloads (or `brew install git`), then try again.\",\n );\n }\n}\n\n/**\n * Split a token-bearing remote URL into the userless URL + creds. Keeps\n * the token off git's argv: we pass `cleanUrl` to git and feed\n * `username` / `password` back through GIT_ASKPASS env vars.\n *\n * If the URL has no embedded credentials, returns empty strings — fine\n * for public repos where git won't invoke askpass at all.\n */\nfunction splitRemoteCredentials(remoteUrl: string): {\n cleanUrl: string;\n username: string;\n password: string;\n} {\n const u = new URL(remoteUrl);\n const username = decodeURIComponent(u.username);\n const password = decodeURIComponent(u.password);\n u.username = \"\";\n u.password = \"\";\n return { cleanUrl: u.toString(), username, password };\n}\n\n/** Strip credential userinfo from a minted remote URL for safe comparison. */\nexport function cleanGitRemoteUrl(remoteUrl: string): string {\n return splitRemoteCredentials(remoteUrl).cleanUrl;\n}\n\n/**\n * Write a stub GIT_ASKPASS shell script to a per-process tmpdir and\n * return its path. The script contains no secret — it echoes back\n * whichever env var (FLUID_PORTAL_GIT_USER / FLUID_PORTAL_GIT_PASS)\n * matches git's prompt. The credentials only live in the spawned git\n * process's env for the duration of the call.\n *\n * One POSIX script for all platforms: Git for Windows executes\n * GIT_ASKPASS through its bundled sh, so a plain sh script works\n * everywhere. Idempotent: subsequent calls reuse the same path.\n */\nlet askpassPath: string | null = null;\nfunction ensureAskpassScript(): string {\n if (askpassPath && existsSync(askpassPath)) return askpassPath;\n const dir = path.join(os.tmpdir(), `fluid-portal-askpass-${process.pid}`);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });\n const p = path.join(dir, \"askpass.sh\");\n writeFileSync(\n p,\n `#!/bin/sh\\ncase \"$1\" in\\n Username*) printf '%s' \"$FLUID_PORTAL_GIT_USER\" ;;\\n *) printf '%s' \"$FLUID_PORTAL_GIT_PASS\" ;;\\nesac\\n`,\n { mode: 0o700 },\n );\n chmodSync(p, 0o700);\n askpassPath = p;\n return askpassPath;\n}\n\n/**\n * Build the spawn env + cleaned URL for a credentialed git invocation.\n * - `cleanUrl`: URL with userinfo stripped — safe to pass as argv.\n * - `env`: parent env plus GIT_ASKPASS + FLUID_PORTAL_GIT_USER/PASS.\n * GIT_TERMINAL_PROMPT=0 prevents the TTY-prompt fallback if the\n * credentials are empty (auth should fail fast, not hang).\n */\nfunction gitAuthSpawn(remoteUrl: string): {\n cleanUrl: string;\n env: NodeJS.ProcessEnv;\n} {\n const { username, password } = splitRemoteCredentials(remoteUrl);\n return {\n cleanUrl: cleanGitRemoteUrl(remoteUrl),\n env: {\n ...process.env,\n GIT_ASKPASS: ensureAskpassScript(),\n GIT_TERMINAL_PROMPT: \"0\",\n FLUID_PORTAL_GIT_USER: username,\n FLUID_PORTAL_GIT_PASS: password,\n },\n };\n}\n\n/** Map an execa failure to a GitError, preferring the friendly\n * git-missing message when the binary itself is absent. */\nfunction toGitError(err: unknown, verb: string): GitError {\n const missing = gitMissingError(err);\n if (missing) return missing;\n const e = err as ExecaError;\n return new GitError(\n `git ${verb} failed${e.exitCode != null ? ` (exit ${e.exitCode})` : \"\"}`,\n e.exitCode ?? 1,\n );\n}\n\n/**\n * `git clone <cleanUrl> <targetDir>`. When `remoteUrl` carries a token,\n * gitAuthSpawn strips it from the URL and re-injects it via GIT_ASKPASS\n * so it never appears in argv. `credential.helper=` stops git from\n * caching what askpass returned to any system credential store.\n *\n * `depth` shallow-clones (used by the create flow, which discards `.git`\n * afterwards anyway).\n */\nexport async function gitClone(\n remoteUrl: string,\n targetDir: string,\n opts: { depth?: number } = {},\n): Promise<void> {\n const { cleanUrl, env } = gitAuthSpawn(remoteUrl);\n const depthArgs = opts.depth ? [\"--depth\", String(opts.depth)] : [];\n try {\n await execa(\n \"git\",\n [\n \"-c\",\n \"credential.helper=\",\n \"clone\",\n \"--quiet\",\n ...depthArgs,\n cleanUrl,\n targetDir,\n ],\n { env, stdio: [\"ignore\", \"inherit\", \"inherit\"] },\n );\n } catch (err) {\n throw toGitError(err, \"clone\");\n }\n}\n\n/** Initialize a fresh repo (used after scaffolding so the project is\n * git-ready). Best-effort: never throws — a scaffold without a repo is\n * still usable, and a missing git binary is handled by the caller. */\nexport async function gitInit(cwd: string): Promise<boolean> {\n try {\n await execa(\"git\", [\"init\", \"--quiet\"], { cwd });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Is `cwd` inside a git work tree? Cheap, never throws. */\nexport async function isGitRepo(cwd: string): Promise<boolean> {\n try {\n const { stdout } = await execa(\n \"git\",\n [\"rev-parse\", \"--is-inside-work-tree\"],\n { cwd },\n );\n return stdout.trim() === \"true\";\n } catch {\n return false;\n }\n}\n\n/** Read `git remote get-url origin` (or null if no origin / not a repo). */\nexport async function getOriginUrl(cwd: string): Promise<string | null> {\n try {\n const { stdout } = await execa(\"git\", [\"remote\", \"get-url\", \"origin\"], {\n cwd,\n });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Are there uncommitted changes (working tree or index)? `git status\n * --porcelain` is empty when clean. Returns false (never throws) when\n * `cwd` isn't a repo.\n */\nexport async function hasUncommittedChanges(cwd: string): Promise<boolean> {\n try {\n const { stdout } = await execa(\"git\", [\"status\", \"--porcelain\"], { cwd });\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Fetch `branch` from a token-bearing remote URL, writing FETCH_HEAD so\n * `gitAheadBehindVsFetchHead` is meaningful. Token comes through\n * GIT_ASKPASS, never argv or `.git/config`.\n */\nexport async function gitFetch(\n cwd: string,\n remoteUrl: string,\n branch = \"main\",\n): Promise<void> {\n const { cleanUrl, env } = gitAuthSpawn(remoteUrl);\n try {\n await execa(\n \"git\",\n [\"-c\", \"credential.helper=\", \"fetch\", \"--quiet\", cleanUrl, branch],\n { cwd, env },\n );\n } catch (err) {\n throw toGitError(err, \"fetch\");\n }\n}\n\n/**\n * Count commits in local HEAD vs FETCH_HEAD (after `gitFetch`). Returns\n * `{ahead, behind}`. `0/0` means \"in sync\" (or no FETCH_HEAD yet).\n */\nexport async function gitAheadBehindVsFetchHead(\n cwd: string,\n): Promise<{ ahead: number; behind: number }> {\n try {\n const { stdout } = await execa(\n \"git\",\n [\"rev-list\", \"--left-right\", \"--count\", \"HEAD...FETCH_HEAD\"],\n { cwd },\n );\n const [a, b] = stdout.trim().split(/\\s+/);\n const ahead = Number.parseInt(a ?? \"0\", 10);\n const behind = Number.parseInt(b ?? \"0\", 10);\n return {\n ahead: Number.isFinite(ahead) ? ahead : 0,\n behind: Number.isFinite(behind) ? behind : 0,\n };\n } catch {\n return { ahead: 0, behind: 0 };\n }\n}\n\n/** Does `cwd` have any commits yet? (false right after `git init`.) */\nexport async function hasCommits(cwd: string): Promise<boolean> {\n try {\n await execa(\"git\", [\"rev-parse\", \"--verify\", \"HEAD\"], { cwd });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Does `branch` exist on the remote? Throws on transport/auth failure so\n * a network blip is never mistaken for an empty remote (which would skip\n * drift detection). Token via GIT_ASKPASS, never argv or .git/config.\n */\nexport async function gitRemoteBranchExists(\n cwd: string,\n remoteUrl: string,\n branch = \"main\",\n): Promise<boolean> {\n const { cleanUrl, env } = gitAuthSpawn(remoteUrl);\n try {\n const { stdout } = await execa(\n \"git\",\n [\n \"-c\",\n \"credential.helper=\",\n \"ls-remote\",\n \"--heads\",\n cleanUrl,\n `refs/heads/${branch}`,\n ],\n { cwd, env },\n );\n return stdout.trim().length > 0;\n } catch (err) {\n throw toGitError(err, \"ls-remote\");\n }\n}\n\n/**\n * Adopt `FETCH_HEAD` as the current (unborn) branch's base: point the\n * branch ref at it and load its tree into the index, leaving the working\n * tree untouched. The next auto-commit (run with `ignoreRemovals`) then\n * records the local project tree on top of the remote history — local\n * additions/edits win, and remote-only files survive because a\n * content-only local dir (e.g. populated by `fluid portal pull`) is no\n * statement about what the full repo tracks — so the push fast-forwards\n * instead of being rejected for unrelated history. Only call when\n * `hasCommits` is false; with local commits it would silently rewrite\n * the branch ref.\n * Mirrors the widget CLI's first-push adoption (the backend provisions\n * repos with an auto-init/template commit, so a fresh root commit is\n * always unrelated history).\n *\n * Returns the number of remote commits adopted.\n */\nexport async function gitAdoptFetchHead(cwd: string): Promise<number> {\n try {\n await execa(\"git\", [\"reset\", \"--mixed\", \"FETCH_HEAD\"], { cwd });\n const { stdout } = await execa(\n \"git\",\n [\"rev-list\", \"--count\", \"FETCH_HEAD\"],\n { cwd },\n );\n const count = Number.parseInt(stdout.trim(), 10);\n return Number.isFinite(count) ? count : 0;\n } catch (err) {\n throw toGitError(err, \"adopt remote history\");\n }\n}\n\n/**\n * Normalize a git remote URL to `host/owner/repo` for identity\n * comparison — strips credentials, protocol, `.git`, and trailing\n * slashes so a token-bearing minted URL compares equal to the stored\n * origin. Returns null when the URL doesn't parse.\n */\nexport function normalizedRepoIdentity(remoteUrl: string): string | null {\n // scp-style SSH remotes (`git@github.com:owner/repo.git`) aren't URLs —\n // normalize them by hand so an SSH origin still verifies against the\n // https minted URL.\n const scp = remoteUrl.match(/^(?:[^@/\\s]+@)?([^:/\\s]+):([^/].*)$/);\n if (scp?.[1] && scp[2] && !remoteUrl.includes(\"://\")) {\n const path = scp[2].replace(/\\.git$/, \"\").replace(/\\/+$/, \"\");\n return `${scp[1]}/${path}`.toLowerCase();\n }\n // Plain filesystem remotes (local bare repos — tests, offline mirrors):\n // identity is the normalized path itself.\n if (remoteUrl.startsWith(\"/\") || remoteUrl.startsWith(\".\")) {\n return remoteUrl\n .replace(/\\.git$/, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n }\n try {\n const u = new URL(remoteUrl);\n const path = u.pathname\n .replace(/\\.git$/, \"\")\n .replace(/\\/+$/, \"\")\n .replace(/^\\/+/, \"\");\n return `${u.host}/${path}`.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * Do HEAD and FETCH_HEAD share any common ancestor? False for unrelated\n * histories — the guard that keeps a whole-tree auto-commit of the WRONG\n * repository from being pushed to a portal's remote. Never throws.\n */\nexport async function hasCommonAncestorWithFetchHead(\n cwd: string,\n): Promise<boolean> {\n try {\n await execa(\"git\", [\"merge-base\", \"HEAD\", \"FETCH_HEAD\"], { cwd });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Fast-forward HEAD to FETCH_HEAD. Fails (non-zero exit) if not\n * fast-forwardable — callers check the ahead-count first. */\nexport async function gitMergeFastForward(cwd: string): Promise<void> {\n try {\n await execa(\"git\", [\"merge\", \"--ff-only\", \"FETCH_HEAD\"], { cwd });\n } catch (err) {\n throw toGitError(err, \"fast-forward merge\");\n }\n}\n\n/**\n * Local-only state that must never reach a portal's provisioned repo,\n * regardless of which tool created the checkout.\n *\n * `.mist-desktop/` is Mist Desktop's per-machine working state for the\n * project directory it opened — chat transcripts, user attachments,\n * screenshots, source baselines, and a media cache. When Desktop drives\n * `fluid portal push`, that directory sits in the project root, so a\n * whole-tree `git add -A` publishes every private conversation to the\n * portal's GitHub repo. `.mist` is the sibling clone marker (a slug\n * pointer); committing it means a later revert deletes it and breaks\n * slug resolution for every subsequent command.\n *\n * Neither is portal content: the portal's own material lives under\n * `portal/` and is pushed separately by the content push.\n */\nconst LOCAL_ONLY_GIT_PATHS = [\".mist\", \".mist-desktop/\"] as const;\n\n/**\n * Append {@link LOCAL_ONLY_GIT_PATHS} to `.git/info/exclude` when absent.\n *\n * Why the local exclude and not `.gitignore`: a portal's `.gitignore`\n * ships from the starter template (bundled or the GitHub\n * `portal-template` repo), so a template edit alone would never reach\n * the clones that already exist on people's machines. `.git/info/exclude`\n * is per-checkout, so writing it here — at sync time, on every push —\n * heals a pre-existing clone on its very next push with no template\n * release required. The template is updated too, for fresh scaffolds.\n *\n * Idempotent and best-effort: never throws. A repo with an unusual `.git`\n * layout (missing `info/`, `.git` as a worktree pointer file) just skips\n * the exclude — {@link gitAutoCommit} also cleans these paths out of the\n * index after staging, so the exclude is a convenience, not the only\n * defense.\n *\n * Mirrors `packages/cli/mist/src/lib/git.ts::excludeMistMarkerFromGit`.\n */\nexport function excludeLocalStateFromGit(cwd: string): void {\n try {\n const gitDir = path.join(cwd, \".git\");\n if (!existsSync(gitDir)) return;\n const infoDir = path.join(gitDir, \"info\");\n if (!existsSync(infoDir)) mkdirSync(infoDir, { recursive: true });\n const excludePath = path.join(infoDir, \"exclude\");\n const existing = existsSync(excludePath)\n ? readFileSync(excludePath, \"utf8\")\n : \"\";\n const existingLines = new Set(\n existing\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean),\n );\n const missing = LOCAL_ONLY_GIT_PATHS.filter(\n (entry) => !existingLines.has(entry),\n );\n if (missing.length === 0) return;\n const needsLeadingNewline = existing.length > 0 && !existing.endsWith(\"\\n\");\n writeFileSync(\n excludePath,\n `${existing}${needsLeadingNewline ? \"\\n\" : \"\"}${missing.join(\"\\n\")}\\n`,\n \"utf8\",\n );\n } catch {\n // Best-effort — see doc comment. gitAutoCommit's explicit index\n // cleanup still keeps local state out if this fails silently.\n }\n}\n\n/**\n * Absolute path of the repository containing `cwd`, or `cwd` itself when\n * resolution fails (not a repo / git missing — the caller's own git\n * command surfaces the real error).\n *\n * Every local-state defense must anchor here rather than to `cwd`: a push\n * run from a project subdirectory would otherwise look for\n * `<subdir>/.git`, silently skip the exclude write, and then let\n * `git add -A` — which always stages the WHOLE repository — sweep the\n * root `.mist-desktop/` into the commit anyway.\n */\nasync function resolveRepoRoot(cwd: string): Promise<string> {\n try {\n const { stdout } = await execa(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n });\n const root = stdout.trim();\n if (root.length > 0) return root;\n } catch {\n // Fall through to cwd.\n }\n return cwd;\n}\n\n/**\n * Repository-root-anchored {@link excludeLocalStateFromGit}, for callers\n * that only have a possibly-nested `cwd`.\n *\n * Run this BEFORE any working-tree cleanliness check, not just before\n * staging: while `.mist-desktop/` is neither tracked nor excluded, it\n * shows up as untracked in `git status --porcelain`, so a Desktop-driven\n * push reads as \"you have uncommitted changes\" and the sync skips with\n * advice to `git pull` that the user can't act on. Excluding it first\n * makes the cleanliness check reflect actual portal changes.\n */\nexport async function ensureLocalStateExcluded(cwd: string): Promise<void> {\n excludeLocalStateFromGit(await resolveRepoRoot(cwd));\n}\n\n/** Paths currently tracked under `.mist-desktop/`, or [] when none. */\nasync function trackedLocalStatePaths(repoRoot: string): Promise<string[]> {\n try {\n const { stdout } = await execa(\"git\", [\"ls-files\", \"--\", \".mist-desktop\"], {\n cwd: repoRoot,\n });\n return stdout.split(\"\\n\").filter((l) => l.trim().length > 0);\n } catch {\n return [];\n }\n}\n\n/**\n * Untrack private Mist Desktop state and, if anything was tracked,\n * commit ONLY that removal so the next push deletes it from the remote.\n * Returns true when a purge commit was made.\n *\n * Call this on every sync BEFORE deciding whether there is anything to\n * auto-commit — a clean checkout that already tracks `.mist-desktop/`\n * still needs the removal, and ignore rules alone cannot untrack an\n * existing path.\n *\n * The commit is built through a THROWAWAY index (`GIT_INDEX_FILE`), not\n * the user's: `read-tree HEAD` → `rm --cached` → `write-tree` →\n * `commit-tree` → `update-ref`. A plain `git commit` would capture the\n * user's entire index, so anything they had pre-staged would be swept\n * into the purge commit and pushed — defeating `--no-commit` and\n * mislabelling their work. A pathspec commit (`git commit -- .mist-desktop`)\n * is also wrong: with `--only` git takes the *working tree* content of\n * those paths, and the chats are still on disk, so it would re-add\n * exactly what we are removing.\n *\n * The user's real index is then updated with the same `rm --cached`,\n * touching only this path — so any other staged work stays staged, and\n * the working tree is never modified.\n */\nexport async function purgeLocalStateFromIndex(\n cwd: string,\n message: string,\n): Promise<boolean> {\n const repoRoot = await resolveRepoRoot(cwd);\n if ((await trackedLocalStatePaths(repoRoot)).length === 0) return false;\n\n // A tracked path implies a commit exists, but resolve defensively:\n // commit-tree needs a parent.\n let head: string;\n try {\n const { stdout } = await execa(\"git\", [\"rev-parse\", \"HEAD\"], {\n cwd: repoRoot,\n });\n head = stdout.trim();\n if (!head) return false;\n } catch {\n return false;\n }\n\n const tmpIndex = path.join(\n os.tmpdir(),\n `fluid-portal-purge-${process.pid}-${randomUUID()}.index`,\n );\n const indexEnv = { ...process.env, GIT_INDEX_FILE: tmpIndex };\n let commitSha: string;\n try {\n await execa(\"git\", [\"read-tree\", head], { cwd: repoRoot, env: indexEnv });\n await execa(\n \"git\",\n [\"rm\", \"-q\", \"-r\", \"--cached\", \"--ignore-unmatch\", \"--\", \".mist-desktop\"],\n { cwd: repoRoot, env: indexEnv },\n );\n const { stdout: tree } = await execa(\"git\", [\"write-tree\"], {\n cwd: repoRoot,\n env: indexEnv,\n });\n const { stdout: sha } = await execa(\n \"git\",\n [\"commit-tree\", tree.trim(), \"-p\", head, \"-m\", message],\n { cwd: repoRoot, env: { ...indexEnv, ...(await identityEnv(repoRoot)) } },\n );\n commitSha = sha.trim();\n if (!commitSha) return false;\n } catch (err) {\n const missing = gitMissingError(err);\n if (missing) throw missing;\n throw toGitError(err, \"purge local state\");\n } finally {\n rmSync(tmpIndex, { force: true });\n }\n\n // Move the branch, then drop the path from the REAL index so it isn't\n // re-added as a staged addition against the new HEAD. Both are scoped:\n // no other index entry and no working-tree file is touched.\n try {\n await execa(\"git\", [\"update-ref\", \"HEAD\", commitSha, head], {\n cwd: repoRoot,\n });\n } catch (err) {\n throw toGitError(err, \"purge local state\");\n }\n try {\n await execa(\n \"git\",\n [\"rm\", \"-q\", \"-r\", \"--cached\", \"--ignore-unmatch\", \"--\", \".mist-desktop\"],\n { cwd: repoRoot },\n );\n } catch {\n // Non-fatal: HEAD is already correct, so the push ships the removal.\n }\n return true;\n}\n\n/** `GIT_{AUTHOR,COMMITTER}_*` overrides for `commit-tree`, which (unlike\n * `git commit`) fails outright rather than prompting when the repo has\n * no identity. Only set what the repo itself doesn't define. */\nasync function identityEnv(cwd: string): Promise<NodeJS.ProcessEnv> {\n const env: NodeJS.ProcessEnv = {};\n const read = async (key: string): Promise<boolean> => {\n try {\n const r = await execa(\"git\", [\"config\", key], { cwd });\n return r.stdout.trim().length > 0;\n } catch {\n return false;\n }\n };\n if (!(await read(\"user.name\"))) {\n env[\"GIT_AUTHOR_NAME\"] = \"Fluid Portal CLI\";\n env[\"GIT_COMMITTER_NAME\"] = \"Fluid Portal CLI\";\n }\n if (!(await read(\"user.email\"))) {\n env[\"GIT_AUTHOR_EMAIL\"] = \"portal@fluid.app\";\n env[\"GIT_COMMITTER_EMAIL\"] = \"portal@fluid.app\";\n }\n return env;\n}\n\n/** Shared `-c user.*` args: use the repo's identity, else a synthetic\n * Fluid bot identity so the commit isn't rejected. */\nasync function resolveIdentityArgs(cwd: string): Promise<string[]> {\n let hasName = false;\n let hasEmail = false;\n try {\n const r = await execa(\"git\", [\"config\", \"user.name\"], { cwd });\n hasName = r.stdout.trim().length > 0;\n } catch {\n // exit 1 from `git config <key>` just means the key is unset.\n }\n try {\n const r = await execa(\"git\", [\"config\", \"user.email\"], { cwd });\n hasEmail = r.stdout.trim().length > 0;\n } catch {\n // ditto.\n }\n return [\n ...(hasName ? [] : [\"-c\", \"user.name=Fluid Portal CLI\"]),\n ...(hasEmail ? [] : [\"-c\", \"user.email=portal@fluid.app\"]),\n ];\n}\n\nexport interface GitAutoCommitOptions {\n /**\n * Stage with `git add --ignore-removal .` (new + modified files only)\n * instead of `git add -A`, so files present in the index but absent\n * from the working tree are NOT recorded as deletions. Used by the\n * first-push adoption path in git-sync: a directory populated by\n * `fluid portal pull` (content-only sync) has no idea what the full\n * repo tracks, so local additions/edits win but remote-only files are\n * preserved — deletions only ever ship from full checkouts.\n */\n readonly ignoreRemovals?: boolean;\n}\n\n/**\n * `git add -A && git commit -m <message>` (or, with\n * `{ ignoreRemovals: true }`, `git add --ignore-removal .` — see\n * GitAutoCommitOptions).\n *\n * `git add -A` intentionally stages the whole work tree: a portal's\n * provisioned repo tracks the entire project (it's seeded from the\n * starter template), and the \"publish my changes\" UX expects every edit\n * to ship — the same stance as `fluid mist push`. The exclusion boundary\n * is the project's `.gitignore` (the starter ignores `.env`,\n * `node_modules/`, `dist/`, `.portal-sync/`, logs, `*.tsbuildinfo`), so\n * generated output and local secrets stay out of the commit.\n *\n * That boundary is not enough on its own for tool-local state written\n * INTO the project directory by whatever opened it — most importantly\n * Mist Desktop's `.mist-desktop/` (private chats, attachments,\n * screenshots) and the `.mist` clone marker. A `.gitignore`-only fix\n * can't reach clones that already exist, so this applies belt AND\n * suspenders, all anchored to the repository root (see\n * {@link resolveRepoRoot}):\n *\n * 1. {@link excludeLocalStateFromGit} — idempotent `.git/info/exclude`\n * write, so `git add` never picks the paths up in the first place.\n * Heals pre-existing clones on their next push.\n * 2. `git reset -- ':(top).mist' .mist` after staging — unstages the\n * marker if step 1 silently failed. We deliberately do NOT pass a\n * `:(exclude)` pathspec to `git add`: when the path is *also*\n * listed in `.git/info/exclude`, git exits 1 with \"paths are\n * ignored by one of your .gitignore files\", turning the normal\n * (already-healed) case into a hard failure. Add-then-reset is a\n * no-op when there's nothing staged.\n * 3. `git rm --cached -r --ignore-unmatch .mist-desktop` — untracks\n * chat history that an older, unhardened push already committed.\n * Ignore rules alone cannot untrack an existing path. `--cached`\n * keeps every file on disk; only the index entry goes away. This\n * runs even under `ignoreRemovals`, where suppressing deletions\n * protects remote-only *portal* files — private chats are never\n * that, and leaving them tracked would keep republishing them.\n *\n * Steps 2 and 3 are best-effort and never fail the publish; the commit\n * itself is what must succeed.\n *\n * Uses the repo's configured identity, else a synthetic Fluid bot\n * identity so the commit isn't rejected. \"nothing to commit\" is treated\n * as a no-op, not an error.\n */\n/**\n * The staged file set, as {@link ChangeEntry}s — the input a\n * {@link CommitMessage} resolver summarizes.\n *\n * `-z` (NUL-delimited) rather than the default: paths with spaces or\n * quotes are legal in git and common in themes and asset folders, and\n * the newline form quotes and escapes them, which would corrupt both the\n * path and the count.\n *\n * Best-effort — a summary is a nicety, never a reason to fail a publish.\n * An empty result makes the resolver fall back to its own constant.\n */\nasync function stagedChanges(cwd: string): Promise<ChangeEntry[]> {\n try {\n const { stdout } = await execa(\n \"git\",\n [\"diff\", \"--cached\", \"--name-status\", \"-z\"],\n { cwd },\n );\n return parseNameStatusZ(stdout);\n } catch {\n return [];\n }\n}\n\nexport async function gitAutoCommit(\n cwd: string,\n message: CommitMessage,\n options: GitAutoCommitOptions = {},\n): Promise<void> {\n const repoRoot = await resolveRepoRoot(cwd);\n // Defensive: heals a clone made before the sync started excluding these\n // paths, or one whose clone-time exclude write failed.\n excludeLocalStateFromGit(repoRoot);\n\n const addArgs = options.ignoreRemovals\n ? [\"add\", \"--ignore-removal\", \".\"]\n : [\"add\", \"-A\"];\n try {\n await execa(\"git\", addArgs, { cwd });\n } catch (err) {\n throw toGitError(err, \"add\");\n }\n\n // Second line of defense — only reachable if the exclude write above\n // failed. `:(top).mist` resolves against the work-tree root so a push\n // from a subdirectory still unstages the root marker; plain `.mist`\n // additionally covers a project nested inside a larger repo. `git\n // reset` on paths with nothing staged already exits 0, so a failure\n // here is unexpected and still not worth aborting a publish over.\n try {\n await execa(\"git\", [\"reset\", \"-q\", \"--\", \":(top).mist\", \".mist\"], { cwd });\n } catch {\n // Non-fatal — see doc comment.\n }\n // Untrack private Desktop state committed by an older publish, from the\n // repository root so a subdirectory invocation still matches it.\n try {\n await execa(\n \"git\",\n [\"rm\", \"-q\", \"-r\", \"--cached\", \"--ignore-unmatch\", \"--\", \".mist-desktop\"],\n { cwd: repoRoot },\n );\n } catch {\n // Non-fatal — the exclude file remains the primary defense.\n }\n\n // Resolve the subject only now: everything above decides what is\n // actually staged, so a summary built any earlier would describe a\n // file set the commit doesn't carry.\n const subject =\n typeof message === \"string\"\n ? message\n : await message(await stagedChanges(cwd));\n const identityArgs = await resolveIdentityArgs(cwd);\n try {\n // BOTH output streams are piped (not inherited) so the catch below\n // can actually inspect them — with \"inherit\", execa's e.stdout /\n // e.stderr are undefined and the documented \"nothing to commit is a\n // no-op\" contract can never match. Notably git prints \"no changes\n // added to commit\" to STDOUT, so piping stderr alone isn't enough.\n // The sync is meant to be invisible, so swallowing the commit\n // summary on success is fine.\n await execa(\"git\", [...identityArgs, \"commit\", \"-m\", subject], {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n } catch (err) {\n const missing = gitMissingError(err);\n if (missing) throw missing;\n const e = err as ExecaError;\n // git prints \"nothing to commit\" on stdout or stderr depending on\n // version/porcelain state — check both captured streams. With\n // ignoreRemovals, a tree whose only pending changes are deletions\n // stages nothing, and git phrases that as \"no changes added to\n // commit\" instead — equally a no-op here.\n const streams = `${typeof e.stdout === \"string\" ? e.stdout : \"\"}\\n${typeof e.stderr === \"string\" ? e.stderr : \"\"}`;\n if (/nothing to commit|no changes added to commit/i.test(streams)) {\n return;\n }\n throw toGitError(err, \"commit\");\n }\n}\n\n/**\n * `git push <cleanUrl> HEAD:<branch>` — credentials supplied via\n * GIT_ASKPASS, never argv. `http.…/.extraheader=` clears any cached\n * Authorization header a CI-style config might inject.\n */\nexport async function gitPush(\n cwd: string,\n remoteUrl: string,\n branch = \"main\",\n): Promise<void> {\n const { cleanUrl, env } = gitAuthSpawn(remoteUrl);\n try {\n await execa(\n \"git\",\n [\n \"-c\",\n \"credential.helper=\",\n \"-c\",\n \"http.https://github.com/.extraheader=\",\n \"push\",\n cleanUrl,\n `HEAD:${branch}`,\n ],\n { cwd, env, stdio: [\"ignore\", \"inherit\", \"inherit\"] },\n );\n } catch (err) {\n throw toGitError(err, \"push\");\n }\n}\n","/**\n * GitHub-template scaffolding for `fluid portal create`.\n *\n * The canonical starter lives at github.com/Fluid-WeCommerce/portal-template\n * as a **fully rendered, runnable** project (GitHub's generate-from-template\n * copies files verbatim, so it can't contain `{{handlebars}}` placeholders).\n * The project name is baked in as a distinctive token (CANONICAL_PROJECT_NAME);\n * after cloning we re-substitute it with the user's chosen app name.\n *\n * This is an explicit opt-in create path. The bundled template is the\n * deterministic default, so git or network availability cannot change the\n * normal scaffold output.\n */\n\nimport {\n readdir,\n readFile,\n writeFile,\n rm,\n copyFile,\n stat,\n} from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { gitClone } from \"../lib/git.js\";\n\n/** Public HTTPS URL of the portal starter template repo. */\nexport const TEMPLATE_REPO_URL =\n \"https://github.com/Fluid-WeCommerce/portal-template.git\";\n\n/**\n * The project name baked into the rendered template repo. `fluid portal\n * create` replaces every occurrence with the user's app name after\n * cloning. Must match the name the template was rendered with — keep in\n * sync with the render used to publish the repo.\n */\nexport const CANONICAL_PROJECT_NAME = \"fluid-portal-starter\";\n\n/**\n * Files/dirs we never rewrite when re-substituting the project name.\n * The lockfile keys packages by import path (`.`), not the root name, so\n * touching it would only risk corruption for zero benefit.\n */\nconst SUBSTITUTION_SKIP = new Set([\".git\", \"node_modules\", \"pnpm-lock.yaml\"]);\n\n/** Extensions we treat as text and safe to rewrite. Everything else is\n * left byte-for-byte (avoids mangling any binary asset a future template\n * might carry). */\nconst TEXT_EXTENSIONS = new Set([\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".json\",\n \".md\",\n \".yml\",\n \".yaml\",\n \".css\",\n \".html\",\n \".txt\",\n]);\n\nfunction hasTextExtension(file: string): boolean {\n const dot = file.lastIndexOf(\".\");\n if (dot < 0) return false;\n return TEXT_EXTENSIONS.has(file.slice(dot));\n}\n\n/** Recursively list absolute file paths, skipping SUBSTITUTION_SKIP dirs. */\nasync function listFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n for (const entry of entries) {\n if (SUBSTITUTION_SKIP.has(entry.name)) continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await listFiles(full)));\n } else {\n files.push(full);\n }\n }\n return files;\n}\n\n/** Escape a string for use in a RegExp. */\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Replace every occurrence of the canonical project name with `appName`\n * across the cloned tree's text files. Only files that actually contain\n * the token are rewritten.\n */\nexport async function rewriteProjectName(\n targetPath: string,\n appName: string,\n): Promise<void> {\n if (appName === CANONICAL_PROJECT_NAME) return;\n const token = new RegExp(escapeRegExp(CANONICAL_PROJECT_NAME), \"g\");\n // listFiles already prunes the SUBSTITUTION_SKIP dirs/files.\n const files = await listFiles(targetPath);\n for (const file of files) {\n if (!hasTextExtension(file)) continue;\n const content = await readFile(file, \"utf-8\");\n if (!content.includes(CANONICAL_PROJECT_NAME)) continue;\n // Function replacement so `$` sequences in the app name are taken\n // literally instead of as replacement patterns ($&, $$, …).\n await writeFile(\n file,\n content.replace(token, () => appName),\n \"utf-8\",\n );\n }\n}\n\n/** Rewrite `.fluidrc` with the chosen CLI profile (the template ships an\n * empty profile). No-op when the file is absent. */\nexport async function writeFluidrcProfile(\n targetPath: string,\n profileName: string,\n): Promise<void> {\n const fluidrcPath = join(targetPath, \".fluidrc\");\n if (!existsSync(fluidrcPath)) return;\n await writeFile(\n fluidrcPath,\n JSON.stringify({ profile: profileName }, null, 2) + \"\\n\",\n \"utf-8\",\n );\n}\n\nexport interface ScaffoldFromGitHubOptions {\n readonly targetPath: string;\n readonly appName: string;\n readonly profileName: string;\n /** Override the template repo URL (tests / private mirrors). */\n readonly repoUrl?: string;\n}\n\n/**\n * Clone the GitHub starter template into `targetPath`, discard its git\n * history, re-substitute the project name, wire `.fluidrc`, and copy\n * `.env.example` → `.env`.\n *\n * Throws (GitError or otherwise) on any failure so the explicit remote\n * template request is reported as a failure.\n */\nexport async function scaffoldFromGitHub(\n options: ScaffoldFromGitHubOptions,\n): Promise<void> {\n const { targetPath, appName, profileName } = options;\n const repoUrl = options.repoUrl ?? TEMPLATE_REPO_URL;\n\n // Shallow clone — we throw the history away immediately.\n await gitClone(repoUrl, targetPath, { depth: 1 });\n\n // Drop the template's git history so the new project starts clean.\n await rm(join(targetPath, \".git\"), { recursive: true, force: true });\n\n await rewriteProjectName(targetPath, appName);\n await writeFluidrcProfile(targetPath, profileName);\n\n // Copy .env.example → .env so dotenv works out of the box (matches the\n // bundled scaffold behaviour).\n const envExample = join(targetPath, \".env.example\");\n try {\n if ((await stat(envExample)).isFile()) {\n await copyFile(envExample, join(targetPath, \".env\"));\n }\n } catch {\n // No .env.example — nothing to seed.\n }\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { join, dirname, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { constants } from \"node:fs\";\nimport {\n copyFile,\n lstat,\n mkdir,\n mkdtemp,\n readdir,\n rm,\n writeFile,\n} from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport type { CreateOptions, ProjectConfig } from \"../types.js\";\nimport { promptProjectConfig } from \"../utils/prompts.js\";\nimport {\n getTemplatePaths,\n copyTemplate,\n directoryExists,\n createDirectory,\n getSdkVersion,\n getCliVersion,\n getPortalCliVersion,\n resolveInstallableTemplateVersion,\n fileExists,\n getLocalPackageLinkVersion,\n} from \"../utils/file-system.js\";\nimport {\n installDependencies,\n getRunCommand,\n} from \"../utils/package-manager.js\";\nimport { isGitAvailable } from \"../lib/git.js\";\nimport { scaffoldFromGitHub } from \"../utils/scaffold-github.js\";\n\nexport interface CreatePortalProjectDependencies {\n readonly installDependencies: typeof installDependencies;\n readonly isGitAvailable: typeof isGitAvailable;\n readonly scaffoldFromGitHub: typeof scaffoldFromGitHub;\n}\n\nconst defaultCreatePortalProjectDependencies = {\n installDependencies,\n isGitAvailable,\n scaffoldFromGitHub,\n} satisfies CreatePortalProjectDependencies;\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(\n \"--skip-install\",\n \"Create the scaffold only; skip dependency installation\",\n )\n .option(\n \"-o, --output-dir <dir>\",\n \"Directory to create the project in (defaults to cwd)\",\n )\n .option(\n \"--local\",\n \"Use local monorepo packages via link: paths (for development testing)\",\n )\n .option(\n \"--remote-template\",\n \"Use the GitHub starter template instead of the bundled template\",\n )\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(\n resolve(options.outputDir ?? process.cwd()),\n appName,\n );\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 await createPortalProject(appName, options, config);\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 async function createPortalProject(\n appName: string,\n options: CreateOptions,\n config: ProjectConfig,\n dependencies: CreatePortalProjectDependencies = defaultCreatePortalProjectDependencies,\n): Promise<void> {\n const targetPath = join(resolve(options.outputDir ?? process.cwd()), appName);\n const isLocal = options.local === true;\n\n if (options.remoteTemplate === true) {\n if (isLocal) {\n throw new Error(\"--remote-template cannot be combined with --local\");\n }\n if (config.selectedPages.length > 0) {\n throw new Error(\n \"--remote-template cannot be used with optional page selections\",\n );\n }\n if (!(await dependencies.isGitAvailable())) {\n throw new Error(\n \"The remote template requires Git. Remove --remote-template to use the bundled template.\",\n );\n }\n\n const spinner = ora(\"Fetching starter template from GitHub...\").start();\n try {\n await dependencies.scaffoldFromGitHub({\n targetPath,\n appName: config.name,\n profileName: config.profileName,\n });\n spinner.succeed(\"Fetched starter template\");\n } catch (error) {\n spinner.fail(\"Failed to fetch the GitHub starter template\");\n await rm(targetPath, { recursive: true, force: true }).catch(() => {});\n throw error;\n }\n } else {\n await scaffoldFromBundled(targetPath, config, isLocal, true);\n }\n\n if (config.installDeps) {\n const installSpinner = ora(\"Installing dependencies with pnpm...\").start();\n try {\n await dependencies.installDependencies(targetPath);\n installSpinner.succeed(\"Installed dependencies\");\n } catch (error) {\n installSpinner.fail(\"Failed to install dependencies\");\n console.log();\n console.log(chalk.yellow(`The scaffold was retained at ${targetPath}.`));\n console.log(chalk.yellow(\"Install dependencies manually to continue:\"));\n console.log(chalk.cyan(` cd ${formatCdPath(targetPath)}`));\n console.log(chalk.cyan(\" pnpm install\"));\n throw error;\n }\n }\n\n console.log();\n if (!config.installDeps) {\n console.log(\n chalk.yellow(\n \"Scaffold only: dependencies were not installed and must be installed before development.\",\n ),\n );\n console.log();\n }\n console.log(chalk.green.bold(\"Success!\") + ` Created ${chalk.cyan(appName)}`);\n console.log();\n console.log(\"Next steps:\");\n console.log();\n console.log(chalk.cyan(` cd ${formatCdPath(targetPath)}`));\n if (!config.installDeps) {\n console.log(chalk.cyan(\" pnpm install\"));\n }\n console.log(chalk.cyan(` ${getRunCommand(\"dev\")}`));\n console.log();\n console.log(\n \"Then open \" + chalk.cyan(\"http://localhost:5173\") + \" in your browser.\",\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 if (!config.profileName) {\n console.log(\n chalk.yellow(\n \" Run \" +\n chalk.cyan(\"fluid login\") +\n \" and update \" +\n chalk.cyan(\".fluidrc\") +\n \" with your profile name.\",\n ),\n );\n console.log();\n }\n console.log(\n \"Run \" +\n chalk.cyan(\"pnpm pull\") +\n \" and edit \" +\n chalk.cyan(\"portal/\") +\n \" JSON to customize your portal definition.\",\n );\n console.log();\n}\n\nfunction formatCdPath(targetPath: string): string {\n return relative(process.cwd(), targetPath) || \".\";\n}\n\n/**\n * Scaffold from the bundled base + overlay templates (Handlebars). Used\n * by default, or with `--local`\n * (which rewrites SDK/core deps to workspace `link:` paths so the\n * generated project resolves unpublished monorepo source).\n *\n * Throws on failure; the caller reports and exits.\n */\nasync function scaffoldFromBundled(\n targetPath: string,\n config: ProjectConfig,\n isLocal: boolean,\n createTarget: boolean,\n): Promise<void> {\n // Get template paths (base + overlay)\n const templatePaths = getTemplatePaths(\"starter\");\n if (!(await directoryExists(templatePaths.base))) {\n throw new Error(\"Base template not found\");\n }\n if (!(await directoryExists(templatePaths.overlay))) {\n throw new Error(\"Starter template not found\");\n }\n\n // Get package versions (SDK and CLI are versioned independently), clamped\n // to what the registry can actually install — a from-source or locally\n // built CLI carries not-yet-published release bumps.\n let sdkVersion: string;\n let localCoreVersion: string | undefined;\n const [cliVersion, portalCliVersion] = await Promise.all([\n getCliVersion().then((version) =>\n resolveInstallableTemplateVersion(\"@fluid-app/fluid-cli\", version),\n ),\n getPortalCliVersion().then((version) =>\n resolveInstallableTemplateVersion(\"@fluid-app/fluid-cli-portal\", version),\n ),\n ]);\n\n if (isLocal) {\n // Resolve absolute link: paths so pnpm still finds the packages after the\n // operating system canonicalizes the generated project path, such as macOS\n // resolving /tmp to /private/tmp. The trade-off is that the generated\n // project is pinned to this monorepo's current location: moving or\n // re-cloning the monorepo breaks the links and the project has to be\n // scaffolded again. The portal CLI lives at\n // packages/cli/portal/dist/index.mjs\n // so going up four levels lands us at the monorepo root, then\n // `packages/portal/{sdk,core}` is the canonical location for the\n // SDK + core packages.\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const monorepoRoot = join(currentDir, \"..\", \"..\", \"..\", \"..\");\n const sdkPath = join(monorepoRoot, \"packages\", \"portal\", \"sdk\");\n const corePath = join(monorepoRoot, \"packages\", \"portal\", \"core\");\n\n if (\n !(await directoryExists(sdkPath)) ||\n !(await directoryExists(corePath))\n ) {\n throw new Error(\n \"--local requires running from within the Fluid monorepo\\n\" +\n \" Could not find packages/portal/sdk or packages/portal/core\",\n );\n }\n\n sdkVersion = getLocalPackageLinkVersion(sdkPath);\n localCoreVersion = getLocalPackageLinkVersion(corePath);\n console.log(chalk.cyan(\" Using local packages (--local mode)\"));\n } else {\n sdkVersion = await getSdkVersion().then((version) =>\n resolveInstallableTemplateVersion(\"@fluid-app/portal-sdk\", version),\n );\n }\n\n const spinner = ora(\n createTarget\n ? \"Creating project directory...\"\n : \"Preparing provisioned portal checkout...\",\n ).start();\n if (createTarget) {\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 } else if (!(await directoryExists(targetPath))) {\n throw new Error(`Portal checkout does not exist: ${targetPath}`);\n }\n\n // Copy base template first, then overlay template-specific files on top\n const templateVariables = {\n projectName: config.name,\n sdkVersion,\n localCoreVersion,\n cliVersion,\n portalCliVersion,\n selectedPages: config.selectedPages,\n hasSelectedPages: config.selectedPages.length > 0,\n profileName: config.profileName,\n };\n\n spinner.start(\"Copying template files...\");\n try {\n await copyTemplate(templatePaths.base, targetPath, templateVariables);\n await copyTemplate(templatePaths.overlay, targetPath, templateVariables);\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\n/**\n * Add the runnable bundled starter to an existing provisioned Git checkout.\n * Portal repositories begin with a bootstrap commit, so a first clone may\n * not contain package.json yet. Rendering into the checkout preserves its\n * .git directory and gives both the CLI and Mist Desktop a usable project\n * without creating unrelated local history.\n */\nexport async function scaffoldPortalProjectIntoExistingDirectory(\n targetPath: string,\n config: ProjectConfig,\n): Promise<void> {\n const pendingMarker = join(targetPath, PORTAL_SCAFFOLD_PENDING_MARKER);\n const stagingRoot = await mkdtemp(\n join(tmpdir(), \"fluid-portal-scaffold-stage-\"),\n );\n const stagedProject = join(stagingRoot, \"portal\");\n let completed = false;\n try {\n // A clone retry can distinguish our interrupted/failed scaffold from an\n // unrelated pre-existing directory. Keep the marker on every failure and\n // remove it only after the complete no-clobber overlay succeeds.\n await writeFile(pendingMarker, \"Fluid Portal scaffold pending\\n\");\n await scaffoldFromBundled(stagedProject, config, false, true);\n const preserved = await copyMissingScaffoldFiles(stagedProject, targetPath);\n if (preserved.length > 0) {\n console.log(\n chalk.dim(\n ` Preserved ${preserved.length} existing repository ${preserved.length === 1 ? \"file\" : \"files\"} while adding the runnable scaffold.`,\n ),\n );\n }\n completed = true;\n } finally {\n if (completed) await rm(pendingMarker, { force: true });\n await rm(stagingRoot, { recursive: true, force: true });\n }\n}\n\nexport const PORTAL_SCAFFOLD_PENDING_MARKER = \".fluid-portal-scaffold-pending\";\n\nasync function copyMissingScaffoldFiles(\n sourceRoot: string,\n targetRoot: string,\n relativeDirectory = \"\",\n): Promise<string[]> {\n const preserved: string[] = [];\n const sourceDirectory = join(sourceRoot, relativeDirectory);\n const entries = await readdir(sourceDirectory, { withFileTypes: true });\n\n for (const entry of entries) {\n const relativePath = join(relativeDirectory, entry.name);\n const sourcePath = join(sourceRoot, relativePath);\n const targetPath = join(targetRoot, relativePath);\n if (entry.isDirectory()) {\n if (!(await ensureScaffoldDirectory(targetPath))) {\n // The tracked repository path still wins, but silently omitting this\n // required subtree would report a successful scaffold that cannot\n // build. Fail without deleting or replacing the cloned repository so\n // the owner can resolve the path conflict and retry.\n throw new Error(\n `Cannot add the runnable Portal scaffold because the repository path \"${relativePath}\" is not a directory. The cloned repository and Git history were preserved.`,\n );\n }\n preserved.push(\n ...(await copyMissingScaffoldFiles(\n sourceRoot,\n targetRoot,\n relativePath,\n )),\n );\n continue;\n }\n if (!entry.isFile()) {\n throw new Error(\n `Portal scaffold contains an unsupported file type at ${relativePath}.`,\n );\n }\n try {\n // COPYFILE_EXCL is the no-clobber boundary. A repository file wins\n // even if it appears after the initial clone inspection.\n await copyFile(sourcePath, targetPath, constants.COPYFILE_EXCL);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n preserved.push(relativePath);\n }\n }\n\n return preserved;\n}\n\nasync function ensureScaffoldDirectory(targetPath: string): Promise<boolean> {\n try {\n return (await lstat(targetPath)).isDirectory();\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n // ENOTDIR means an ancestor was concurrently replaced by a file. That\n // repository path still wins and its subtree must remain untouched.\n if (code === \"ENOTDIR\") return false;\n throw error;\n }\n }\n\n try {\n await mkdir(targetPath);\n return true;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\" && code !== \"ENOTDIR\") throw error;\n try {\n return (await lstat(targetPath)).isDirectory();\n } catch (statError) {\n if ((statError as NodeJS.ErrnoException).code === \"ENOTDIR\") return false;\n throw statError;\n }\n }\n}\n\nexport function registerCreateCommand(ctx: PluginContext): void {\n ctx.program.addCommand(createCommand);\n}\n","/**\n * `fluid portal dev` command\n *\n * Starts the Vite development server with the portal dev plugin,\n * which intercepts manifest API requests and serves content from\n * the local `portal/` directory.\n *\n * If no `portal/` directory exists, prompts the user to run `fluid portal pull`\n * or auto-pulls if they are logged in.\n */\n\nimport { 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 { isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport type { DevOptions } from \"../types.js\";\n\nconst PORTAL_DIR = \"portal\";\nconst WATCHED_DIRECTORIES = [\"portal\", \"public\", \"src\"] as const;\nconst WATCHED_ROOT_FILES = new Set([\n \"index.html\",\n \"package.json\",\n \"tsconfig.json\",\n \"vite.config.cjs\",\n \"vite.config.cts\",\n \"vite.config.js\",\n \"vite.config.mjs\",\n \"vite.config.mts\",\n \"vite.config.ts\",\n]);\n\n/**\n * Restrict Vite's root watcher to files that can affect a portal preview.\n * Directory paths themselves must remain visible to Chokidar so it can\n * descend into them and discover their files.\n */\nexport function createPortalWatchIgnored(cwd: string) {\n const projectRoot = resolve(cwd);\n\n return (filePath: string): boolean => {\n const absolutePath = isAbsolute(filePath)\n ? filePath\n : resolve(projectRoot, filePath);\n const projectPath = relative(projectRoot, absolutePath);\n\n if (projectPath === \"\") return false;\n if (projectPath === \"..\" || projectPath.startsWith(`..${sep}`)) return true;\n\n const [topLevelPath] = projectPath.split(sep);\n if (WATCHED_DIRECTORIES.some((directory) => topLevelPath === directory)) {\n return false;\n }\n\n const isEnvironmentFile =\n projectPath === \".env\" || projectPath.startsWith(\".env.\");\n\n return !WATCHED_ROOT_FILES.has(projectPath) && !isEnvironmentFile;\n };\n}\n\nexport function portalDevServerOptions(\n port: number,\n exposeToNetwork: boolean,\n cwd = process.cwd(),\n) {\n return {\n port,\n host: exposeToNetwork ? true : undefined,\n watch: {\n ignored: createPortalWatchIgnored(cwd),\n },\n };\n}\n\n/**\n * Check if the portal directory exists and has content files.\n * Returns true if at minimum `portal/definition.json` exists.\n */\nfunction hasPortalContent(cwd: string): boolean {\n return existsSync(join(cwd, PORTAL_DIR, \"definition.json\"));\n}\n\n/**\n * Preflight check: does `<cwd>/node_modules` actually contain what this\n * command is about to `import()` (`vite`) and inject into the config\n * (`@fluid-app/portal-sdk`, pulled in transitively by the vite plugins)?\n *\n * Without this, a project whose install failed or was never run (no\n * `node_modules` at all) produces a cascade of confusing failures instead\n * of one clear message: `createServer()` below throws \"failed to load\n * config from .../vite.config.ts\", we catch that and warn \"falling back to\n * `vite`\", and the fallback then dies with a raw\n * `ERR_MODULE_NOT_FOUND: Cannot find package 'vite'` — never once saying\n * the actual problem is a missing `pnpm install`.\n */\nexport function checkDependenciesInstalled(\n cwd: string,\n): { ok: true } | { ok: false; message: string } {\n const nodeModulesPath = join(cwd, \"node_modules\");\n const hasVite = existsSync(join(nodeModulesPath, \"vite\"));\n const hasPortalSdk = existsSync(\n join(nodeModulesPath, \"@fluid-app\", \"portal-sdk\"),\n );\n\n if (existsSync(nodeModulesPath) && hasVite && hasPortalSdk) {\n return { ok: true };\n }\n\n return {\n ok: false,\n message:\n `Dependencies aren't installed in ${cwd} — run \\`pnpm install\\`, ` +\n \"then retry.\",\n };\n}\n\n/**\n * Attempt to auto-pull portal content by invoking the pull command's action.\n * Falls back to a helpful error message if pull is not possible.\n */\nasync function autoPull(cwd: string): Promise<boolean> {\n console.log();\n console.log(\n chalk.yellow(\"No portal/ directory found.\") +\n \" Attempting to pull content...\",\n );\n console.log();\n\n try {\n // Dynamically import the pull command to avoid circular deps at module level.\n // Auth is handled internally by the pull command via stored CLI credentials\n // (getAuthToken / getActiveProfile), so no explicit token args are needed.\n // Note: the pull command may call process.exit(1) on failure (e.g. auth\n // errors), which will terminate the process rather than throwing. Use\n // --skip-pull to bypass this if auto-pull causes issues.\n const { pullCommand } = await import(\"./pull.js\");\n\n await pullCommand.parseAsync([], { from: \"user\" });\n\n // Verify content was pulled\n return hasPortalContent(cwd);\n } catch (err) {\n console.log();\n console.log(\n chalk.red(\"Auto-pull failed: \") +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n console.log(\n \"Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" manually to set up local content.\",\n );\n console.log();\n return false;\n }\n}\n\nexport const devCommand: Command = new Command(\"dev\")\n .description(\"Start the development server with local portal content serving\")\n .option(\"-p, --port <port>\", \"Port to run the dev server on\", \"5173\")\n .option(\"--host\", \"Expose the dev server to the network\")\n .option(\"--skip-pull\", \"Skip auto-pull if portal/ directory is missing\")\n .action(async (options: DevOptions & { skipPull?: boolean }) => {\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 // ── Dependency preflight ──────────────────────────────────────────\n // Must run before anything below attempts to load `vite` or the\n // project's vite.config — those failures are confusing (stack\n // traces, ERR_MODULE_NOT_FOUND) where this one is not.\n const depsCheck = checkDependenciesInstalled(cwd);\n if (!depsCheck.ok) {\n console.error(chalk.red(`Error: ${depsCheck.message}`));\n process.exit(1);\n }\n\n // ── Auto-pull check ────────────────────────────────────────────────\n if (!hasPortalContent(cwd) && !options.skipPull) {\n const pulled = await autoPull(cwd);\n if (!pulled) {\n console.error(\n chalk.red(\"Cannot start dev server without portal content.\"),\n );\n console.error(\n chalk.yellow(\n \"Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" to download content first.\",\n ),\n );\n process.exit(1);\n }\n }\n\n if (hasPortalContent(cwd)) {\n console.log();\n console.log(\n chalk.green(\"Portal dev mode: \") +\n \"local content from \" +\n chalk.cyan(\"portal/\") +\n \" will be served\",\n );\n console.log(\n chalk.gray(\n \" Manifest requests intercepted at /api/app/manifest (+ /api/fluid_os/definitions/active)\",\n ),\n );\n console.log(\n chalk.gray(\" File changes in portal/ will trigger a page reload\"),\n );\n }\n\n console.log();\n console.log(chalk.bold(\"Starting development server...\"));\n console.log();\n\n // Fallback args for the degraded path (project's own `vite`).\n const viteArgs = [\"vite\"];\n if (options.port) viteArgs.push(\"--port\", String(options.port));\n if (options.host) viteArgs.push(\"--host\");\n\n // Start Vite programmatically and INJECT the portal dev plugins here, so\n // a portal never has to wire portalDevPlugin/backendDevPlugin into its own\n // vite.config — this command \"just serves the local portal/\". The user's\n // vite.config (react, tailwind, build output) is still loaded and merged\n // underneath. Falls back to spawning the project's `vite` if the\n // programmatic path fails, so behavior degrades rather than breaks.\n // Number.isFinite (not `|| 5173`) so an explicit `--port 0` (\"pick a free\n // port\") is honored rather than coerced back to the default.\n const parsedPort = Number(options.port);\n const port = Number.isFinite(parsedPort) ? parsedPort : 5173;\n\n try {\n const { createServer } = await import(\"vite\");\n const { portalDevPlugin } =\n await import(\"../vite-plugin/portal-dev-plugin.js\");\n const { backendDevPlugin } =\n await import(\"../vite-plugin/backend-dev-plugin.js\");\n // Serves this project's own widgets to preview hosts before they are\n // deployed, so iterating on a widget never requires publishing it.\n const { portalWidgetDevPlugin } =\n await import(\"../vite-plugin/portal-widget-dev-plugin.js\");\n\n // No inline `root` or `server.proxy` here: inline config takes\n // precedence over the project's vite.config, so setting them would\n // silently override a project's own root (e.g. root: \"web\") or a\n // local/staging /api proxy. portalDevPlugin's config() hook provides\n // the /api proxy fallback — resolved from the project's env, yielding\n // to any proxy the project already defines.\n const server = await createServer({\n configFile: viteConfigPath,\n envPrefix: [\"VITE_\", \"NEXT_PUBLIC_\"],\n plugins: [\n portalDevPlugin(),\n backendDevPlugin(),\n ...portalWidgetDevPlugin(),\n ],\n server: portalDevServerOptions(port, options.host === true, cwd),\n });\n\n await server.listen();\n server.printUrls();\n\n const shutdown = () => {\n void server.close().finally(() => process.exit(0));\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Keep the process alive until a signal tears the server down.\n await new Promise<never>(() => {});\n } catch (error) {\n console.warn(\n chalk.yellow(\n \"Could not start the enriched dev server; falling back to `vite`. \" +\n \"Local portal content requires portalDevPlugin in vite.config for the fallback path.\",\n ),\n error instanceof Error ? error.message : String(error),\n );\n try {\n await execa(\"pnpm\", viteArgs, { cwd, stdio: \"inherit\" });\n } catch (fallbackErr) {\n // execa v8 sets `signal` (not `code`) when killed by a signal. Treat\n // both SIGINT and SIGTERM as clean shutdowns, matching the\n // programmatic path above.\n const execaError = fallbackErr as { signal?: string };\n if (execaError.signal === \"SIGINT\" || execaError.signal === \"SIGTERM\") {\n return;\n }\n console.error(chalk.red(\"Development server exited with an error\"));\n process.exit(1);\n }\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\n await execa(\"pnpm\", [\"run\", \"build\"], {\n cwd,\n stdio: \"pipe\",\n });\n\n spinner.succeed(\"Build completed\");\n\n const outDir = options.outDir ?? \"dist\";\n console.log();\n console.log(`Output written to ${chalk.cyan(outDir)}/`);\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","import type { WidgetSchema } from \"../types/widget-schema\";\nimport type { ContainerWidgetBehavior } from \"./container-types\";\nimport type { WidgetManifest } from \"./widget-manifest\";\n\n/**\n * Registry mapping widget types to their container behaviors\n */\nexport type ContainerRegistry = Map<string, ContainerWidgetBehavior>;\n\n/**\n * Default container registry with LayoutWidget and ContainerWidget\n */\nexport const DEFAULT_CONTAINER_REGISTRY: ContainerRegistry = new Map([\n [\n \"LayoutWidget\",\n {\n isContainer: true,\n getChildren: (props) => (props.children as (WidgetSchema | null)[]) || [],\n setChildren: (props, children) => ({ ...props, children }),\n },\n ],\n [\n \"CardWidget\",\n {\n isContainer: true,\n getChildren: (props) => (props.children as (WidgetSchema | null)[]) || [],\n setChildren: (props, children) => ({ ...props, children }),\n },\n ],\n [\n \"ContainerWidget\",\n {\n isContainer: true,\n getChildren: (props) => (props.children as (WidgetSchema | null)[]) || [],\n setChildren: (props, children) => ({ ...props, children }),\n },\n ],\n]);\n\n/**\n * Check if a widget type is a container\n */\nexport function isContainerWidget(\n widgetType: string,\n containerRegistry: ContainerRegistry,\n): boolean {\n return containerRegistry.has(widgetType);\n}\n\n/**\n * Get children from a container widget\n */\nexport function getContainerChildren(\n widget: WidgetSchema,\n containerRegistry: ContainerRegistry,\n): (WidgetSchema | null)[] {\n const behavior = containerRegistry.get(widget.type);\n if (!behavior) return [];\n return behavior.getChildren(widget.props);\n}\n\n/**\n * Create updated widget with new children\n */\nexport function setContainerChildren(\n widget: WidgetSchema,\n children: (WidgetSchema | null)[],\n containerRegistry: ContainerRegistry,\n): WidgetSchema {\n const behavior = containerRegistry.get(widget.type);\n if (!behavior) return widget;\n\n return {\n ...widget,\n props: behavior.setChildren(widget.props, children),\n };\n}\n\n/**\n * Register a new container widget type\n */\nexport function createContainerRegistry(\n entries: [string, ContainerWidgetBehavior][] = [],\n): ContainerRegistry {\n return new Map([...DEFAULT_CONTAINER_REGISTRY, ...entries]);\n}\n\n/**\n * Creates a container registry from widget manifests.\n * Extracts container behaviors from manifests that define them,\n * then merges with defaults via createContainerRegistry().\n */\nexport function createContainerRegistryFromManifests(\n manifests: WidgetManifest[],\n): ContainerRegistry {\n const containerEntries: [string, ContainerWidgetBehavior][] = manifests\n .filter((m) => m.container != null)\n .map((m) => [m.type, m.container!]);\n return createContainerRegistry(containerEntries);\n}\n","/**\n * Cross-reference validation and change categorization utilities for the push command.\n *\n * Extracted into a standalone utility so that pure logic can be tested\n * without pulling in CLI dependencies (ora, chalk, prompts, etc.).\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join, basename, relative, sep } from \"node:path\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\n\nimport {\n DEFAULT_CONTAINER_REGISTRY,\n isContainerWidget,\n} from \"@fluid-app/portal-core/registries\";\nimport {\n validatePortalResource,\n type PortalResourceKind,\n} from \"@fluid-app/portal-definition-schema\";\n\nimport type { PortalMappings } from \"./mappings.js\";\nimport type { SnapshotDiff } from \"./snapshot.js\";\nimport type {\n LocalNavigation,\n LocalNavigationItem,\n LocalProfile,\n LocalScreen,\n} from \"./transform.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Categorize changed files by resource type. */\nexport interface CategorizedChanges {\n readonly screens: { new: string[]; changed: string[]; deleted: string[] };\n readonly themes: { new: string[]; changed: string[]; deleted: string[] };\n readonly navigations: { new: string[]; changed: string[]; deleted: string[] };\n readonly profiles: { new: string[]; changed: string[]; deleted: string[] };\n}\n\n/** A validation error found during cross-reference checking. */\nexport interface ValidationError {\n readonly file: string;\n readonly message: string;\n}\n\n/**\n * A non-blocking advisory. Same shape as {@link ValidationError}, kept distinct\n * so a caller cannot accidentally treat one as the other — warnings must never\n * fail a push.\n */\nexport interface ValidationWarning {\n readonly file: string;\n readonly message: string;\n}\n\nconst RESOURCE_DIRECTORIES = [\n [\"screen\", \"screens\"],\n [\"theme\", \"themes\"],\n [\"navigation\", \"navigations\"],\n [\"profile\", \"profiles\"],\n] as const satisfies readonly (readonly [PortalResourceKind, string])[];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Extract the slug from a file path (e.g., \"screens/home.json\" -> \"home\").\n */\nexport function slugFromPath(filePath: string): string {\n return basename(filePath, \".json\");\n}\n\n/**\n * Extract the resource type directory from a file path (e.g., \"screens/home.json\" -> \"screens\").\n */\nfunction resourceTypeFromPath(\n filePath: string,\n): \"screens\" | \"themes\" | \"navigations\" | \"profiles\" | null {\n const dir = filePath.split(\"/\")[0];\n if (\n dir === \"screens\" ||\n dir === \"themes\" ||\n dir === \"navigations\" ||\n dir === \"profiles\"\n ) {\n return dir;\n }\n return null;\n}\n\n/**\n * Read and parse a JSON file from the portal directory.\n */\nasync function readPortalFile<T>(\n portalDir: string,\n relativePath: string,\n): Promise<T> {\n const filePath = join(portalDir, relativePath);\n const content = await readFile(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n}\n\nfunction collectJsonFiles(\n directory: string,\n baseDirectory = directory,\n): string[] {\n const files: string[] = [];\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n const absolutePath = join(directory, entry.name);\n const isDirectory = entry.isSymbolicLink()\n ? statSync(absolutePath).isDirectory()\n : entry.isDirectory();\n if (isDirectory) {\n files.push(...collectJsonFiles(absolutePath, baseDirectory));\n } else if (entry.name.endsWith(\".json\")) {\n files.push(relative(baseDirectory, absolutePath).split(sep).join(\"/\"));\n }\n }\n return files.sort();\n}\n\n/** Validate every managed local portal file against the canonical schemas. */\nexport async function validatePortalSchemas(\n portalDir: string,\n): Promise<ValidationError[]> {\n const files: Array<{ kind: PortalResourceKind; file: string }> = [];\n const definitionPath = join(portalDir, \"definition.json\");\n if (existsSync(definitionPath)) {\n files.push({ kind: \"definition\", file: \"definition.json\" });\n } else {\n return [\n {\n file: \"definition.json\",\n message: \"File is required by the canonical portal schema.\",\n },\n ];\n }\n\n for (const [kind, directory] of RESOURCE_DIRECTORIES) {\n const absoluteDirectory = join(portalDir, directory);\n if (!existsSync(absoluteDirectory)) continue;\n for (const file of collectJsonFiles(absoluteDirectory)) {\n files.push({ kind, file: `${directory}/${file}` });\n }\n }\n\n const errors: ValidationError[] = [];\n for (const { kind, file } of files) {\n let value: unknown;\n try {\n value = await readPortalFile<unknown>(portalDir, file);\n } catch {\n errors.push({ file, message: \"File is not valid JSON.\" });\n continue;\n }\n\n const result = validatePortalResource(kind, value);\n if (!result.success) {\n for (const issue of result.issues) {\n errors.push({\n file,\n message: `${issue.path} ${issue.message}`,\n });\n }\n }\n }\n return errors;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Change categorization\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Categorize a snapshot diff into resource-type-specific change lists.\n */\nexport function categorizeChanges(diff: SnapshotDiff): CategorizedChanges {\n const result: CategorizedChanges = {\n screens: { new: [], changed: [], deleted: [] },\n themes: { new: [], changed: [], deleted: [] },\n navigations: { new: [], changed: [], deleted: [] },\n profiles: { new: [], changed: [], deleted: [] },\n };\n\n for (const file of diff.new) {\n const type = resourceTypeFromPath(file);\n if (type) result[type].new.push(file);\n }\n for (const file of diff.changed) {\n const type = resourceTypeFromPath(file);\n if (type) result[type].changed.push(file);\n }\n for (const file of diff.deleted) {\n const type = resourceTypeFromPath(file);\n if (type) result[type].deleted.push(file);\n }\n\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Screen widget nodes\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Duplicate IDs require tree-wide state and remain a semantic validation. */\nfunction collectWidgetNodeErrors(nodes: readonly unknown[]): string[] {\n return walkWidgetNodes(nodes, \"component_tree\", new Map());\n}\n\nfunction walkWidgetNodes(\n nodes: readonly unknown[],\n path: string,\n seenIds: Map<string, string>,\n): string[] {\n const errors: string[] = [];\n\n nodes.forEach((node, index) => {\n const at = `${path}[${index}]`;\n\n if (node === null || typeof node !== \"object\" || Array.isArray(node))\n return;\n\n const record = node as Record<string, unknown>;\n if (typeof record.id === \"string\" && record.id.trim().length > 0) {\n const firstClaim = seenIds.get(record.id);\n if (firstClaim === undefined) {\n seenIds.set(record.id, at);\n } else {\n errors.push(\n `${at} reuses the id ${JSON.stringify(record.id)}, already used by ${firstClaim}. Ids must be unique within a screen — the builder resolves a widget by id and takes the first match, so duplicates make select, delete and drag-drop act on the wrong widget.`,\n );\n }\n }\n\n const props = record.props;\n if (typeof props !== \"object\" || props === null || Array.isArray(props)) {\n return;\n }\n const children = (props as Record<string, unknown>).children;\n if (Array.isArray(children)) {\n errors.push(\n ...walkWidgetNodes(children, `${at}.props.children`, seenIds),\n );\n }\n });\n\n return errors;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cross-reference validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validate that all cross-references between local portal files are valid.\n *\n * Checks:\n * - Widget IDs remain unique within each changed screen\n * - Navigation items' \"screen\" slugs reference existing screen files or mappings\n * - Profile \"navigation\" and \"mobile_navigation\" slugs reference existing nav files or mappings\n * - Profile \"themes\" slugs reference existing theme files or mappings\n */\nexport async function validateCrossReferences(\n portalDir: string,\n mappings: PortalMappings,\n changes: CategorizedChanges,\n): Promise<ValidationError[]> {\n const errors: ValidationError[] = [];\n\n // Build sets of valid slugs (existing mappings + local files on disk)\n const validScreenSlugs = buildValidSlugsSet(portalDir, \"screens\", mappings);\n const validNavSlugs = buildValidSlugsSet(portalDir, \"navigations\", mappings);\n const validThemeSlugs = buildValidSlugsSet(portalDir, \"themes\", mappings);\n\n // Remove deleted resource slugs from valid sets\n for (const file of changes.screens.deleted) {\n validScreenSlugs.delete(slugFromPath(file));\n }\n for (const file of changes.navigations.deleted) {\n validNavSlugs.delete(slugFromPath(file));\n }\n for (const file of changes.themes.deleted) {\n validThemeSlugs.delete(slugFromPath(file));\n }\n\n const screenFilesToCheck = [\n ...changes.screens.new,\n ...changes.screens.changed,\n ];\n for (const file of screenFilesToCheck) {\n try {\n const screen = await readPortalFile<LocalScreen>(portalDir, file);\n if (Array.isArray(screen.component_tree)) {\n for (const error of collectWidgetNodeErrors(screen.component_tree)) {\n errors.push({ file, message: error });\n }\n }\n } catch {\n errors.push({ file, message: \"Failed to read screen file\" });\n }\n }\n\n // Validate navigation files (new + changed)\n const navFilesToCheck = [\n ...changes.navigations.new,\n ...changes.navigations.changed,\n ];\n for (const file of navFilesToCheck) {\n try {\n const nav = await readPortalFile<LocalNavigation>(portalDir, file);\n if (Array.isArray(nav.navigation_items)) {\n validateNavigationItems(\n nav.navigation_items,\n file,\n validScreenSlugs,\n errors,\n );\n }\n } catch {\n errors.push({ file, message: \"Failed to read navigation file\" });\n }\n }\n\n // Validate profile files (new + changed)\n const profileFilesToCheck = [\n ...changes.profiles.new,\n ...changes.profiles.changed,\n ];\n for (const file of profileFilesToCheck) {\n try {\n const profile = await readPortalFile<LocalProfile>(portalDir, file);\n validateProfileReferences(\n profile,\n file,\n validNavSlugs,\n validThemeSlugs,\n errors,\n );\n } catch {\n errors.push({ file, message: \"Failed to read profile file\" });\n }\n }\n\n if (changes.screens.deleted.length > 0) {\n const alreadyChecked = new Set(navFilesToCheck);\n for (const file of listResourceFiles(portalDir, \"navigations\")) {\n if (alreadyChecked.has(file)) continue;\n const nav = await readPortalFileOrNull<LocalNavigation>(portalDir, file);\n if (!nav || !Array.isArray(nav.navigation_items)) continue;\n validateNavigationItems(\n nav.navigation_items,\n file,\n validScreenSlugs,\n errors,\n );\n }\n }\n\n if (\n changes.navigations.deleted.length > 0 ||\n changes.themes.deleted.length > 0\n ) {\n const alreadyChecked = new Set(profileFilesToCheck);\n for (const file of listResourceFiles(portalDir, \"profiles\")) {\n if (alreadyChecked.has(file)) continue;\n const profile = await readPortalFileOrNull<LocalProfile>(portalDir, file);\n if (!profile) continue;\n validateProfileReferences(\n profile,\n file,\n validNavSlugs,\n validThemeSlugs,\n errors,\n );\n }\n }\n\n return errors;\n}\n\nfunction listResourceFiles(\n portalDir: string,\n resourceType: \"navigations\" | \"profiles\",\n): string[] {\n const directory = join(portalDir, resourceType);\n if (!existsSync(directory)) return [];\n try {\n return collectJsonFiles(directory).map((file) => `${resourceType}/${file}`);\n } catch {\n return [];\n }\n}\n\nasync function readPortalFileOrNull<T>(\n portalDir: string,\n relativePath: string,\n): Promise<T | null> {\n try {\n return await readPortalFile<T>(portalDir, relativePath);\n } catch {\n return null;\n }\n}\n\nfunction validateProfileReferences(\n profile: LocalProfile,\n file: string,\n validNavSlugs: Set<string>,\n validThemeSlugs: Set<string>,\n errors: ValidationError[],\n): void {\n if (profile.navigation && !validNavSlugs.has(profile.navigation)) {\n errors.push({\n file,\n message: `References navigation \"${profile.navigation}\" which does not exist`,\n });\n }\n\n if (\n profile.mobile_navigation &&\n !validNavSlugs.has(profile.mobile_navigation)\n ) {\n errors.push({\n file,\n message: `References mobile_navigation \"${profile.mobile_navigation}\" which does not exist`,\n });\n }\n\n for (const themeSlug of profile.themes ?? []) {\n if (!validThemeSlugs.has(themeSlug)) {\n errors.push({\n file,\n message: `References theme \"${themeSlug}\" which does not exist`,\n });\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Screen structure advisories\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Warns about screens the admin visual builder will not accept drops on.\n *\n * Only container widgets receive the builder's edit-mode child-management\n * callbacks (`onAddChild`, child select/delete/duplicate), so a screen whose\n * `component_tree` has no container at the top level renders perfectly — in\n * local preview and in the live portal — while offering no drop zones at all.\n * A human opening it in the builder cannot drag anything onto the page.\n *\n * That combination (correct everywhere you can look, broken in the one place\n * you cannot) is why this is worth reporting at push time. It is the most\n * likely defect in a hand- or agent-authored screen.\n *\n * Deliberately a WARNING, never an error. Screens without a container root are\n * valid definitions that render correctly, and a repository may hold ones that\n * predate this check; failing their push would break working portals to enforce\n * an authoring convention.\n *\n * Silent on anything it cannot parse. Malformed and unreadable screens are\n * already reported as hard errors by {@link validateCrossReferences}, and a\n * second complaint about a file that never parsed would be noise.\n */\nexport async function validateScreenStructure(\n portalDir: string,\n changes: CategorizedChanges,\n): Promise<ValidationWarning[]> {\n const warnings: ValidationWarning[] = [];\n const files = [...changes.screens.new, ...changes.screens.changed];\n\n for (const file of files) {\n let screen: LocalScreen;\n try {\n screen = await readPortalFile<LocalScreen>(portalDir, file);\n } catch {\n continue;\n }\n\n const tree = screen.component_tree;\n if (!Array.isArray(tree) || tree.length === 0) continue;\n\n // Multi-root is a hard failure raised by `validateCrossReferences`, which\n // exits before this runs. Skipped rather than re-reported: an advisory\n // about drop zones on a screen that is already being refused would only\n // compete with the real reason.\n if (tree.length !== 1) continue;\n\n const root = tree[0];\n const rootType =\n root && typeof root === \"object\"\n ? (root as { type?: unknown }).type\n : undefined;\n const hasContainerRoot =\n typeof rootType === \"string\" &&\n isContainerWidget(rootType, CONTAINER_REGISTRY);\n if (hasContainerRoot) continue;\n\n warnings.push({\n file,\n message:\n `No container at the top of \"component_tree\", so the admin builder will show no drop zones on this screen. ` +\n `Wrap the widgets in a ${CONTAINER_TYPE_LIST} node and move them into its \"props.children\".`,\n });\n }\n\n return warnings;\n}\n\n/**\n * Shared with the portal runtime so this check cannot drift from the behaviour\n * it predicts. `DEFAULT_CONTAINER_REGISTRY` is the same map the builder consults\n * via `isContainerWidget`.\n */\nconst CONTAINER_REGISTRY = DEFAULT_CONTAINER_REGISTRY;\n\n/** Rendered into the warning so it names the real options, not a stale list. */\nconst CONTAINER_TYPE_LIST = [...DEFAULT_CONTAINER_REGISTRY.keys()]\n .map((type) => `\"${type}\"`)\n .join(\" / \");\n\n/**\n * Build a set of valid slugs for a resource type by combining\n * existing mapping slugs with local file slugs on disk.\n */\nfunction buildValidSlugsSet(\n portalDir: string,\n resourceType: \"screens\" | \"themes\" | \"navigations\" | \"profiles\",\n mappings: PortalMappings,\n): Set<string> {\n const slugs = new Set<string>();\n\n // Add all slugs from mappings\n for (const slug of Object.keys(mappings[resourceType])) {\n slugs.add(slug);\n }\n\n // Add slugs from local files on disk\n const dir = join(portalDir, resourceType);\n if (existsSync(dir)) {\n try {\n const entries = readdirSync(dir);\n for (const entry of entries) {\n if (entry.endsWith(\".json\")) {\n slugs.add(basename(entry, \".json\"));\n }\n }\n } catch {\n // Directory doesn't exist or can't be read — skip\n }\n }\n\n return slugs;\n}\n\n/**\n * Recursively validate navigation item screen references.\n */\nfunction validateNavigationItems(\n items: LocalNavigationItem[],\n file: string,\n validScreenSlugs: Set<string>,\n errors: ValidationError[],\n): void {\n for (const item of items) {\n if (item.screen && !validScreenSlugs.has(item.screen)) {\n errors.push({\n file,\n message: `Navigation item \"${item.label ?? \"(unlabeled)\"}\" references screen \"${item.screen}\" which does not exist`,\n });\n }\n if (item.children && item.children.length > 0) {\n validateNavigationItems(item.children, file, validScreenSlugs, errors);\n }\n }\n}\n","/**\n * Portal git-credential adapter (the \"invisible git\" seam).\n *\n * Mints a short-lived (~1h) remote URL with an embedded installation\n * token for the portal's provisioned GitHub repo. The URL is used\n * immediately for one git op and discarded — NEVER persisted or logged.\n *\n * The backend endpoint (GitSync phase 006, portal adapter) mirrors the\n * widget contract exactly:\n *\n * POST <endpoint> -> 201 { git_credential: { remote_url, expires_at }, status, meta }\n * errors: 409 not_provisioned/not_connected, 502 vendor_error, 422 else\n * { error_message, errors: { type: [type] }, meta }\n *\n * The exact path is still being finalized as 006 lands, so it lives in\n * one constant here. Until the endpoint ships to prod a request 404s —\n * which we surface as `unavailable` so callers degrade gracefully\n * (warn + skip the git sync, proceed with the normal content push).\n */\n\n/** Minted credential — token embedded in `remote_url`. Treat as secret. */\nimport { ApiError } from \"@fluid-app/api-client-core\";\nimport { createFluidOsHeyApiClient } from \"@fluid-app/fluidos-api-client\";\nimport { fluidOsV0DefinitionGitCredentialCreate } from \"@fluid-app/fluidos-api-client/sdk\";\n\nexport interface PortalGitCredential {\n readonly remote_url: string;\n readonly expires_at: string;\n}\n\n/**\n * Outcome of a credential request. `unavailable` means \"no repo to sync\"\n * (endpoint missing / not provisioned / not connected). Its `retryable`\n * flag distinguishes backend states that resolve on their own from ones\n * that never will.\n *\n * Both `not_provisioned` and `not_connected` are retryable. The first\n * self-heals because the credential request itself triggers provisioning;\n * the second is the state a brand-new definition sits in for its first\n * few seconds, while the Sidekiq job that creates the GitHub repo runs.\n * (An earlier revision read `not_connected` as a durable \"this company's\n * Git integration isn't connected\" — that was a misdiagnosis, and it made\n * every newly created portal give up before its repo was ready.)\n *\n * `repoStatus` carries the underlying GitSync repo status when the API\n * reports one; `disconnected` is the one value a retry can never fix.\n * `conflictType` carries the raw 409 conflict type so callers can tailor\n * messaging. `error` is an unexpected transport or provider failure.\n */\nexport type GitCredentialOutcome =\n | { readonly status: \"ok\"; readonly credential: PortalGitCredential }\n | {\n readonly status: \"unavailable\";\n readonly reason: string;\n readonly retryable: boolean;\n readonly conflictType?: string;\n readonly repoStatus?: string;\n }\n | { readonly status: \"error\"; readonly reason: string };\n\n/**\n * Build the portal git-credential path for a definition id.\n *\n * Matches GitSync Phase 006 (fluid-commerce/fluid #19596): a member\n * route on the company-scoped definitions resource —\n * `POST /api/company/fluid_os/definitions/:id/git_credential`.\n */\nexport function portalGitCredentialPath(definitionId: number): string {\n return `/api/company/fluid_os/definitions/${definitionId}/git_credential`;\n}\n\nexport interface CreatePortalGitCredentialOptions {\n readonly baseUrl: string;\n readonly token: string;\n readonly definitionId: number;\n /** Injectable for tests; defaults to global fetch. */\n readonly fetchImpl?: typeof fetch;\n /** Deadline for one API request; defaults to 15 seconds. */\n readonly requestTimeoutMs?: number;\n}\n\nexport interface PortalGitCredentialRetryEvent {\n readonly attempt: number;\n readonly totalAttempts: number;\n readonly reason: string;\n}\n\nexport interface CreatePortalGitCredentialWithRetryOptions extends CreatePortalGitCredentialOptions {\n readonly retryDelaysMs?: readonly number[];\n readonly sleepImpl?: (delayMs: number) => Promise<void>;\n readonly onRetry?: (event: PortalGitCredentialRetryEvent) => void;\n}\n\n// A freshly-created definition's git repo is provisioned asynchronously by the\n// backend, so create-then-clone can race that provisioning. The retry is the\n// readiness wait — sized to about 55s so new portals usually clone with Git\n// history instead of falling back before the repository is ready.\nconst DEFAULT_RETRY_DELAYS_MS = [\n 500, 1_000, 2_000, 4_000, 8_000, 10_000, 15_000, 15_000,\n] as const;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 15_000;\n// Both 409 conflict types are worth waiting on. `not_provisioned` self-heals:\n// the credential request itself triggers provisioning. `not_connected` is the\n// state a brand-new definition presents — the backend inserts the repo row\n// synchronously during create (status `provisioning`) and does the GitHub work\n// in a Sidekiq job, which lands a few seconds later.\nconst RETRYABLE_CONFLICT_TYPES = new Set([\"not_provisioned\", \"not_connected\"]);\n// The one repo status a retry can never resolve: `disconnected` repos were\n// deliberately torn down and the backend refuses to re-adopt them. Absent on\n// backends that don't report the status yet — treat that as \"keep waiting\".\n// Mirrors DURABLE_REPO_STATUSES in mist-desktop's portal-clone-preflight.ts.\nconst DURABLE_REPO_STATUSES = new Set([\"disconnected\"]);\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction firstString(value: unknown): string | undefined {\n if (Array.isArray(value)) return nonEmptyString(value[0]);\n return nonEmptyString(value);\n}\n\nfunction apiErrorBody(error: ApiError): unknown {\n if (\n error.data !== null &&\n typeof error.data === \"object\" &&\n (\"error\" in error.data ||\n \"error_message\" in error.data ||\n \"errors\" in error.data)\n ) {\n return error.data;\n }\n return { error_message: error.message, errors: error.data };\n}\n\nfunction extractErrorDetails(\n body: unknown,\n fallback: string,\n): {\n readonly reason: string;\n readonly failureType?: string;\n readonly repoStatus?: string;\n} {\n if (!body || typeof body !== \"object\") return { reason: fallback };\n\n const envelope = body as {\n readonly error_message?: unknown;\n readonly error?: unknown;\n readonly errors?: unknown;\n };\n const error =\n envelope.error && typeof envelope.error === \"object\"\n ? (envelope.error as {\n readonly message?: unknown;\n readonly details?: unknown;\n })\n : undefined;\n const nestedDetails =\n error?.details && typeof error.details === \"object\"\n ? (error.details as {\n readonly type?: unknown;\n readonly repo_status?: unknown;\n })\n : undefined;\n const errors =\n envelope.errors && typeof envelope.errors === \"object\"\n ? (envelope.errors as {\n readonly type?: unknown;\n readonly repo_status?: unknown;\n })\n : undefined;\n const reason =\n nonEmptyString(envelope.error_message) ??\n nonEmptyString(error?.message) ??\n nonEmptyString(envelope.error) ??\n fallback;\n const failureType =\n firstString(errors?.type) ?? nonEmptyString(nestedDetails?.type);\n const repoStatus =\n firstString(errors?.repo_status) ??\n nonEmptyString(nestedDetails?.repo_status);\n\n return {\n reason,\n ...(failureType ? { failureType } : {}),\n ...(repoStatus ? { repoStatus } : {}),\n };\n}\n\nfunction sleep(delayMs: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n}\n\n/**\n * Retry clone-credential conflicts while the backend provisions or revives\n * the portal's repository. The first credential request is the trigger for\n * older definitions with no repo, so a single-shot clone would always fail\n * even though the repository becomes ready seconds later.\n */\nexport async function createPortalGitCredentialWithRetry(\n options: CreatePortalGitCredentialWithRetryOptions,\n): Promise<GitCredentialOutcome> {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;\n const sleepImpl = options.sleepImpl ?? sleep;\n const requestOptions: CreatePortalGitCredentialOptions = {\n baseUrl: options.baseUrl,\n token: options.token,\n definitionId: options.definitionId,\n ...(options.fetchImpl !== undefined\n ? { fetchImpl: options.fetchImpl }\n : {}),\n ...(options.requestTimeoutMs !== undefined\n ? { requestTimeoutMs: options.requestTimeoutMs }\n : {}),\n };\n let outcome = await createPortalGitCredential(requestOptions);\n\n for (const [index, delayMs] of retryDelaysMs.entries()) {\n if (outcome.status !== \"unavailable\" || !outcome.retryable) return outcome;\n\n options.onRetry?.({\n attempt: index + 2,\n totalAttempts: retryDelaysMs.length + 1,\n reason: outcome.reason,\n });\n await sleepImpl(delayMs);\n outcome = await createPortalGitCredential(requestOptions);\n }\n\n return outcome;\n}\n\n/**\n * Request a git credential for the portal definition's provisioned repo.\n *\n * Never throws — every failure maps to an `unavailable`/`error` outcome\n * so the invisible-git sync is strictly best-effort.\n */\nexport async function createPortalGitCredential(\n options: CreatePortalGitCredentialOptions,\n): Promise<GitCredentialOutcome> {\n const { baseUrl, token, definitionId } = options;\n const fetchImpl = options.fetchImpl ?? fetch;\n const requestTimeoutMs =\n options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const abortController = new AbortController();\n let requestTimedOut = false;\n const timeout = setTimeout(() => {\n requestTimedOut = true;\n abortController.abort();\n }, requestTimeoutMs);\n\n try {\n const client = createFluidOsHeyApiClient({\n baseUrl,\n fetch: fetchImpl,\n getAuthToken: () => token,\n });\n const response = await fluidOsV0DefinitionGitCredentialCreate({\n client,\n path: { id: definitionId },\n signal: abortController.signal,\n });\n\n const credential = response.git_credential;\n if (!credential?.remote_url) {\n return {\n status: \"error\",\n reason: \"git credential response missing remote_url\",\n };\n }\n\n return { status: \"ok\", credential };\n } catch (err) {\n if (requestTimedOut) {\n return {\n status: \"error\",\n reason: `git credential request timed out after ${requestTimeoutMs}ms`,\n };\n }\n\n if (err instanceof ApiError) {\n if (err.status === 404) {\n return {\n status: \"unavailable\",\n reason: \"git sync isn't available for portals on this Fluid API yet\",\n retryable: false,\n };\n }\n\n const details = extractErrorDetails(\n apiErrorBody(err),\n `git credential request failed (HTTP ${err.status})`,\n );\n if (err.status === 409) {\n const durable =\n details.repoStatus !== undefined &&\n DURABLE_REPO_STATUSES.has(details.repoStatus);\n return {\n status: \"unavailable\",\n reason: details.reason,\n retryable:\n !durable &&\n details.failureType !== undefined &&\n RETRYABLE_CONFLICT_TYPES.has(details.failureType),\n ...(details.failureType !== undefined\n ? { conflictType: details.failureType }\n : {}),\n ...(details.repoStatus !== undefined\n ? { repoStatus: details.repoStatus }\n : {}),\n };\n }\n return { status: \"error\", reason: details.reason };\n }\n\n return {\n status: \"error\",\n reason: err instanceof Error ? err.message : String(err),\n };\n } finally {\n clearTimeout(timeout);\n }\n}\n","/**\n * Invisible-git sync for `fluid portal push`.\n *\n * Before the normal content push, we best-effort mirror the local portal\n * project to its provisioned GitHub repo: fetch → fast-forward-pull if a\n * teammate pushed → auto-commit working-tree changes → push HEAD:main.\n * This mirrors `fluid mist push`, but is STRICTLY non-fatal: any problem\n * (no git, no repo, credential endpoint missing, diverged history,\n * network blip) degrades to a skip so the content push always proceeds.\n */\n\nimport type { CommitMessage } from \"@fluid-app/fluid-cli\";\nimport { gitSyncCommitSubject } from \"@fluid-app/fluid-cli\";\nimport {\n createPortalGitCredential,\n type CreatePortalGitCredentialOptions,\n} from \"../api/git-credential.js\";\nimport {\n ensureLocalStateExcluded,\n getOriginUrl,\n gitAdoptFetchHead,\n gitAheadBehindVsFetchHead,\n gitAutoCommit,\n gitFetch,\n gitInit,\n gitMergeFastForward,\n gitPush,\n gitRemoteBranchExists,\n hasCommits,\n hasCommonAncestorWithFetchHead,\n hasUncommittedChanges,\n isGitRepo,\n normalizedRepoIdentity,\n purgeLocalStateFromIndex,\n} from \"./git.js\";\n\n/** Subject for the index-only commit that untracks leaked Desktop state.\n * Kept distinct from the publish message so the history explains itself,\n * but in the canonical GitSync shape so it parses like every other\n * commit. Attributed to `Fluid` because it's housekeeping the CLI does\n * on its own initiative, not something the user asked for. */\nconst PURGE_COMMIT_MESSAGE = (): string =>\n gitSyncCommitSubject(\n \"Remove private Mist Desktop state from the portal repository\",\n null,\n );\n\nexport type PortalGitSyncResult =\n | { readonly status: \"skipped\"; readonly reason: string }\n | { readonly status: \"synced\"; readonly pushed: boolean }\n /** Local git state was already mutated (commit / fast-forward) and the\n * push then failed — the caller must NOT proceed with the content\n * push, or published artifacts drift from unsynced source. */\n | { readonly status: \"failed\"; readonly reason: string };\n\nexport interface PortalGitSyncOptions {\n readonly cwd: string;\n readonly baseUrl: string;\n readonly token: string;\n readonly definitionId: number;\n /** Literal subject, or a resolver handed the staged changes so it\n * can describe them (see `CommitMessage`). */\n readonly commitMessage: CommitMessage;\n readonly branch?: string;\n /** Injectable for tests. */\n readonly fetchImpl?: CreatePortalGitCredentialOptions[\"fetchImpl\"];\n}\n\n/**\n * Run the best-effort git sync. Returns a result describing what\n * happened; never throws. Callers surface `skipped` reasons as a dim\n * note and continue.\n */\nexport async function runPortalGitSync(\n options: PortalGitSyncOptions,\n): Promise<PortalGitSyncResult> {\n const { cwd, baseUrl, token, definitionId, commitMessage } = options;\n const branch = options.branch ?? \"main\";\n\n // Ensure a local repo exists — `fluid portal create` strips the\n // template's git history, so the first push initializes one and adopts\n // the provisioned remote's history below (the widget CLI's proven\n // first-push flow). A failed init falls back to the old skip.\n if (!(await isGitRepo(cwd)) && !(await gitInit(cwd))) {\n return { status: \"skipped\", reason: \"not a git repository\" };\n }\n\n // Scope the sync to the portal project before ANY tree inspection.\n // Mist Desktop writes its private per-machine state (`.mist-desktop/`:\n // chat transcripts, attachments, screenshots) into the project root it\n // opened. Left unexcluded it both dirties the cleanliness checks below\n // — producing a \"you have uncommitted changes, run git pull\" skip the\n // user can't resolve — and gets published wholesale by the auto-commit.\n await ensureLocalStateExcluded(cwd);\n\n // One credential for the whole sync — tokens live ~1h, far beyond a\n // push. Minting again mid-flow would open a degrade window AFTER the\n // auto-commit: a failure there reads as \"sync unavailable\" while the\n // fresh commit stays local and the content push carries on with\n // unsynced source. With a single mint, unavailability can only surface\n // before any local or remote mutation.\n const outcome = await createPortalGitCredential({\n baseUrl,\n token,\n definitionId,\n fetchImpl: options.fetchImpl,\n });\n if (outcome.status !== \"ok\") {\n return { status: \"skipped\", reason: outcome.reason };\n }\n const remoteUrl = outcome.credential.remote_url;\n\n // Identity guard: if this checkout already has an origin, it must be\n // the repo the credential was minted for. Without this, running\n // `fluid portal push` from an UNRELATED git repo would whole-tree\n // auto-commit that repo and push it to the portal's remote.\n const origin = await getOriginUrl(cwd);\n if (origin) {\n const originId = normalizedRepoIdentity(origin);\n const mintedId = normalizedRepoIdentity(remoteUrl);\n // Fail closed: an origin we can't normalize can't be verified, and\n // the downstream merge-base guard doesn't help when repos share\n // starter-template ancestry — skip rather than risk pushing another\n // project to the portal's remote.\n if (mintedId && originId !== mintedId) {\n return {\n status: \"skipped\",\n reason:\n `this directory's git origin (${originId ?? origin}) is not the ` +\n `portal's provisioned repository (${mintedId}) — are you in the ` +\n \"right project directory?\",\n };\n }\n }\n\n try {\n // Detect drift. When strictly behind with a CLEAN tree we can\n // fast-forward a teammate's commits in safely. We deliberately do NOT\n // stash-then-merge a dirty tree: a stash-pop conflict would leave the\n // checkout advanced with conflict markers while the content push\n // carried on. Instead, behind + dirty (and diverged) simply skip and\n // ask the user to reconcile in a terminal — the working tree is left\n // exactly as it was.\n // A freshly-initialized local repo (unborn HEAD) can't reconcile via\n // ahead/behind — and the backend provisions repos WITH an auto-init\n // or starter-template commit, so a fresh root commit would be\n // rejected as unrelated history. Adopt the remote history as the\n // local base instead; the auto-commit below then records the local\n // tree on top of it and the push fast-forwards.\n if (!(await hasCommits(cwd))) {\n if (await gitRemoteBranchExists(cwd, remoteUrl, branch)) {\n await gitFetch(cwd, remoteUrl, branch);\n await gitAdoptFetchHead(cwd);\n }\n // The adopted history may itself carry chats leaked by an older\n // CLI — purge before the (removal-suppressing) adoption commit.\n await purgeLocalStateFromIndex(cwd, PURGE_COMMIT_MESSAGE());\n if (await hasUncommittedChanges(cwd)) {\n // ADOPTION-ONLY semantics: stage new + modified files, never\n // removals. This directory may hold only a content subset (e.g.\n // populated by `fluid portal pull`, which syncs content but not\n // the repo), so a file absent locally is not a deletion — with\n // plain `add -A`, the first push would delete every remote-only\n // file from the provisioned repo. Local additions/edits win;\n // remote-only files are preserved on adoption. Deletions only\n // ever ship from full checkouts (the `add -A` path below).\n await gitAutoCommit(cwd, commitMessage, { ignoreRemovals: true });\n }\n return await pushCommitted(cwd, remoteUrl, branch);\n }\n\n await gitFetch(cwd, remoteUrl, branch);\n // Second identity guard, for checkouts without an origin: a repo\n // whose history shares no ancestor with the provisioned repo is some\n // OTHER project — never auto-commit and push it wholesale.\n if (!(await hasCommonAncestorWithFetchHead(cwd))) {\n return {\n status: \"skipped\",\n reason:\n \"this repository's history is unrelated to the portal's \" +\n \"provisioned repository — are you in the right project directory?\",\n };\n }\n const { ahead, behind } = await gitAheadBehindVsFetchHead(cwd);\n if (behind > 0 && ahead > 0) {\n return {\n status: \"skipped\",\n reason:\n \"local branch and origin have diverged — run `git pull --rebase` and retry\",\n };\n }\n if (behind > 0) {\n if (await hasUncommittedChanges(cwd)) {\n return {\n status: \"skipped\",\n reason:\n \"origin has newer commits and you have uncommitted changes — \" +\n \"run `git pull` and retry\",\n };\n }\n await gitMergeFastForward(cwd);\n }\n\n // Untrack chats leaked by an older CLI, UNCONDITIONALLY — a checkout\n // whose only problem is a tracked `.mist-desktop/` has a perfectly\n // clean working tree, so gating this on `hasUncommittedChanges`\n // below would skip it forever and leave the chats published. Runs\n // after every skip guard above, so a sync that bails still leaves\n // the repository untouched. No-op (and no commit) when nothing was\n // tracked, which is the overwhelmingly common case.\n await purgeLocalStateFromIndex(cwd, PURGE_COMMIT_MESSAGE());\n\n // Bundle working-tree edits into one commit so push has something to\n // send. No-op when the tree is already clean.\n if (await hasUncommittedChanges(cwd)) {\n await gitAutoCommit(cwd, commitMessage);\n }\n\n return await pushCommitted(cwd, remoteUrl, branch);\n } catch (err) {\n // Reconcile-phase failure (fetch / ls-remote / adopt / ff-merge /\n // commit). An ff-merge may already have advanced the checkout to\n // include a teammate's commits — that state is valid and self-heals,\n // but say so instead of pretending nothing happened.\n return {\n status: \"skipped\",\n reason: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * The push gets its own failure boundary: by this point the auto-commit\n * (and any fast-forward) has already mutated local git state. A push\n * failure here is FATAL to the content push (matching `fluid widget\n * push`): publishing artifacts whose source never reached the repo is\n * exactly the drift the invisible-git model exists to prevent. The\n * local commit is durable — rerunning push finds the branch ahead and\n * retries the sync.\n */\nasync function pushCommitted(\n cwd: string,\n remoteUrl: string,\n branch: string,\n): Promise<PortalGitSyncResult> {\n try {\n await gitPush(cwd, remoteUrl, branch);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n status: \"failed\",\n reason:\n \"your changes are committed locally but couldn't be pushed \" +\n `(${message}) — nothing was published; rerun \\`fluid portal push\\` ` +\n \"to retry\",\n };\n }\n return { status: \"synced\", pushed: true };\n}\n","/**\n * `GET /api/me` — the signed-in user, used to stamp portal git-sync\n * commits in the canonical GitSync format so history says who published.\n * See `@fluid-app/fluid-cli`'s git-sync/commit-subject.ts for the shape.\n *\n * Best-effort by contract: a failed, slow or malformed response resolves\n * to null and the commit is attributed to `Fluid` instead. Publishing is\n * essential; the stamp is not.\n */\n\nimport { gitSyncActorFromMe, resolveGitSyncActor } from \"@fluid-app/fluid-cli\";\nimport type { GitSyncActor } from \"@fluid-app/fluid-cli\";\n\nconst ME_TIMEOUT_MS = 5_000;\n\nexport async function fetchPortalActor(\n baseUrl: string,\n token: string,\n timeoutMs = ME_TIMEOUT_MS,\n): Promise<GitSyncActor | null> {\n // Served from the profile cache after the first push; the request\n // below only fires on a miss.\n return resolveGitSyncActor(() => requestActor(baseUrl, token, timeoutMs));\n}\n\nasync function requestActor(\n baseUrl: string,\n token: string,\n timeoutMs: number,\n): Promise<GitSyncActor | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const resp = await fetch(`${baseUrl.replace(/\\/+$/, \"\")}/api/me`, {\n headers: { Authorization: `Bearer ${token}`, Accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!resp.ok) return null;\n return gitSyncActorFromMe(await resp.json());\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n","import type {\n RemoteDomWidgetDefinition,\n RemoteDomWidgetPackageDescriptor,\n} from \"./remote-dom-widget-package\";\n\nexport const REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITIES = [\n \"networkAccess\",\n \"orders\",\n] as const;\n\nexport type RemoteWidgetGrantRequiredCapability =\n (typeof REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITIES)[number];\n\nexport const REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITY_VERSIONS = {\n networkAccess: \"1\",\n orders: \"1\",\n} as const satisfies Readonly<\n Record<RemoteWidgetGrantRequiredCapability, string>\n>;\n\nconst GRANT_REQUIRED_CAPABILITIES = new Set<string>(\n REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITIES,\n);\n\nexport interface RemoteWidgetCapabilityGrant {\n readonly packageId: string;\n readonly packageVersion: string;\n readonly capabilityVersion: string;\n}\n\nexport type RemoteWidgetCapabilityGrants = Readonly<\n Record<string, RemoteWidgetCapabilityGrant>\n>;\n\nexport function isRemoteWidgetGrantRequiredCapability(\n capability: string,\n): capability is RemoteWidgetGrantRequiredCapability {\n return GRANT_REQUIRED_CAPABILITIES.has(capability);\n}\n\nexport function createRemoteWidgetCapabilityGrant(\n remotePackage: Pick<\n RemoteDomWidgetPackageDescriptor,\n \"packageId\" | \"version\"\n >,\n widget: Pick<RemoteDomWidgetDefinition, \"capabilities\">,\n capability: string,\n): RemoteWidgetCapabilityGrant | undefined {\n if (!isRemoteWidgetGrantRequiredCapability(capability)) return undefined;\n const declaration = widget.capabilities.find(\n ({ name }) => name === capability,\n );\n const supportedVersion =\n REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITY_VERSIONS[capability];\n if (!declaration || declaration.version !== supportedVersion) {\n return undefined;\n }\n\n return {\n packageId: remotePackage.packageId,\n packageVersion: remotePackage.version,\n capabilityVersion: declaration.version,\n };\n}\n\nexport function createRemoteWidgetCapabilityGrants(\n remotePackage: Pick<\n RemoteDomWidgetPackageDescriptor,\n \"packageId\" | \"version\"\n >,\n widget: Pick<RemoteDomWidgetDefinition, \"capabilities\">,\n): RemoteWidgetCapabilityGrants | undefined {\n const grants = Object.fromEntries(\n REMOTE_WIDGET_GRANT_REQUIRED_CAPABILITIES.flatMap((capability) => {\n const grant = createRemoteWidgetCapabilityGrant(\n remotePackage,\n widget,\n capability,\n );\n return grant ? [[capability, grant] as const] : [];\n }),\n );\n return Object.keys(grants).length === 0 ? undefined : grants;\n}\n\nexport function isExactRemoteWidgetCapabilityGrant(\n grant: RemoteWidgetCapabilityGrant | undefined,\n capability: string,\n remotePackage: Pick<\n RemoteDomWidgetPackageDescriptor,\n \"packageId\" | \"version\"\n >,\n widget: Pick<RemoteDomWidgetDefinition, \"capabilities\">,\n): boolean {\n const expected = createRemoteWidgetCapabilityGrant(\n remotePackage,\n widget,\n capability,\n );\n return (\n expected !== undefined &&\n grant?.packageId === expected.packageId &&\n grant.packageVersion === expected.packageVersion &&\n grant.capabilityVersion === expected.capabilityVersion\n );\n}\n\nexport function hasExactRemoteWidgetCapabilityGrants(\n grants: RemoteWidgetCapabilityGrants | undefined,\n remotePackage: Pick<\n RemoteDomWidgetPackageDescriptor,\n \"packageId\" | \"version\"\n >,\n widget: Pick<RemoteDomWidgetDefinition, \"capabilities\">,\n): boolean {\n const requiredDeclarations = widget.capabilities.filter(({ name }) =>\n isRemoteWidgetGrantRequiredCapability(name),\n );\n const expected = createRemoteWidgetCapabilityGrants(remotePackage, widget);\n if (requiredDeclarations.length === 0) return true;\n if (\n !expected ||\n Object.keys(expected).length !== requiredDeclarations.length\n ) {\n return false;\n }\n return Object.keys(expected).every((capability) =>\n isExactRemoteWidgetCapabilityGrant(\n grants?.[capability],\n capability,\n remotePackage,\n widget,\n ),\n );\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport {\n isRemoteDomWidgetPackageDescriptor,\n type RemoteDomWidgetPackageDescriptor,\n} from \"@fluid-app/portal-core/remote-dom-widget-package\";\nimport { createRemoteWidgetCapabilityGrant } from \"@fluid-app/portal-core/remote-widget-capability-grants\";\nimport { atomicWriteFile } from \"./atomic-write.js\";\nimport {\n collectWidgetCapabilityGrants,\n type SnapshotWidgetCapabilityGrant,\n type SnapshotWidgetCapabilityGrantMap,\n} from \"./snapshot.js\";\n\nexport interface WidgetCapabilityPushGrant extends SnapshotWidgetCapabilityGrant {\n readonly file: string;\n}\n\nexport function isWidgetCapabilityApprovedByPushFlags(\n capability: string,\n options: {\n readonly allowNetworkWidgets?: boolean;\n readonly allowWidgetCapabilities?: boolean;\n },\n): boolean {\n return (\n options.allowWidgetCapabilities === true ||\n (capability === \"networkAccess\" && options.allowNetworkWidgets === true)\n );\n}\n\nexport async function prepareWidgetCapabilityPush(options: {\n readonly portalDir: string;\n readonly screenFiles: readonly string[];\n readonly baseline: SnapshotWidgetCapabilityGrantMap | undefined;\n readonly catalogCandidates: readonly unknown[];\n}): Promise<readonly WidgetCapabilityPushGrant[]> {\n const catalog = options.catalogCandidates.filter(\n isRemoteDomWidgetPackageDescriptor,\n );\n const widgetCatalog = indexSensitiveWidgetCapabilities(catalog);\n const newlyEnabled: WidgetCapabilityPushGrant[] = [];\n\n for (const file of options.screenFiles) {\n const path = join(options.portalDir, file);\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as unknown;\n const current = collectWidgetCapabilityGrants(parsed);\n const previousByKey = new Map(\n (options.baseline?.[file] ?? []).map((grant) => [grantKey(grant), grant]),\n );\n for (const grant of current) {\n const expected = widgetCatalog.get(catalogKey(grant));\n if (!expected) {\n throw new Error(\n `Capability grant \"${grant.capability}\" for widget \"${grant.widgetType}\" in ${file} does not match the current widget catalog.`,\n );\n }\n const previous = previousByKey.get(grantKey(grant));\n const previouslyApproved =\n previous?.packageId === expected.packageId &&\n previous.packageVersion === expected.packageVersion &&\n previous.capabilityVersion === expected.capabilityVersion;\n if (!previouslyApproved && !sameGrant(grant, expected) && !previous) {\n throw new Error(\n `Capability grant \"${grant.capability}\" for widget \"${grant.widgetType}\" in ${file} is stale. Pull or approve the current widget package in the builder first.`,\n );\n }\n if (!previouslyApproved) {\n newlyEnabled.push({\n ...expected,\n widgetId: grant.widgetId,\n widgetType: grant.widgetType,\n file,\n });\n }\n }\n }\n\n return newlyEnabled;\n}\n\nexport async function persistApprovedWidgetCapabilityPushGrants(options: {\n readonly portalDir: string;\n readonly grants: readonly WidgetCapabilityPushGrant[];\n}): Promise<void> {\n const grantsByFile = new Map<string, WidgetCapabilityPushGrant[]>();\n for (const grant of options.grants) {\n const fileGrants = grantsByFile.get(grant.file) ?? [];\n fileGrants.push(grant);\n grantsByFile.set(grant.file, fileGrants);\n }\n for (const [file, grants] of grantsByFile) {\n const path = join(options.portalDir, file);\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as unknown;\n for (const grant of grants) {\n writeGrant(parsed, grant);\n }\n await atomicWriteFile(path, `${JSON.stringify(parsed, null, 2)}\\n`);\n }\n}\n\nfunction indexSensitiveWidgetCapabilities(\n packages: readonly RemoteDomWidgetPackageDescriptor[],\n): ReadonlyMap<string, SnapshotWidgetCapabilityGrant> {\n const capabilities = new Map<string, SnapshotWidgetCapabilityGrant>();\n for (const remotePackage of packages) {\n for (const widget of remotePackage.widgets) {\n for (const declaration of widget.capabilities) {\n const exactGrant = createRemoteWidgetCapabilityGrant(\n remotePackage,\n widget,\n declaration.name,\n );\n if (!exactGrant) continue;\n const grant: SnapshotWidgetCapabilityGrant = {\n capability: declaration.name,\n widgetId: \"\",\n widgetType: widget.type,\n ...exactGrant,\n };\n const key = catalogKey(grant);\n if (capabilities.has(key)) {\n throw new Error(\n `Widget type \"${widget.type}\" has multiple catalog entries for capability \"${declaration.name}\".`,\n );\n }\n capabilities.set(key, grant);\n }\n }\n }\n return capabilities;\n}\n\nfunction catalogKey(\n grant: Pick<SnapshotWidgetCapabilityGrant, \"widgetType\" | \"capability\">,\n): string {\n return `${grant.widgetType}\\0${grant.capability}`;\n}\n\nfunction grantKey(\n grant: Pick<\n SnapshotWidgetCapabilityGrant,\n \"widgetType\" | \"widgetId\" | \"packageId\" | \"capability\"\n >,\n): string {\n return `${grant.widgetType}\\0${grant.widgetId}\\0${grant.packageId}\\0${grant.capability}`;\n}\n\nfunction sameGrant(\n actual: SnapshotWidgetCapabilityGrant,\n expected: SnapshotWidgetCapabilityGrant,\n): boolean {\n return (\n actual.packageId === expected.packageId &&\n actual.packageVersion === expected.packageVersion &&\n actual.capabilityVersion === expected.capabilityVersion\n );\n}\n\nfunction writeGrant(\n value: unknown,\n grant: SnapshotWidgetCapabilityGrant,\n): boolean {\n if (!value || typeof value !== \"object\") return false;\n if (Array.isArray(value)) {\n return value.some((entry) => writeGrant(entry, grant));\n }\n const record = value as Record<string, unknown>;\n if (record.id === grant.widgetId && record.type === grant.widgetType) {\n const grants =\n record.capabilityGrants && typeof record.capabilityGrants === \"object\"\n ? (record.capabilityGrants as Record<string, unknown>)\n : {};\n record.capabilityGrants = {\n ...grants,\n [grant.capability]: {\n packageId: grant.packageId,\n packageVersion: grant.packageVersion,\n capabilityVersion: grant.capabilityVersion,\n },\n };\n return true;\n }\n return Object.values(record).some((entry) => writeGrant(entry, grant));\n}\n","/**\n * `fluid portal push` command\n *\n * Pushes local portal content changes to the Fluid OS API.\n * Detects changes since the last pull/push via snapshot diffing,\n * validates cross-references, and pushes resources in dependency order.\n */\n\nimport { Command } from \"commander\";\nimport type { ChangeEntry, PluginContext } from \"@fluid-app/fluid-cli\";\nimport {\n getAuthToken,\n getActiveProfile,\n gitSyncCommitSubject,\n subjectFragment,\n summarizeChanges,\n} from \"@fluid-app/fluid-cli\";\nimport type { FluidOsNavigationItem } from \"@fluid-app/fluidos-api-client/types\";\nimport type { PortalWidgetNode } from \"@fluid-app/portal-definition-schema\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { join } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport prompts from \"prompts\";\nimport {\n createFluidOsClient,\n fluidOsApi,\n toFluidOsDynamicObject,\n type FluidOsClient,\n} from \"../api/fluidos.js\";\n\nimport {\n readMappings,\n writeMappings,\n updateMapping,\n removeMapping,\n resolveSlugToId,\n} from \"../utils/mappings.js\";\nimport type { PortalMappings } from \"../utils/mappings.js\";\nimport {\n readSnapshot,\n diffAgainstSnapshot,\n writeSnapshot,\n computeFileHash,\n collectSnapshotWidgetCapabilityGrants,\n collectWidgetCapabilityGrants,\n selectLegacyNetworkWidgetGrants,\n} from \"../utils/snapshot.js\";\nimport type { SnapshotDiff } from \"../utils/snapshot.js\";\nimport type {\n LocalScreen,\n LocalTheme,\n LocalNavigation,\n LocalNavigationItem,\n LocalProfile,\n} from \"../utils/transform.js\";\nimport {\n buildIdToSlugMap,\n transformNavigationItems,\n} from \"../utils/transform.js\";\nimport {\n categorizeChanges,\n validateCrossReferences,\n validatePortalSchemas,\n validateScreenStructure,\n slugFromPath,\n} from \"../utils/push-validation.js\";\nimport type { CategorizedChanges } from \"../utils/push-validation.js\";\nimport { atomicWriteFile } from \"../utils/atomic-write.js\";\nimport { runPortalGitSync } from \"../lib/git-sync.js\";\nimport { fetchPortalActor } from \"../api/me.js\";\nimport {\n isWidgetCapabilityApprovedByPushFlags,\n prepareWidgetCapabilityPush,\n persistApprovedWidgetCapabilityPushGrants,\n} from \"../utils/widget-capability-grants.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Derived from the generated FluidOSNavigationItem but with `id` optional\n * (new items don't have one yet) and `label`/`position` required (needed\n * for create/update payloads).\n */\ntype NavigationSyncItem = Omit<\n FluidOsNavigationItem,\n \"id\" | \"label\" | \"position\" | \"children\"\n> & {\n id?: number;\n label: string;\n position: number;\n children?: NavigationSyncItem[];\n parent_id?: number | null;\n};\n\ntype ApiNavigationItem = FluidOsNavigationItem;\n\ntype FlattenedNavigationItem = Omit<NavigationSyncItem, \"children\"> & {\n /** Pre-order index of the item's structural parent in the local tree. */\n readonly treeParentIndex?: number;\n};\n\ninterface NavigationItemCreateShape {\n label: string;\n position: number;\n icon: string | undefined;\n screen_id: number | undefined;\n slug: string | undefined;\n source: ApiNavigationItem[\"source\"];\n parent_id: number | null;\n}\n\nclass NavigationRemoteStateUnknownError extends Error {\n override readonly name = \"NavigationRemoteStateUnknownError\";\n}\n\ninterface PushOptions {\n yes?: boolean;\n activate?: boolean;\n /** Subject for the invisible-git auto-commit. Overrides the derived\n * change summary — an intent is something no diff can recover. */\n message?: string;\n allowNetworkWidgets?: boolean;\n allowWidgetCapabilities?: boolean;\n}\n\n/** Result of a single push operation. */\ninterface PushResult {\n readonly file: string;\n readonly action: \"created\" | \"updated\" | \"deleted\";\n readonly success: boolean;\n readonly error?: string;\n /**\n * All intended remote mutations completed, so snapshot the current local\n * file even though post-push reconciliation failed. This prevents a retry\n * from replaying non-idempotent creates; the error directs the user to pull.\n */\n readonly advanceSnapshot?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PORTAL_DIR = \"portal\";\nconst PORTAL_SYNC_DIR = \".portal-sync\";\n\n/**\n * Convert the local array-form component_tree back to the object\n * the API expects. The pull command normalizes the API object into\n * an array for local convenience; this reverses that transformation.\n */\nfunction toApiComponentTree(\n tree: readonly PortalWidgetNode[],\n): Record<string, unknown> | null {\n if (tree.length === 0) return null;\n if (tree.length === 1) return { ...tree[0]! };\n // Fallback: wrap multiple roots in a container (shouldn't happen in practice)\n return { children: tree };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create an authenticated FluidOsClient using the stored CLI profile.\n */\nfunction createClient(): FluidOsClient {\n const token = getAuthToken();\n if (!token) {\n const profile = getActiveProfile();\n if (!profile) {\n throw new Error(\n \"Not logged in. Run \" + chalk.cyan(\"fluid login\") + \" first.\",\n );\n }\n throw new Error(\n \"No auth token found for profile \" +\n chalk.cyan(profile.name) +\n \". Run \" +\n chalk.cyan(\"fluid login\") +\n \" to re-authenticate.\",\n );\n }\n\n const baseUrl = process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n\n return createFluidOsClient({\n baseUrl,\n token,\n });\n}\n\nasync function createAndActivateVersion(\n client: FluidOsClient,\n definitionId: number,\n): Promise<string> {\n const result = await fluidOsApi.createVersion(client, definitionId);\n const versionId = result.version?.id;\n if (!versionId) {\n throw new Error(\"the version API returned no version ID\");\n }\n await fluidOsApi.updateVersion(client, definitionId, versionId, {\n version: { active: true },\n });\n return String(versionId);\n}\n\nasync function activateDraft(\n definitionId: number,\n existingClient?: FluidOsClient,\n): Promise<boolean> {\n const spinner = ora(\"Creating and activating a live version...\");\n spinner.start();\n try {\n const versionId = await createAndActivateVersion(\n existingClient ?? createClient(),\n definitionId,\n );\n spinner.succeed(`Live version ${versionId} activated`);\n return true;\n } catch (err) {\n spinner.fail(\"Could not activate a live version\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exitCode = 1;\n return false;\n }\n}\n\n/**\n * Extract an enriched error message from a caught value.\n * Includes structured API error data when available.\n */\nfunction enrichedErrorMessage(err: unknown): string {\n let msg = err instanceof Error ? err.message : String(err);\n if (err && typeof err === \"object\" && \"data\" in err) {\n msg += ` — ${JSON.stringify((err as { data: unknown }).data)}`;\n }\n return msg;\n}\n\nfunction navigationPushFailure(\n err: unknown,\n remoteChangesComplete: boolean,\n): Pick<PushResult, \"error\" | \"advanceSnapshot\"> {\n const error = enrichedErrorMessage(err);\n if (err instanceof NavigationRemoteStateUnknownError) {\n return { advanceSnapshot: true, error };\n }\n if (!remoteChangesComplete) return { error };\n\n return {\n advanceSnapshot: true,\n error: `Remote changes were applied before the push failed: ${error}. Run fluid portal pull before editing or pushing again.`,\n };\n}\n\n/**\n * Best-effort \"invisible git\" sync: commit the local project and push it\n * to the portal's provisioned GitHub repo before the content push.\n * Degradable failures (no git, endpoint missing, diverged) are reported\n * as a dim note and the content push proceeds. Returns false — with a\n * clean error, never a thrown stack — when local git state was mutated\n * but the push failed, so the caller aborts the content push.\n */\nasync function runInvisibleGitSync(\n cwd: string,\n definitionId: number,\n definitionName: string,\n explicitMessage?: string,\n): Promise<boolean> {\n const token = getAuthToken();\n if (!token) return true; // content push reports the auth problem clearly.\n const baseUrl = process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n // Canonical GitSync subject — same shape as mist / theme / widget, so\n // one `git log` across a company's repos reads as one system and every\n // commit says who made it. Actor lookup is best-effort: an unreachable\n // /api/me falls back to a `Fluid:` system commit rather than blocking\n // the publish.\n //\n // Precedence for the action: an explicit `-m` states the INTENT, which\n // no diff can recover; otherwise describe what actually changed; the\n // portal-name constant is the last resort for an unreadable diff.\n // Start the identity lookup but DON'T await it here: the git\n // reconcile below (credential mint, fetch, fast-forward) is the slow\n // part, and awaiting up front would add /api/me's latency to the push\n // serially. The resolver awaits it at commit time instead, by which\n // point it has almost always resolved. It is bounded (5s) and\n // best-effort — a publish must never hang describing itself.\n const actorPromise = fetchPortalActor(baseUrl, token);\n const fallback = `Publish portal ${subjectFragment(definitionName)}`;\n const explicit = explicitMessage?.trim();\n const commitMessage = async (\n changes: readonly ChangeEntry[],\n ): Promise<string> =>\n gitSyncCommitSubject(\n explicit || summarizeChanges(changes) || fallback,\n await actorPromise,\n );\n\n const spinner = ora(\"Syncing project to git…\").start();\n const result = await runPortalGitSync({\n cwd,\n baseUrl,\n token,\n definitionId,\n commitMessage,\n });\n if (result.status === \"synced\") {\n spinner.succeed(\"Synced project to git\");\n } else if (result.status === \"failed\") {\n // Local git state was mutated but the push didn't land — abort\n // before the content push so published artifacts can't drift from\n // unsynced source (same contract as `fluid widget push`).\n spinner.fail(\"Git sync failed\");\n // Clean message, no stack trace: this is a user-facing outcome, not\n // a crash.\n console.error(chalk.red(result.reason));\n console.log();\n return false;\n } else {\n spinner.info(chalk.gray(`Skipped git sync — ${result.reason}`));\n }\n console.log();\n return true;\n}\n\n/**\n * Read and parse a JSON file from the portal directory.\n */\nasync function readPortalFile<T>(\n portalDir: string,\n relativePath: string,\n): Promise<T> {\n const filePath = join(portalDir, relativePath);\n const content = await readFile(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Resource push functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Push screen changes to the API.\n */\nasync function pushScreens(\n client: FluidOsClient,\n defId: number,\n portalDir: string,\n changes: CategorizedChanges[\"screens\"],\n mappings: PortalMappings,\n): Promise<{ results: PushResult[]; mappings: PortalMappings }> {\n const results: PushResult[] = [];\n let currentMappings = mappings;\n\n // Create new screens\n for (const file of changes.new) {\n const slug = slugFromPath(file);\n try {\n const local = await readPortalFile<LocalScreen>(portalDir, file);\n const response = await fluidOsApi.createScreen(client, defId, {\n screen: {\n name: local.name,\n slug,\n component_tree: toFluidOsDynamicObject(\n toApiComponentTree(local.component_tree),\n ),\n },\n });\n const newId = response.screen?.id;\n if (newId != null) {\n currentMappings = updateMapping(\n currentMappings,\n \"screens\",\n slug,\n newId,\n );\n }\n results.push({ file, action: \"created\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"created\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Update changed screens\n for (const file of changes.changed) {\n const slug = slugFromPath(file);\n const screenId = resolveSlugToId(currentMappings, \"screens\", slug);\n if (screenId == null) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: `No mapping found for screen slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n const local = await readPortalFile<LocalScreen>(portalDir, file);\n await fluidOsApi.updateScreen(client, defId, screenId, {\n screen: {\n name: local.name,\n slug,\n component_tree: toFluidOsDynamicObject(\n toApiComponentTree(local.component_tree),\n ),\n },\n });\n results.push({ file, action: \"updated\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Delete screens\n for (const file of changes.deleted) {\n const slug = slugFromPath(file);\n const screenId = resolveSlugToId(currentMappings, \"screens\", slug);\n if (screenId == null) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: `No mapping found for screen slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n await fluidOsApi.deleteScreen(client, defId, screenId);\n currentMappings = removeMapping(currentMappings, \"screens\", slug);\n results.push({ file, action: \"deleted\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n return { results, mappings: currentMappings };\n}\n\n/**\n * Push theme changes to the API.\n */\nasync function pushThemes(\n client: FluidOsClient,\n defId: number,\n portalDir: string,\n changes: CategorizedChanges[\"themes\"],\n mappings: PortalMappings,\n): Promise<{ results: PushResult[]; mappings: PortalMappings }> {\n const results: PushResult[] = [];\n let currentMappings = mappings;\n\n // Create new themes\n for (const file of changes.new) {\n const slug = slugFromPath(file);\n try {\n const local = await readPortalFile<LocalTheme>(portalDir, file);\n const response = await fluidOsApi.createTheme(client, defId, {\n theme: {\n name: local.name,\n active: local.active,\n config: toFluidOsDynamicObject({ ...local.config }),\n },\n });\n const newId = response.theme?.id;\n if (newId != null) {\n currentMappings = updateMapping(currentMappings, \"themes\", slug, newId);\n }\n results.push({ file, action: \"created\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"created\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Update changed themes\n for (const file of changes.changed) {\n const slug = slugFromPath(file);\n const themeId = resolveSlugToId(currentMappings, \"themes\", slug);\n if (themeId == null) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: `No mapping found for theme slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n const local = await readPortalFile<LocalTheme>(portalDir, file);\n await fluidOsApi.updateTheme(client, defId, themeId, {\n theme: {\n name: local.name,\n active: local.active,\n config: toFluidOsDynamicObject({ ...local.config }),\n },\n });\n results.push({ file, action: \"updated\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Delete themes\n for (const file of changes.deleted) {\n const slug = slugFromPath(file);\n const themeId = resolveSlugToId(currentMappings, \"themes\", slug);\n if (themeId == null) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: `No mapping found for theme slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n await fluidOsApi.deleteTheme(client, defId, themeId);\n currentMappings = removeMapping(currentMappings, \"themes\", slug);\n results.push({ file, action: \"deleted\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n return { results, mappings: currentMappings };\n}\n\n/**\n * Resolve screen slug references to screen IDs in navigation items.\n * Returns a new tree with `screen_id` instead of `screen` slug,\n * shaped to match the FluidOSNavigationItemSyncItem schema.\n */\nfunction resolveNavigationItemScreenIds(\n items: LocalNavigationItem[],\n mappings: PortalMappings,\n): NavigationSyncItem[] {\n return items.map((item) => {\n const screenId = item.screen\n ? (resolveSlugToId(mappings, \"screens\", item.screen) ?? undefined)\n : undefined;\n const result: NavigationSyncItem = {\n ...(item.id ? { id: item.id } : {}),\n label: item.label ?? \"\",\n position: item.position ?? 0,\n icon: item.icon,\n screen_id: screenId ?? null,\n slug: item.slug,\n source: (item.source as \"user\" | \"system\" | \"code\") ?? \"user\",\n parent_id: item.parent_id,\n children: resolveNavigationItemScreenIds(item.children ?? [], mappings),\n };\n return result;\n });\n}\n\n/**\n * Flatten a tree of navigation sync items into a flat list.\n * The API reconciliation logic requires a flat list to correctly\n * compare against the flat server response.\n */\nfunction flattenNavigationItems(\n items: readonly NavigationSyncItem[],\n treeParentIndex?: number,\n flat: FlattenedNavigationItem[] = [],\n): FlattenedNavigationItem[] {\n for (const item of items) {\n const { children, ...rest } = item;\n const itemIndex = flat.length;\n flat.push({ ...rest, treeParentIndex });\n if (children && children.length > 0) {\n flattenNavigationItems(children, itemIndex, flat);\n }\n }\n return flat;\n}\n\n/**\n * The navigation-items index returns root items with descendants nested under\n * `children`. Flatten descendants first so removals cannot orphan or revisit a\n * child when its parent is also deleted.\n */\nfunction flattenServerNavigationItems(\n items: readonly ApiNavigationItem[],\n flat: ApiNavigationItem[] = [],\n): ApiNavigationItem[] {\n for (const item of items) {\n flattenServerNavigationItems(item.children ?? [], flat);\n flat.push(item);\n }\n return flat;\n}\n\nfunction resolveNavigationItemParentId(\n item: FlattenedNavigationItem,\n serverIdByItemIndex: ReadonlyMap<number, number>,\n): number | null {\n if (item.treeParentIndex != null) {\n const serverParentId = serverIdByItemIndex.get(item.treeParentIndex);\n if (serverParentId == null) {\n throw new Error(\n \"Failed to resolve the server ID for a nested navigation item's parent\",\n );\n }\n return serverParentId;\n }\n\n // The canonical local format is a tree. A pulled child retains its previous\n // parent_id as server metadata, so structural root placement must clear that\n // stale relationship instead of silently preserving it.\n return null;\n}\n\nasync function resolveCreatedNavigationItemId(\n client: FluidOsClient,\n defId: number,\n navigationId: number,\n responseId: number | undefined,\n knownServerIds: Set<number>,\n requestedItem: NavigationItemCreateShape,\n file: string,\n): Promise<number> {\n if (responseId != null) {\n knownServerIds.add(responseId);\n return responseId;\n }\n\n // A successful response must include the new ID. If that contract is ever\n // violated, recover only when a fresh authoritative list has exactly one\n // previously unseen item matching the request; concurrent writes make any\n // other result unsafe to guess from.\n let response: Awaited<ReturnType<typeof fluidOsApi.listNavigationItems>>;\n try {\n response = await fluidOsApi.listNavigationItems(\n client,\n defId,\n navigationId,\n );\n } catch (recoveryError) {\n throw new NavigationRemoteStateUnknownError(\n `Remote navigation state is unknown because item creation succeeded without returning its required ID, and the recovery read failed: ${enrichedErrorMessage(recoveryError)}. Back up ${file}, run fluid portal pull, then reapply pending edits before pushing again.`,\n );\n }\n const unseenItems = flattenServerNavigationItems(\n response.navigation_items ?? [],\n ).filter((item) => !knownServerIds.has(item.id));\n const matchingItems = unseenItems.filter(\n (item) =>\n (item.label ?? \"\") === requestedItem.label &&\n (item.position ?? 0) === requestedItem.position &&\n (item.icon ?? undefined) === requestedItem.icon &&\n (item.screen_id ?? undefined) === requestedItem.screen_id &&\n (item.slug ?? undefined) === requestedItem.slug &&\n (item.source ?? \"user\") === (requestedItem.source ?? \"user\") &&\n (item.parent_id ?? null) === (requestedItem.parent_id ?? null),\n );\n\n if (unseenItems.length !== 1 || matchingItems.length !== 1) {\n throw new NavigationRemoteStateUnknownError(\n `Remote navigation state is unknown because item creation succeeded without returning its required ID, and recovery found ${matchingItems.length} matching items among ${unseenItems.length} new server items. Back up ${file}, run fluid portal pull, then reapply pending edits before pushing again.`,\n );\n }\n\n const recoveredId = matchingItems[0]?.id;\n if (recoveredId == null) {\n throw new NavigationRemoteStateUnknownError(\n `Remote navigation state is unknown because item creation succeeded without returning its required ID. Back up ${file}, run fluid portal pull, then reapply pending edits before pushing again.`,\n );\n }\n knownServerIds.add(recoveredId);\n return recoveredId;\n}\n\nfunction assignNavigationItemId(\n navigation: LocalNavigation,\n targetIndex: number,\n serverId: number,\n): LocalNavigation {\n // Resolved API items and local items use the same pre-order traversal. Keep\n // the rest of the desired tree intact so a partial push can retry safely.\n let currentIndex = 0;\n let previousId: number | undefined;\n\n function assign(items: LocalNavigationItem[]): LocalNavigationItem[] {\n return items.map((item) => {\n const itemIndex = currentIndex++;\n const children = assign(item.children ?? []);\n if (itemIndex !== targetIndex) return { ...item, children };\n\n previousId = item.id;\n return { ...item, id: serverId, children };\n });\n }\n\n function rewriteParentIds(\n items: LocalNavigationItem[],\n ): LocalNavigationItem[] {\n return items.map((item) => ({\n ...item,\n parent_id: item.parent_id === previousId ? serverId : item.parent_id,\n children: rewriteParentIds(item.children ?? []),\n }));\n }\n\n const navigationItems = assign(navigation.navigation_items);\n return {\n ...navigation,\n navigation_items:\n previousId == null || previousId === serverId\n ? navigationItems\n : rewriteParentIds(navigationItems),\n };\n}\n\nasync function persistCreatedNavigationItemId(\n client: FluidOsClient,\n defId: number,\n navigationId: number,\n portalDir: string,\n file: string,\n navigation: LocalNavigation,\n itemIndex: number,\n serverId: number,\n): Promise<LocalNavigation> {\n const updated = assignNavigationItemId(navigation, itemIndex, serverId);\n try {\n await atomicWriteFile(\n join(portalDir, file),\n JSON.stringify(updated, null, 2) + \"\\n\",\n );\n } catch (persistError) {\n try {\n await fluidOsApi.deleteNavigationItem(\n client,\n defId,\n navigationId,\n serverId,\n );\n } catch (rollbackError) {\n throw new NavigationRemoteStateUnknownError(\n `Failed to persist navigation item ${serverId} locally: ${enrichedErrorMessage(persistError)}. Automatic rollback also failed: ${enrichedErrorMessage(rollbackError)}. Back up ${file}, run fluid portal pull, then reapply pending edits before pushing again.`,\n );\n }\n throw persistError;\n }\n return updated;\n}\n\n/**\n * Replace a pushed navigation file with the authoritative server tree.\n * New navigation items receive their IDs from the server, so the local file\n * must be reconciled before the push snapshot hashes it.\n */\nasync function reconcileNavigationFile(\n client: FluidOsClient,\n defId: number,\n navId: number,\n portalDir: string,\n file: string,\n local: LocalNavigation,\n mappings: PortalMappings,\n): Promise<void> {\n const response = await fluidOsApi.listNavigationItems(client, defId, navId);\n const reconciled: LocalNavigation = {\n ...local,\n navigation_items: transformNavigationItems(\n response.navigation_items ?? [],\n buildIdToSlugMap(mappings.screens),\n ),\n };\n\n const filePath = join(portalDir, file);\n await atomicWriteFile(filePath, JSON.stringify(reconciled, null, 2) + \"\\n\");\n}\n\n/**\n * Push navigation changes to the API.\n */\nexport async function pushNavigations(\n client: FluidOsClient,\n defId: number,\n portalDir: string,\n changes: CategorizedChanges[\"navigations\"],\n mappings: PortalMappings,\n): Promise<{ results: PushResult[]; mappings: PortalMappings }> {\n const results: PushResult[] = [];\n let currentMappings = mappings;\n // A prior create may have persisted its navigation mapping before a later\n // item failed. Treat that still-snapshot-new file as an update on retry so\n // the remote navigation itself is not created twice.\n const newFiles = changes.new.filter(\n (file) =>\n resolveSlugToId(currentMappings, \"navigations\", slugFromPath(file)) ==\n null,\n );\n const changedFiles = [\n ...changes.changed,\n ...changes.new.filter(\n (file) =>\n resolveSlugToId(currentMappings, \"navigations\", slugFromPath(file)) !=\n null,\n ),\n ];\n\n // Create new navigations\n for (const file of newFiles) {\n const slug = slugFromPath(file);\n let remoteChangesComplete = false;\n try {\n let local = await readPortalFile<LocalNavigation>(portalDir, file);\n const response = await fluidOsApi.createNavigation(client, defId, {\n navigation: {\n name: local.name,\n platform: local.platform as \"web\" | \"mobile\",\n },\n });\n const newId = response.navigation?.id;\n if (newId != null) {\n currentMappings = updateMapping(\n currentMappings,\n \"navigations\",\n slug,\n newId,\n );\n // Create navigation items individually (flatten tree for API).\n // Track each tree item's resolved server ID so descendants reference\n // their structural parent's authoritative ID.\n const resolvedItems = flattenNavigationItems(\n resolveNavigationItemScreenIds(\n local.navigation_items,\n currentMappings,\n ),\n );\n const serverIdByItemIndex = new Map<number, number>();\n const knownServerIds = new Set<number>();\n\n for (const [itemIndex, item] of resolvedItems.entries()) {\n const resolvedParentId = resolveNavigationItemParentId(\n item,\n serverIdByItemIndex,\n );\n\n const createBody: NavigationItemCreateShape = {\n label: item.label ?? \"\",\n position: item.position ?? 0,\n icon: item.icon ?? undefined,\n screen_id: item.screen_id ?? undefined,\n slug: item.slug ?? undefined,\n source: item.source ?? undefined,\n parent_id: resolvedParentId,\n };\n const created = await fluidOsApi.createNavigationItem(\n client,\n defId,\n newId,\n {\n navigation_item: createBody,\n },\n );\n\n const createdId = await resolveCreatedNavigationItemId(\n client,\n defId,\n newId,\n created.navigation_item?.id,\n knownServerIds,\n createBody,\n file,\n );\n local = await persistCreatedNavigationItemId(\n client,\n defId,\n newId,\n portalDir,\n file,\n local,\n itemIndex,\n createdId,\n );\n serverIdByItemIndex.set(itemIndex, createdId);\n }\n\n remoteChangesComplete = true;\n await reconcileNavigationFile(\n client,\n defId,\n newId,\n portalDir,\n file,\n local,\n currentMappings,\n );\n }\n results.push({ file, action: \"created\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"created\",\n success: false,\n ...navigationPushFailure(err, remoteChangesComplete),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Update changed navigations\n for (const file of changedFiles) {\n const slug = slugFromPath(file);\n const navId = resolveSlugToId(currentMappings, \"navigations\", slug);\n if (navId == null) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: `No mapping found for navigation slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n let remoteChangesComplete = false;\n try {\n let local = await readPortalFile<LocalNavigation>(portalDir, file);\n\n // Update navigation metadata\n await fluidOsApi.updateNavigation(client, defId, navId, {\n navigation: {\n name: local.name,\n platform: local.platform as \"web\" | \"mobile\",\n },\n });\n\n // Reconcile navigation items via individual CRUD operations.\n // The bulk sync endpoint is unreliable, so we diff local vs server\n // and issue create/update/delete calls like the admin builder does.\n // Flatten the tree since the API returns/expects a flat list.\n const resolvedItems = flattenNavigationItems(\n resolveNavigationItemScreenIds(local.navigation_items, currentMappings),\n );\n\n const serverResponse = await fluidOsApi.listNavigationItems(\n client,\n defId,\n navId,\n );\n const serverItems = flattenServerNavigationItems(\n serverResponse.navigation_items ?? [],\n );\n const serverById = new Map(serverItems.map((s) => [s.id, s]));\n const knownServerIds = new Set(serverById.keys());\n const localIds = new Set(\n resolvedItems.filter((i) => i.id).map((i) => i.id),\n );\n\n // Create or update local items.\n // Track each tree item's resolved server ID so descendants reference\n // their structural parent's authoritative ID.\n const serverIdByItemIndex = new Map<number, number>();\n\n for (const [itemIndex, item] of resolvedItems.entries()) {\n const resolvedParentId = resolveNavigationItemParentId(\n item,\n serverIdByItemIndex,\n );\n\n const body = {\n label: item.label,\n position: item.position,\n icon: item.icon ?? undefined,\n screen_id: item.screen_id ?? undefined,\n slug: item.slug ?? undefined,\n source: item.source ?? undefined,\n parent_id: resolvedParentId,\n };\n\n if (item.id && serverById.has(item.id)) {\n // Update existing\n await fluidOsApi.updateNavigationItem(client, defId, navId, item.id, {\n navigation_item: body,\n });\n serverIdByItemIndex.set(itemIndex, item.id);\n } else {\n // Create new\n const createBody: NavigationItemCreateShape = {\n ...body,\n label: body.label ?? \"\",\n position: body.position ?? 0,\n };\n const created = await fluidOsApi.createNavigationItem(\n client,\n defId,\n navId,\n {\n navigation_item: createBody,\n },\n );\n\n const createdId = await resolveCreatedNavigationItemId(\n client,\n defId,\n navId,\n created.navigation_item?.id,\n knownServerIds,\n createBody,\n file,\n );\n local = await persistCreatedNavigationItemId(\n client,\n defId,\n navId,\n portalDir,\n file,\n local,\n itemIndex,\n createdId,\n );\n serverIdByItemIndex.set(itemIndex, createdId);\n }\n }\n\n // Move retained descendants before deleting removed ancestors. Rails\n // cascades navigation-item children, so deleting first could destroy an\n // item that the local tree intends to keep under a new parent.\n for (const serverItem of serverItems) {\n if (!localIds.has(serverItem.id)) {\n await fluidOsApi.deleteNavigationItem(\n client,\n defId,\n navId,\n serverItem.id,\n );\n }\n }\n\n remoteChangesComplete = true;\n await reconcileNavigationFile(\n client,\n defId,\n navId,\n portalDir,\n file,\n local,\n currentMappings,\n );\n\n results.push({ file, action: \"updated\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n ...navigationPushFailure(err, remoteChangesComplete),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Delete navigations\n for (const file of changes.deleted) {\n const slug = slugFromPath(file);\n const navId = resolveSlugToId(currentMappings, \"navigations\", slug);\n if (navId == null) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: `No mapping found for navigation slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n await fluidOsApi.deleteNavigation(client, defId, navId);\n currentMappings = removeMapping(currentMappings, \"navigations\", slug);\n results.push({ file, action: \"deleted\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n return { results, mappings: currentMappings };\n}\n\n/**\n * Push profile changes to the API.\n */\nasync function pushProfiles(\n client: FluidOsClient,\n defId: number,\n portalDir: string,\n changes: CategorizedChanges[\"profiles\"],\n mappings: PortalMappings,\n): Promise<{ results: PushResult[]; mappings: PortalMappings }> {\n const results: PushResult[] = [];\n let currentMappings = mappings;\n\n // Create new profiles\n for (const file of changes.new) {\n const slug = slugFromPath(file);\n try {\n const local = await readPortalFile<LocalProfile>(portalDir, file);\n const body = resolveProfileBody(local, currentMappings);\n const response = await fluidOsApi.createProfile(client, defId, {\n profile: body,\n });\n const newId = response.profile?.id;\n if (newId != null) {\n currentMappings = updateMapping(\n currentMappings,\n \"profiles\",\n slug,\n newId,\n );\n }\n results.push({ file, action: \"created\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"created\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Update changed profiles\n for (const file of changes.changed) {\n const slug = slugFromPath(file);\n const profileId = resolveSlugToId(currentMappings, \"profiles\", slug);\n if (profileId == null) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: `No mapping found for profile slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n const local = await readPortalFile<LocalProfile>(portalDir, file);\n const body = resolveProfileBody(local, currentMappings);\n await fluidOsApi.updateProfile(client, defId, profileId, {\n profile: body,\n });\n results.push({ file, action: \"updated\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"updated\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n // Delete profiles\n for (const file of changes.deleted) {\n const slug = slugFromPath(file);\n const profileId = resolveSlugToId(currentMappings, \"profiles\", slug);\n if (profileId == null) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: `No mapping found for profile slug \"${slug}\"`,\n });\n return { results, mappings: currentMappings };\n }\n try {\n await fluidOsApi.deleteProfile(client, defId, profileId);\n currentMappings = removeMapping(currentMappings, \"profiles\", slug);\n results.push({ file, action: \"deleted\", success: true });\n } catch (err) {\n results.push({\n file,\n action: \"deleted\",\n success: false,\n error: enrichedErrorMessage(err),\n });\n return { results, mappings: currentMappings };\n }\n }\n\n return { results, mappings: currentMappings };\n}\n\n/** Typed profile body for API requests (create and update share the same shape). */\ninterface ProfileBody {\n name: string;\n default?: boolean;\n navigation_id?: number;\n mobile_navigation_id?: number;\n theme_ids?: number[];\n permissions?: {\n countries?: number[];\n ranks?: number[];\n roles?: string[];\n platform?: string[];\n };\n}\n\n/**\n * Resolve profile slug references to API IDs for create/update request body.\n */\nfunction resolveProfileBody(\n local: LocalProfile,\n mappings: PortalMappings,\n): ProfileBody {\n const body: ProfileBody = {\n name: local.name,\n default: local.default,\n permissions: local.permissions,\n };\n\n if (local.navigation) {\n const navId = resolveSlugToId(mappings, \"navigations\", local.navigation);\n if (navId != null) {\n body.navigation_id = navId;\n }\n }\n\n if (local.mobile_navigation) {\n const mobileNavId = resolveSlugToId(\n mappings,\n \"navigations\",\n local.mobile_navigation,\n );\n if (mobileNavId != null) {\n body.mobile_navigation_id = mobileNavId;\n }\n }\n\n const themeIds = local.themes\n .map((slug) => resolveSlugToId(mappings, \"themes\", slug))\n .filter((id): id is number => id != null);\n body.theme_ids = themeIds;\n\n return body;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Print helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction printChangesSummary(diff: SnapshotDiff, definitionName: string): void {\n console.log(\n chalk.blue(\"Changes to push for \") +\n chalk.white.bold(`\"${definitionName}\"`) +\n chalk.blue(\":\"),\n );\n console.log();\n\n if (diff.new.length > 0) {\n console.log(chalk.green(\" New: \") + diff.new.join(\", \"));\n }\n if (diff.changed.length > 0) {\n console.log(chalk.yellow(\" Changed: \") + diff.changed.join(\", \"));\n }\n if (diff.deleted.length > 0) {\n console.log(chalk.red(\" Deleted: \") + diff.deleted.join(\", \"));\n }\n console.log();\n}\n\nfunction printPushReport(results: PushResult[], skippedPhases: string[]): void {\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (succeeded.length > 0) {\n console.log(chalk.green.bold(\"Succeeded:\"));\n for (const r of succeeded) {\n console.log(chalk.green(\" \" + r.action + \": \") + r.file);\n }\n }\n\n if (failed.length > 0) {\n console.log();\n console.log(chalk.red.bold(\"Failed:\"));\n for (const r of failed) {\n console.log(\n chalk.red(\" \" + r.action + \": \") +\n r.file +\n chalk.gray(\" — \" + (r.error ?? \"Unknown error\")),\n );\n }\n }\n\n if (skippedPhases.length > 0) {\n console.log();\n console.log(chalk.yellow.bold(\"Skipped phases:\"));\n for (const phase of skippedPhases) {\n console.log(chalk.yellow(\" \" + phase));\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Command\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const pushCommand: Command = new Command(\"push\")\n .description(\"Push local portal content changes to the Fluid OS API\")\n .option(\"--yes\", \"Skip confirmation prompt\")\n .option(\n \"--allow-network-widgets\",\n \"Approve widgets that newly request external network access\",\n )\n .option(\n \"--allow-widget-capabilities\",\n \"Approve widgets that newly request sensitive capabilities\",\n )\n .option(\n \"--activate\",\n \"Create and activate a live version after a successful push\",\n )\n .option(\n \"-m, --message <message>\",\n \"Commit message for the git auto-commit (default: a summary of what changed)\",\n )\n .action(async (options: PushOptions) => {\n const cwd = process.cwd();\n const portalDir = join(cwd, PORTAL_DIR);\n const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Portal Push\"));\n console.log();\n\n // ── Check for portal directory ─────────────────────────────────────\n if (!existsSync(portalDir)) {\n console.log(\n chalk.red(\"Error:\") +\n \" No portal/ directory found. Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" first.\",\n );\n console.log();\n process.exit(1);\n }\n\n // ── Read snapshot ──────────────────────────────────────────────────\n const snapshot = await readSnapshot(portalSyncDir);\n if (!snapshot) {\n console.log(\n chalk.red(\"Error:\") +\n \" No snapshot found in .portal-sync/. Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" first.\",\n );\n console.log();\n process.exit(1);\n }\n\n // ── Read mappings ──────────────────────────────────────────────────\n const mappings = await readMappings(portalSyncDir);\n if (!mappings) {\n console.log(\n chalk.red(\"Error:\") +\n \" No mappings found in .portal-sync/. Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" first.\",\n );\n console.log();\n process.exit(1);\n }\n\n const definitionId = snapshot.definition_id;\n const definitionName = snapshot.definition;\n\n console.log(\n chalk.gray(\"Definition: \") +\n chalk.white(definitionName) +\n chalk.gray(` (ID: ${definitionId})`),\n );\n console.log();\n\n // ── Invisible git sync (before the content push) ────────────────────\n // Mirror the local project to its provisioned GitHub repo. Degradable\n // problems — no git / no repo / endpoint missing (404) / diverged\n // history — degrade to a dim note and the content push proceeds. The\n // one fatal case is a rejected push AFTER local git state was mutated:\n // publishing then would ship content whose source never reached the\n // repo, so we stop here (clean error already printed).\n if (\n !(await runInvisibleGitSync(\n cwd,\n definitionId,\n definitionName,\n options.message,\n ))\n ) {\n process.exitCode = 1;\n return;\n }\n\n // ── Detect changes ─────────────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Detecting changes...\");\n\n const diff = await diffAgainstSnapshot(portalDir, snapshot);\n const totalChanges =\n diff.new.length + diff.changed.length + diff.deleted.length;\n\n if (totalChanges === 0) {\n spinner.succeed(\"Nothing to push.\");\n console.log();\n if (options.activate) {\n await activateDraft(definitionId);\n console.log();\n }\n return;\n }\n\n spinner.succeed(`Found ${totalChanges} change(s)`);\n console.log();\n\n // ── Show changes and confirm ───────────────────────────────────────\n printChangesSummary(diff, definitionName);\n\n if (!options.yes) {\n const { confirmed } = await prompts({\n type: \"confirm\",\n name: \"confirmed\",\n message: `Push ${totalChanges} change(s) to Fluid OS?`,\n initial: false,\n });\n\n if (!confirmed) {\n console.log();\n console.log(chalk.gray(\"Push cancelled.\"));\n console.log();\n return;\n }\n console.log();\n }\n\n // ── Categorize changes ─────────────────────────────────────────────\n const changes = categorizeChanges(diff);\n\n // ── Cross-reference validation ─────────────────────────────────────\n spinner.start(\"Validating portal files...\");\n const schemaErrors = await validatePortalSchemas(portalDir);\n const validationErrors =\n schemaErrors.length > 0\n ? schemaErrors\n : await validateCrossReferences(portalDir, mappings, changes);\n\n if (validationErrors.length > 0) {\n spinner.fail(\"Portal file validation failed\");\n console.log();\n for (const err of validationErrors) {\n console.log(chalk.red(\" \" + err.file + \": \") + err.message);\n }\n console.log();\n process.exit(1);\n }\n spinner.succeed(\"Portal files valid\");\n\n // Advisory only — these screens push and render fine, but the admin\n // builder will not offer drop zones on them. Reported after the pass\n // message so it reads as a note rather than a failure.\n const structureWarnings = await validateScreenStructure(portalDir, changes);\n if (structureWarnings.length > 0) {\n console.log();\n for (const warning of structureWarnings) {\n console.log(\n chalk.yellow(\" warning \") +\n chalk.white(warning.file + \": \") +\n warning.message,\n );\n }\n console.log();\n }\n\n // ── Authenticate ───────────────────────────────────────────────────\n spinner.start(\"Authenticating...\");\n\n let client: FluidOsClient;\n try {\n client = createClient();\n spinner.succeed(\"Authenticated\");\n } catch (err) {\n spinner.fail(\"Authentication failed\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Dedicated sensitive-capability approval ───────────────────────\n const changedScreenFiles = [\n ...changes.screens.new,\n ...changes.screens.changed,\n ];\n const hasWidgetCapabilityGrants = (\n await Promise.all(\n changedScreenFiles.map(async (file) =>\n collectWidgetCapabilityGrants(\n JSON.parse(\n await readFile(join(portalDir, file), \"utf8\"),\n ) as unknown,\n ),\n ),\n )\n ).some((grants) => grants.length > 0);\n if (hasWidgetCapabilityGrants) {\n try {\n const catalog = await fluidOsApi.listWidgetPackages(client);\n const newlyEnabled = await prepareWidgetCapabilityPush({\n portalDir,\n screenFiles: changedScreenFiles,\n baseline: snapshot.widget_capability_grants,\n catalogCandidates: catalog.packages,\n });\n if (newlyEnabled.length > 0) {\n console.log();\n console.log(chalk.yellow.bold(\"Widgets requesting capabilities:\"));\n for (const grant of newlyEnabled) {\n console.log(\n ` ${chalk.white(grant.widgetType)} ${chalk.yellow(grant.capability)} ${chalk.cyan(`${grant.packageId}@${grant.packageVersion}`)} ${chalk.gray(`${grant.file} (${grant.widgetId})`)}`,\n );\n }\n console.log();\n\n const needsApproval = newlyEnabled.some(\n ({ capability }) =>\n !isWidgetCapabilityApprovedByPushFlags(capability, options),\n );\n if (needsApproval) {\n if (!process.stdin.isTTY) {\n throw new Error(\n \"New sensitive widget capabilities require --allow-widget-capabilities in a non-interactive session. --yes does not approve capabilities; --allow-network-widgets approves only networkAccess.\",\n );\n }\n const { approved } = await prompts({\n type: \"confirm\",\n name: \"approved\",\n message:\n \"Allow all listed widget capabilities for these package versions?\",\n initial: false,\n });\n if (!approved) {\n console.log();\n console.log(chalk.gray(\"Push cancelled.\"));\n console.log();\n return;\n }\n console.log();\n }\n await persistApprovedWidgetCapabilityPushGrants({\n portalDir,\n grants: newlyEnabled,\n });\n }\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.exitCode = 1;\n return;\n }\n }\n\n // ── Phase 1: Screens + Themes (parallel) ───────────────────────────\n const allResults: PushResult[] = [];\n const skippedPhases: string[] = [];\n let currentMappings = mappings;\n let aborted = false;\n\n const hasScreenChanges =\n changes.screens.new.length > 0 ||\n changes.screens.changed.length > 0 ||\n changes.screens.deleted.length > 0;\n const hasThemeChanges =\n changes.themes.new.length > 0 ||\n changes.themes.changed.length > 0 ||\n changes.themes.deleted.length > 0;\n\n if (hasScreenChanges || hasThemeChanges) {\n spinner.start(\"Phase 1: Pushing screens and themes...\");\n\n const phase1Tasks: Promise<{\n results: PushResult[];\n mappings: PortalMappings;\n }>[] = [];\n\n if (hasScreenChanges) {\n phase1Tasks.push(\n pushScreens(\n client,\n definitionId,\n portalDir,\n changes.screens,\n currentMappings,\n ),\n );\n }\n if (hasThemeChanges) {\n phase1Tasks.push(\n pushThemes(\n client,\n definitionId,\n portalDir,\n changes.themes,\n currentMappings,\n ),\n );\n }\n\n const phase1Results = await Promise.all(phase1Tasks);\n\n // Merge mappings from both parallel operations\n // Only apply the resource type each operation actually owns\n for (const result of phase1Results) {\n allResults.push(...result.results);\n }\n if (hasScreenChanges) {\n currentMappings = {\n ...currentMappings,\n screens: phase1Results[0]!.mappings.screens,\n };\n }\n if (hasThemeChanges) {\n const idx = hasScreenChanges ? 1 : 0;\n currentMappings = {\n ...currentMappings,\n themes: phase1Results[idx]!.mappings.themes,\n };\n }\n\n const phase1Failed = phase1Results.some((r) =>\n r.results.some((res) => !res.success),\n );\n\n if (phase1Failed) {\n spinner.fail(\"Phase 1 failed\");\n aborted = true;\n skippedPhases.push(\"Phase 2: Navigations\", \"Phase 3: Profiles\");\n } else {\n spinner.succeed(\"Phase 1 complete\");\n }\n }\n\n // ── Phase 2: Navigations ───────────────────────────────────────────\n const hasNavChanges =\n changes.navigations.new.length > 0 ||\n changes.navigations.changed.length > 0 ||\n changes.navigations.deleted.length > 0;\n\n if (!aborted && hasNavChanges) {\n spinner.start(\"Phase 2: Pushing navigations...\");\n\n const navResult = await pushNavigations(\n client,\n definitionId,\n portalDir,\n changes.navigations,\n currentMappings,\n );\n\n allResults.push(...navResult.results);\n currentMappings = {\n ...currentMappings,\n navigations: navResult.mappings.navigations,\n };\n\n const phase2Failed = navResult.results.some((r) => !r.success);\n if (phase2Failed) {\n spinner.fail(\"Phase 2 failed\");\n aborted = true;\n skippedPhases.push(\"Phase 3: Profiles\");\n } else {\n spinner.succeed(\"Phase 2 complete\");\n }\n } else if (aborted && hasNavChanges) {\n // Already marked as skipped above\n }\n\n // ── Phase 3: Profiles ──────────────────────────────────────────────\n const hasProfileChanges =\n changes.profiles.new.length > 0 ||\n changes.profiles.changed.length > 0 ||\n changes.profiles.deleted.length > 0;\n\n if (!aborted && hasProfileChanges) {\n spinner.start(\"Phase 3: Pushing profiles...\");\n\n const profileResult = await pushProfiles(\n client,\n definitionId,\n portalDir,\n changes.profiles,\n currentMappings,\n );\n\n allResults.push(...profileResult.results);\n currentMappings = {\n ...currentMappings,\n profiles: profileResult.mappings.profiles,\n };\n\n const phase3Failed = profileResult.results.some((r) => !r.success);\n if (phase3Failed) {\n spinner.fail(\"Phase 3 failed\");\n } else {\n spinner.succeed(\"Phase 3 complete\");\n }\n }\n\n // ── Update mappings ────────────────────────────────────────────────\n await writeMappings(portalSyncDir, currentMappings);\n\n // ── Update snapshot for successfully pushed files ───────────────────\n const filesToSnapshot = new Set(\n allResults\n .filter((r) => r.success || r.advanceSnapshot)\n .map((r) => r.file),\n );\n\n if (filesToSnapshot.size > 0) {\n // Only advance snapshot entries for successfully pushed files;\n // also advance a fully-applied mutation or unknown remote state so an\n // immediate retry cannot replay a create whose response omitted its ID.\n // Other skipped/failed entries retain their original hashes.\n const updatedHashes = { ...snapshot.files };\n for (const file of filesToSnapshot) {\n const fullPath = join(portalDir, file);\n if (existsSync(fullPath)) {\n updatedHashes[file] = await computeFileHash(fullPath);\n } else {\n // deleted file — remove from snapshot\n delete updatedHashes[file];\n }\n }\n const updatedCapabilityGrants = {\n ...snapshot.widget_capability_grants,\n };\n const currentCapabilityGrants =\n await collectSnapshotWidgetCapabilityGrants(\n portalDir,\n [...filesToSnapshot].filter(\n (file) =>\n file.startsWith(\"screens/\") &&\n file.endsWith(\".json\") &&\n existsSync(join(portalDir, file)),\n ),\n );\n for (const file of filesToSnapshot) {\n if (!file.startsWith(\"screens/\") || !file.endsWith(\".json\")) continue;\n const grants = currentCapabilityGrants[file];\n if (grants && grants.length > 0) updatedCapabilityGrants[file] = grants;\n else delete updatedCapabilityGrants[file];\n }\n await writeSnapshot(portalSyncDir, {\n ...snapshot,\n files: updatedHashes,\n widget_capability_grants: updatedCapabilityGrants,\n network_widget_grants: selectLegacyNetworkWidgetGrants(\n updatedCapabilityGrants,\n ),\n });\n }\n\n // ── Report ─────────────────────────────────────────────────────────\n console.log();\n const allSucceeded = allResults.every((r) => r.success);\n\n if (allSucceeded && !aborted) {\n console.log(chalk.green.bold(\"Push complete!\"));\n } else {\n console.log(chalk.yellow.bold(\"Push completed with issues:\"));\n }\n console.log();\n printPushReport(allResults, skippedPhases);\n console.log();\n\n if (!allSucceeded || aborted) {\n process.exitCode = 1;\n return;\n }\n if (options.activate) {\n await activateDraft(definitionId, client);\n console.log();\n }\n });\n\nexport function registerPushCommand(ctx: PluginContext): void {\n ctx.program.addCommand(pushCommand);\n}\n","/**\n * `fluid portal clone [app]` — mints a fresh git credential for a Fluid\n * OS portal definition and clones its provisioned GitHub repo into\n * `./<slug>/`. Mirrors `fluid mist clone` (credential → clone), but\n * against the portal git-credential endpoint.\n *\n * When the credential endpoint reports that no repository is available\n * (the repo was torn down, provisioning failed, it is still not ready\n * after the retry budget, or the endpoint isn't deployed), clone falls\n * back to scaffolding the portal locally from the bundled starter\n * template instead of failing. Local files plus API sync\n * (`fluid portal push`/`pull`) are a fully functional portal — Git is\n * an optional sync channel, not a prerequisite — so the user still\n * lands in a working project and Git history can attach later via\n * `fluid portal push`, which adopts the remote's history.\n */\n\nimport { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { getAuthToken, getActiveProfile } from \"@fluid-app/fluid-cli\";\nimport {\n createFluidOsClient,\n fluidOsApi,\n type FluidOsClient,\n} from \"../api/fluidos.js\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { deriveSlug } from \"../utils/mappings.js\";\nimport { createPortalGitCredentialWithRetry } from \"../api/git-credential.js\";\nimport {\n cleanGitRemoteUrl,\n ensureGitAvailable,\n excludeLocalStateFromGit,\n getOriginUrl,\n gitClone,\n isGitRepo,\n} from \"../lib/git.js\";\nimport { fileExists } from \"../utils/file-system.js\";\nimport {\n PORTAL_SCAFFOLD_PENDING_MARKER,\n scaffoldPortalProjectIntoExistingDirectory,\n} from \"./create.js\";\n\ninterface CloneOptions {\n app?: string;\n dir?: string;\n id?: string;\n}\n\nfunction getBaseUrl(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nasync function removeIncompleteCheckout(\n targetDir: string,\n): Promise<string | null> {\n try {\n await rm(targetDir, { recursive: true, force: true });\n return null;\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n\nfunction createClient(token: string): FluidOsClient {\n return createFluidOsClient({\n baseUrl: getBaseUrl(),\n token,\n });\n}\n\n/**\n * Human message for the git-sync-unavailable fallback, split by conflict\n * type. `not_connected` is a durable state (the company's Git integration\n * isn't connected), so it must not claim that waiting will fix anything;\n * `not_provisioned` genuinely may resolve later, so it says so.\n */\nexport function gitSyncUnavailableNotice(outcome: {\n readonly reason: string;\n readonly conflictType?: string;\n readonly repoStatus?: string;\n}): string {\n const attachHint = `Creating the portal locally from the starter template so you can start now — ${chalk.cyan(\"fluid portal push\")}/${chalk.cyan(\"pull\")} sync content without Git, and Git history can attach later with ${chalk.cyan(\"fluid portal push\")}.`;\n\n if (outcome.repoStatus === \"disconnected\") {\n return `The portal's Git repository was disconnected, so there is nothing to clone. ${attachHint}`;\n }\n if (outcome.repoStatus === \"error\") {\n return `The portal's Git repository failed to provision. ${attachHint}`;\n }\n if (outcome.conflictType === \"not_connected\") {\n // Reached only after the retry budget is exhausted — the repo was still\n // being provisioned the whole time, which is unusual but not an error.\n return (\n \"The portal's Git repository is still being provisioned — it didn't come online in time. \" +\n attachHint\n );\n }\n if (outcome.conflictType === \"not_provisioned\") {\n return `The portal's Git repository isn't provisioned yet. ${attachHint}`;\n }\n return `Git sync isn't available (${outcome.reason}). ${attachHint}`;\n}\n\n/**\n * A git-less fallback scaffold that failed or was interrupted leaves the\n * pending marker behind with no `.git`. Only that exact shape is safe to\n * resume — anything else is an unrelated pre-existing directory.\n */\nexport async function canResumeGitlessPortalScaffold(\n targetDir: string,\n): Promise<boolean> {\n return (\n (await fileExists(join(targetDir, PORTAL_SCAFFOLD_PENDING_MARKER))) &&\n !existsSync(join(targetDir, \".git\"))\n );\n}\n\nexport async function canResumePortalScaffold(\n targetDir: string,\n credentialRemoteUrl: string,\n): Promise<boolean> {\n if (\n !(await fileExists(join(targetDir, PORTAL_SCAFFOLD_PENDING_MARKER))) ||\n !(await isGitRepo(targetDir))\n ) {\n return false;\n }\n const origin = await getOriginUrl(targetDir);\n if (!origin) return false;\n const expected = cleanGitRemoteUrl(credentialRemoteUrl);\n return origin.replace(/\\/$/, \"\") === expected.replace(/\\/$/, \"\");\n}\n\ninterface ResolvedDefinition {\n id: number;\n name: string;\n}\n\ninterface DefinitionSummary {\n id?: number;\n name?: string | null;\n}\n\nexport function resolveDefinition(\n definitions: readonly DefinitionSummary[],\n appName?: string,\n rawDefinitionId?: string,\n): ResolvedDefinition | null {\n if (rawDefinitionId !== undefined) {\n if (!/^[1-9]\\d*$/.test(rawDefinitionId)) {\n throw new Error(\"--id must be a positive integer\");\n }\n const definitionId = Number.parseInt(rawDefinitionId, 10);\n const match = definitions.find(\n (definition) => definition.id === definitionId,\n );\n if (!match) {\n throw new Error(`No definition found with id ${definitionId}.`);\n }\n return {\n id: definitionId,\n name: match.name ?? `portal-${definitionId}`,\n };\n }\n\n if (!appName) return null;\n const match = definitions.find(\n (definition) => definition.name?.toLowerCase() === appName.toLowerCase(),\n );\n if (!match || match.id == null) {\n const available = definitions\n .map((definition) => chalk.cyan(definition.name ?? \"(unnamed)\"))\n .join(\", \");\n throw new Error(\n `No definition found matching \"${appName}\". Available: ${available}`,\n );\n }\n return { id: match.id, name: match.name ?? `portal-${match.id}` };\n}\n\n/** Select a definition by --app name, else interactively. */\nasync function selectDefinition(\n client: FluidOsClient,\n appName?: string,\n rawDefinitionId?: string,\n): Promise<ResolvedDefinition> {\n const definitions: DefinitionSummary[] = [];\n let page = 1;\n while (true) {\n const response = await fluidOsApi.listDefinitions(client, {\n page,\n per_page: 500,\n });\n const batch = response.definitions ?? [];\n definitions.push(...batch);\n const totalPages = response.meta?.total_pages ?? 1;\n if (page >= totalPages || batch.length === 0) break;\n page++;\n }\n\n if (definitions.length === 0) {\n throw new Error(\n \"No Fluid OS definitions found. Create one in the admin dashboard first.\",\n );\n }\n\n const resolved = resolveDefinition(definitions, appName, rawDefinitionId);\n if (resolved) return resolved;\n\n const { definitionId } = await prompts({\n type: \"select\",\n name: \"definitionId\",\n message: \"Select a Fluid OS definition to clone\",\n choices: definitions\n .filter((d) => d.id != null)\n .map((d) => ({ title: d.name ?? \"(unnamed)\", value: d.id })),\n });\n if (definitionId == null) throw new Error(\"No definition selected.\");\n const selected = definitions.find((d) => d.id === definitionId);\n if (!selected || selected.id == null) {\n throw new Error(\"Selected definition not found.\");\n }\n return { id: selected.id, name: selected.name ?? `portal-${selected.id}` };\n}\n\nexport const cloneCommand: Command = new Command(\"clone\")\n .description(\n \"Clone a portal definition's provisioned GitHub repo (mints a fresh token per call)\",\n )\n .argument(\"[app]\", \"Definition name (prompts to select when omitted)\")\n .option(\"--app <name>\", \"Definition name (same as the positional argument)\")\n .option(\"--id <id>\", \"Definition id (avoids ambiguity when names repeat)\")\n .option(\"--dir <dir>\", \"Target directory (defaults to ./<slug>/)\")\n .action(async (appArg: string | undefined, options: CloneOptions) => {\n try {\n const token = getAuthToken();\n if (!token) {\n const profile = getActiveProfile();\n throw new Error(\n profile\n ? `No auth token for profile ${chalk.cyan(profile.name)}. Run ${chalk.cyan(\"fluid login\")}.`\n : `Not logged in. Run ${chalk.cyan(\"fluid login\")} first.`,\n );\n }\n const client = createClient(token);\n\n const spinner = ora(\"Resolving definition…\").start();\n const definition = await selectDefinition(\n client,\n options.app ?? appArg,\n options.id,\n );\n\n spinner.text = \"Minting git credential…\";\n const outcome = await createPortalGitCredentialWithRetry({\n baseUrl: getBaseUrl(),\n token,\n definitionId: definition.id,\n onRetry: ({ attempt, totalAttempts }) => {\n spinner.text = `Preparing Git repository… retrying (${attempt}/${totalAttempts})`;\n },\n });\n if (outcome.status === \"error\") {\n spinner.warn(`Can't clone — ${outcome.reason}`);\n process.exit(1);\n }\n\n const slug = deriveSlug(definition.name);\n const targetDir = resolve(options.dir ?? slug);\n\n if (outcome.status === \"unavailable\") {\n // No repository to clone — not a transient failure worth a scary\n // exit. Local files + API sync are a fully working portal, so\n // scaffold the bundled starter into the target directory instead\n // and let Git attach later if/when it becomes available.\n spinner.info(gitSyncUnavailableNotice(outcome));\n const resumeGitless =\n existsSync(targetDir) &&\n (await canResumeGitlessPortalScaffold(targetDir));\n if (existsSync(targetDir) && !resumeGitless) {\n spinner.fail(\n `Directory ${targetDir} already exists. Pass --dir or remove it first.`,\n );\n process.exit(1);\n }\n if (!resumeGitless) {\n await mkdir(dirname(targetDir), { recursive: true });\n try {\n // Reserve the exact destination (same race-closing pattern as\n // the clone path below).\n await mkdir(targetDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n spinner.fail(\n `Directory ${targetDir} was created by another process. Choose a different --dir or retry after it finishes.`,\n );\n process.exit(1);\n }\n throw error;\n }\n }\n try {\n spinner.text = resumeGitless\n ? \"Resuming portal scaffold…\"\n : \"Scaffolding portal from the starter template…\";\n await scaffoldPortalProjectIntoExistingDirectory(targetDir, {\n name: slug,\n installDeps: false,\n selectedPages: [],\n profileName: getActiveProfile()?.name ?? \"\",\n });\n } catch (err) {\n spinner.fail(\n `Git sync is unavailable and the local scaffold failed at ${targetDir}. Fix the reported path or permission conflict and retry.`,\n );\n throw err;\n }\n spinner.succeed(\n `Created ${chalk.cyan(targetDir)} from the starter template (no Git history yet)`,\n );\n console.log(chalk.dim(` cd ${targetDir}`));\n console.log(\n chalk.dim(` ${chalk.cyan(\"fluid portal pull\")} to sync content`),\n );\n return;\n }\n\n await ensureGitAvailable();\n const resumeScaffold =\n existsSync(targetDir) &&\n (await canResumePortalScaffold(\n targetDir,\n outcome.credential.remote_url,\n ));\n if (existsSync(targetDir) && !resumeScaffold) {\n spinner.fail(\n `Directory ${targetDir} already exists. Pass --dir or remove it first.`,\n );\n process.exit(1);\n }\n\n if (!resumeScaffold) {\n await mkdir(dirname(targetDir), { recursive: true });\n try {\n // Reserve the exact destination after the friendly pre-check. This\n // closes the race where two clone commands both saw a missing path\n // and one later deleted the other's checkout during failure cleanup.\n await mkdir(targetDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n spinner.fail(\n `Directory ${targetDir} was created by another process. Choose a different --dir or retry after it finishes.`,\n );\n process.exit(1);\n }\n throw error;\n }\n\n spinner.text = `Cloning into ${slug}/…`;\n try {\n // Token-bearing URL is passed inline — never written to .git/config.\n await gitClone(outcome.credential.remote_url, targetDir);\n } catch (err) {\n const cleanupError = await removeIncompleteCheckout(targetDir);\n spinner.fail(\n cleanupError\n ? `git clone failed, and Fluid could not remove the incomplete checkout: ${cleanupError}`\n : \"git clone failed; removed the incomplete checkout\",\n );\n throw err;\n }\n\n // Scope the fresh checkout to portal content up front, so even a\n // hand-run `git add -A` (before the user's first `fluid portal\n // push`) can't sweep in Mist Desktop's private state. The sync\n // re-applies this on every push, which is what heals clones made\n // before this existed.\n excludeLocalStateFromGit(targetDir);\n }\n\n if (\n resumeScaffold ||\n !(await fileExists(join(targetDir, \"package.json\")))\n ) {\n try {\n spinner.text = resumeScaffold\n ? \"Resuming runnable portal scaffold…\"\n : \"Adding runnable portal scaffold…\";\n await scaffoldPortalProjectIntoExistingDirectory(targetDir, {\n name: slug,\n installDeps: false,\n selectedPages: [],\n profileName: getActiveProfile()?.name ?? \"\",\n });\n } catch (err) {\n spinner.fail(\n `Repository cloned at ${targetDir}, but the runnable scaffold failed. The provisioned Git history and repository files were preserved; fix the reported path or permission conflict and retry the scaffold.`,\n );\n throw err;\n }\n }\n spinner.succeed(`Cloned into ${chalk.cyan(targetDir)}`);\n console.log(chalk.dim(` cd ${targetDir}`));\n console.log(\n chalk.dim(` ${chalk.cyan(\"fluid portal pull\")} to sync content`),\n );\n } catch (error) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (error instanceof Error ? error.message : String(error)),\n );\n process.exit(1);\n }\n });\n\nexport function registerCloneCommand(ctx: PluginContext): void {\n ctx.program.addCommand(cloneCommand);\n}\n","import { findProjectConfig, readConfig } from \"@fluid-app/fluid-cli\";\nimport {\n publishWidgetPackage,\n type PublishedWidgetPackage,\n type WidgetPackageOwner,\n type WidgetPackageOwnerKind,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nimport {\n createFluidOsHeyApiClient,\n type FluidOsHeyApiClient,\n} from \"@fluid-app/fluidos-api-client\";\nimport chalk from \"chalk\";\nimport { getPortalCliVersion } from \"../utils/file-system.js\";\n\nexport interface PublishWidgetRuntimeArtifactsOptions {\n readonly projectDir: string;\n readonly outDir: string;\n readonly buildOwner: WidgetPackageOwnerKind;\n readonly uploadOwner: WidgetPackageOwner;\n readonly dryRun?: boolean;\n readonly client?: FluidOsHeyApiClient;\n /** Publish as this version rather than the config's. */\n readonly version?: string;\n}\n\nexport type PublishWidgetRuntimeArtifactsResult = PublishedWidgetPackage;\n\nexport async function publishWidgetRuntimeArtifacts(\n options: PublishWidgetRuntimeArtifactsOptions,\n): Promise<PublishWidgetRuntimeArtifactsResult> {\n const isDryRun = options.dryRun === true;\n const client = options.client;\n\n if (!isDryRun && !client) {\n throw new Error(\"A FluidOS API client is required when dryRun is false.\");\n }\n\n const cliVersion = await getPortalCliVersion();\n\n // Errors propagate exactly as thrown. This used to re-wrap any error carrying\n // `details` into a plain `Error` with the details appended to the message —\n // which discarded `code` and `diagnostics` on precisely the failures that had\n // the most to say about themselves. Callers that want the details in their\n // output call `printWidgetPublishError`; callers that want to act on the\n // cause still have it.\n if (isDryRun) {\n return await publishWidgetPackage({\n projectDir: options.projectDir,\n outDir: options.outDir,\n owner: options.buildOwner,\n uploadOwner: options.uploadOwner,\n cliVersion,\n dryRun: true,\n });\n }\n\n if (!client) {\n throw new Error(\"A FluidOS API client is required when dryRun is false.\");\n }\n\n return await publishWidgetPackage({\n projectDir: options.projectDir,\n outDir: options.outDir,\n owner: options.buildOwner,\n uploadOwner: options.uploadOwner,\n cliVersion,\n ...(options.version ? { version: options.version } : {}),\n client,\n });\n}\n\n/**\n * Prints a publish failure with its `details` block, which is where the\n * validation tooling names the offending widget and field.\n */\nexport function printWidgetPublishError(error: unknown): void {\n console.error(\n chalk.red(\"Error:\") +\n \" \" +\n (error instanceof Error ? error.message : String(error)),\n );\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"details\" in error &&\n typeof error.details === \"string\" &&\n error.details.length > 0\n ) {\n console.error(error.details);\n }\n}\n\nconst DEFAULT_API_BASE = \"https://api.fluid.app\";\n\n/**\n * Builds the client used to publish widget artifacts.\n *\n * `FLUID_TOKEN` / `FLUID_API_TOKEN` win over any stored profile, matching the\n * resolution order the rest of the CLI documents and implements (see\n * `getAuthToken` in `@fluid-app/fluid-cli`). This path used to read only\n * `~/.fluid/config.json`, which made it the one command a caller could not\n * point at a chosen company: a host that spawns the CLI would publish to\n * whichever profile the terminal happened to have active, and shipping a\n * company's widgets to the wrong tenant is not a failure they could detect from\n * the output.\n *\n * With the env token set, the base URL comes from `FLUID_API_BASE` rather than\n * the stored profile's — a token issued elsewhere has no relationship to the\n * host that profile points at.\n */\nexport function createAuthenticatedWidgetPackageClient(): FluidOsHeyApiClient {\n const envToken =\n process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"] ?? \"\";\n if (envToken.length > 0) {\n return createFluidOsHeyApiClient({\n baseUrl: process.env[\"FLUID_API_BASE\"] ?? DEFAULT_API_BASE,\n getAuthToken: () => envToken,\n });\n }\n\n const { profile, profileName, token } = resolveAuthenticatedProfile();\n if (!token) {\n if (!profileName) {\n throw new Error(\n \"Not logged in. Run \" + chalk.cyan(\"fluid login\") + \" first.\",\n );\n }\n throw new Error(\n \"No auth token found for profile \" +\n chalk.cyan(profileName) +\n \". Run \" +\n chalk.cyan(\"fluid login\") +\n \" to re-authenticate.\",\n );\n }\n\n return createFluidOsHeyApiClient({\n baseUrl:\n profile?.baseUrl ?? process.env[\"FLUID_API_BASE\"] ?? DEFAULT_API_BASE,\n getAuthToken: () => token,\n });\n}\n\ntype ProfileWithBaseUrl = ReturnType<typeof readConfig>[\"profiles\"][string] & {\n readonly baseUrl?: string;\n};\n\nfunction resolveAuthenticatedProfile(): {\n readonly profile?: ProfileWithBaseUrl;\n readonly profileName?: string;\n readonly token?: string;\n} {\n const config = readConfig();\n const projectConfig = findProjectConfig(process.cwd());\n\n if (projectConfig?.profile) {\n const profile = config.profiles[projectConfig.profile] as\n | ProfileWithBaseUrl\n | undefined;\n if (profile) {\n return {\n profile,\n profileName: projectConfig.profile,\n token: profile.token,\n };\n }\n }\n\n if (!config.activeProfile) return {};\n const profile = config.profiles[config.activeProfile] as\n | ProfileWithBaseUrl\n | undefined;\n if (!profile) return {};\n\n return {\n profile,\n profileName: config.activeProfile,\n token: profile.token,\n };\n}\n\nexport function printWidgetPublishSummary(options: {\n readonly title: string;\n readonly ownerLabel: string;\n readonly environment?: string;\n readonly outDir: string;\n readonly packageId: string;\n readonly version: string;\n readonly artifactCount: number;\n}): void {\n console.log();\n console.log(chalk.green.bold(`${options.title} complete`));\n console.log();\n console.log(chalk.gray(\"Owner: \") + chalk.white(options.ownerLabel));\n if (options.environment) {\n console.log(chalk.gray(\"Environment: \") + chalk.white(options.environment));\n }\n console.log(chalk.gray(\"Package: \") + chalk.white(options.packageId));\n console.log(chalk.gray(\"Version: \") + chalk.white(options.version));\n console.log(chalk.gray(\"Output: \") + chalk.cyan(options.outDir));\n console.log(\n chalk.gray(\"Artifacts: \") + chalk.white(String(options.artifactCount)),\n );\n console.log();\n}\n\nexport function printRuntimeOnlyNotice(): void {\n console.log(\n chalk.yellow(\n \"This publishes widget runtime artifacts only; it does not deploy the hosted portal shell.\",\n ),\n );\n console.log();\n}\n\nexport function printDryRunSessionPayload(sessionPayload: unknown): void {\n console.log(chalk.bold(\"Dry-run upload session payload:\"));\n console.log(JSON.stringify(sessionPayload, null, 2));\n console.log();\n}\n","/**\n * Pure helper functions for widget scaffolding.\n * Extracted from widget-create command for testability.\n */\n\n/**\n * Convert kebab-case name to PascalCase widget type.\n * e.g., \"stock-ticker\" → \"StockTickerWidget\"\n */\nexport function toWidgetType(name: string): string {\n const pascal = name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\"\");\n return pascal.endsWith(\"Widget\") ? pascal : `${pascal}Widget`;\n}\n\n/**\n * Derive the component name from a widget type.\n * Strips the trailing \"Widget\" suffix, but only if the result is non-empty\n * and starts with a letter (not a digit).\n * e.g., \"StockTickerWidget\" → \"StockTicker\"\n */\nexport function toComponentName(widgetType: string): string {\n if (widgetType === \"Widget\") return \"Widget\";\n const stripped = widgetType.replace(/Widget$/, \"\");\n return stripped || widgetType;\n}\n\n/**\n * Convert kebab-case name to a display name.\n * e.g., \"stock-ticker\" → \"Stock Ticker\"\n */\nexport function toDisplayName(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \");\n}\n\n/**\n * Convert kebab-case to camelCase.\n * e.g., \"stock-ticker\" → \"stockTicker\"\n */\nexport function toCamelCase(name: string): string {\n return name.replace(/-./g, (x) => x.charAt(1).toUpperCase());\n}\n\n/**\n * Validate a widget name for scaffold.\n * Must be kebab-case, no trailing/consecutive dashes, no bare \"widget\".\n */\nexport function validateWidgetName(name: string): string | null {\n if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name)) {\n return \"Widget name must be kebab-case with no trailing or consecutive dashes (e.g., stock-ticker).\";\n }\n if (name === \"widget\") {\n return 'Widget name \"widget\" is reserved. Use a more descriptive name (e.g., custom-widget).';\n }\n return null;\n}\n\n/**\n * Insert an import line after the last import in a source file.\n * Skips if the import already exists (prevents duplicates on re-scaffold).\n * Returns null if no imports exist in the source.\n */\nexport function insertImport(\n source: string,\n importLine: string,\n): string | null {\n // Skip if import already exists\n if (source.includes(importLine)) return source;\n\n // Find the last real import statement (starts at column 0, not inside comments)\n const importPattern = /^import\\s/gm;\n let lastImportIndex = -1;\n let match: RegExpExecArray | null;\n while ((match = importPattern.exec(source)) !== null) {\n lastImportIndex = match.index;\n }\n if (lastImportIndex === -1) return null;\n\n const lineEnd = source.indexOf(\"\\n\", lastImportIndex);\n if (lineEnd === -1) {\n // Last import is on the final line with no trailing newline\n return source + \"\\n\" + importLine + \"\\n\";\n }\n\n return (\n source.slice(0, lineEnd + 1) + importLine + \"\\n\" + source.slice(lineEnd + 1)\n );\n}\n\n/** Insert a widget definition into a defineWidgetPackage widgets array. */\nexport function insertIntoWidgetPackage(\n source: string,\n exportName: string,\n): string | null {\n const packageStart = source.lastIndexOf(\"defineWidgetPackage(\");\n if (packageStart === -1) return null;\n\n const packageSource = source.slice(packageStart);\n const pattern = /(widgets\\s*:\\s*)\\[([^\\]]*)\\]/g;\n let lastMatch: RegExpExecArray | null = null;\n let m: RegExpExecArray | null;\n while ((m = pattern.exec(packageSource)) !== null) {\n lastMatch = m;\n }\n\n if (!lastMatch) return null;\n\n const fullMatch = lastMatch[0];\n const declaration = lastMatch[1]!;\n const inner = lastMatch[2]!;\n const matchStart = packageStart + lastMatch.index;\n\n const lines = inner.split(\"\\n\");\n\n // Collect existing real entries (stripped of commas for comparison)\n const existingEntries: string[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith(\"//\")) {\n existingEntries.push(trimmed.replace(/,$/, \"\"));\n }\n }\n\n // Skip if already registered\n if (existingEntries.includes(exportName)) {\n return source;\n }\n\n // Preserve comment lines\n const commentLines = lines\n .filter((line) => line.trim().startsWith(\"//\"))\n .map((line) => line.trimEnd());\n\n const commentBlock =\n commentLines.length > 0 ? \"\\n\" + commentLines.join(\"\\n\") : \"\";\n\n // Build entry lines with consistent 2-space indentation\n const allEntries = [...existingEntries, exportName];\n const entryBlock = allEntries.map((e) => ` ${e},`).join(\"\\n\");\n\n const replacement = `${declaration}[${commentBlock}\\n${entryBlock}\\n]`;\n\n return (\n source.slice(0, matchStart) +\n replacement +\n source.slice(matchStart + fullMatch.length)\n );\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport path from \"node:path\";\nimport {\n readCompanyWidgetPackages,\n type CompanyWidgetPackage,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nimport type { WidgetCreateOptions } from \"../types.js\";\nimport { createAuthenticatedWidgetPackageClient } from \"./widget-package-publish.js\";\nimport {\n insertImport,\n insertIntoWidgetPackage,\n toCamelCase,\n toComponentName,\n toDisplayName,\n toWidgetType,\n validateWidgetName,\n} from \"../utils/widget-helpers.js\";\n\nconst WIDGET_CONFIG_PATH = path.join(\"src\", \"widgets.config.ts\");\n\nconst createSubcommand = new Command(\"create\")\n .description(\"Scaffold a new Remote DOM widget\")\n .argument(\"<name>\", \"Widget name in kebab-case (e.g., stock-ticker)\")\n .option(\n \"-c, --category <category>\",\n \"Widget category for palette grouping\",\n \"components\",\n )\n .action(async (name: string, options: WidgetCreateOptions) => {\n const cwd = process.cwd();\n const packageJsonPath = path.join(cwd, \"package.json\");\n const configPath = path.join(cwd, WIDGET_CONFIG_PATH);\n const widgetDir = path.join(cwd, \"src\", \"widgets\", name);\n\n let stylesAppended = false;\n let resolvedPackageKey: string | null = null;\n\n const validationError = validateWidgetName(name);\n if (validationError) {\n console.error(chalk.red(`Error: ${validationError}`));\n process.exit(1);\n }\n\n if (!(await fs.pathExists(packageJsonPath))) {\n console.error(chalk.red(\"Error: No package.json found.\"));\n console.error(\n chalk.yellow(\"Run this command from a Fluid portal project root.\"),\n );\n process.exit(1);\n }\n\n if (await fs.pathExists(widgetDir)) {\n console.error(\n chalk.red(\n `Error: Widget directory already exists: src/widgets/${name}`,\n ),\n );\n process.exit(1);\n }\n\n const widgetType = toWidgetType(name);\n const componentName = toComponentName(widgetType);\n const camelName = toCamelCase(name);\n const exportName = camelName.endsWith(\"Widget\")\n ? camelName\n : `${camelName}Widget`;\n const displayName = toDisplayName(name);\n const category = options.category ?? \"components\";\n const importLine = `import { ${exportName} } from \"./widgets/${name}\";`;\n\n try {\n await fs.ensureDir(widgetDir);\n await fs.writeFile(\n path.join(widgetDir, \"index.tsx\"),\n createWidgetSource({\n name,\n widgetType,\n componentName,\n exportName,\n displayName,\n category,\n }),\n \"utf-8\",\n );\n // Rules go in the PROJECT-ROOT stylesheet, never a co-located one, and\n // the widget module never imports CSS. Both halves matter:\n //\n // • An `import \"./styles.css\"` inside a widget is fatal in dev. Vite\n // answers it with a browser style-injector that pulls in\n // `/@vite/client`, which evaluates `class ErrorOverlay extends\n // HTMLElement` at module top level. There is no HTMLElement in a Web\n // Worker, so it throws while evaluating the package source and NO\n // widget in the package registers — one widget's import takes down\n // every widget in the project.\n //\n // • Only the root file is delivered anyway. Dev serves the descriptor's\n // `cssUrls`, which default to `/styles.css`; the build entry imports\n // `<projectDir>/styles.css`. A co-located file reaches the shadow root\n // on neither path.\n //\n // A stylesheet is still the ONLY way to style a widget — the host\n // sanitizer drops `style` attributes and bans `<style>`.\n await appendWidgetStyles(cwd, name);\n stylesAppended = true;\n\n if (await fs.pathExists(configPath)) {\n const configSource = await fs.readFile(configPath, \"utf-8\");\n if (isEmptyWidgetConfig(configSource)) {\n resolvedPackageKey = await resolveCompanyPackageKey();\n await fs.writeFile(\n configPath,\n createWidgetConfigSource(\n importLine,\n exportName,\n resolvedPackageKey ?? undefined,\n ),\n \"utf-8\",\n );\n } else {\n const withImport = insertImport(configSource, importLine);\n const updated = withImport\n ? insertIntoWidgetPackage(withImport, exportName)\n : null;\n if (!updated) {\n throw new Error(\n `${WIDGET_CONFIG_PATH} must export the starter widgetPackages placeholder or contain a defineWidgetPackage({ widgets: [...] }) declaration.`,\n );\n }\n await fs.writeFile(configPath, updated, \"utf-8\");\n }\n } else {\n resolvedPackageKey = await resolveCompanyPackageKey();\n await fs.writeFile(\n configPath,\n createWidgetConfigSource(\n importLine,\n exportName,\n resolvedPackageKey ?? undefined,\n ),\n \"utf-8\",\n );\n }\n } catch (error) {\n await fs.remove(widgetDir).catch(() => {});\n // The stylesheet append happens before the config update, so a config\n // failure used to leave `.<name> { … }` rules in the project-root sheet\n // for a widget directory that had just been deleted. Cosmetic rather than\n // corrupting — re-running is idempotent — but a rollback that only undoes\n // half of what it did is the kind of thing someone later has to work out\n // from first principles.\n if (stylesAppended) {\n await removeWidgetStyles(cwd, name).catch(() => {});\n }\n console.error(chalk.red(\"Error: Failed to scaffold Remote DOM widget.\"));\n console.error(error);\n process.exit(1);\n }\n\n console.log();\n console.log(\n chalk.green(\"Created widget:\") + ` src/widgets/${name}/index.tsx`,\n );\n console.log(\n chalk.green(\"Registered\") + ` in ${chalk.cyan(WIDGET_CONFIG_PATH)}`,\n );\n if (resolvedPackageKey) {\n console.log(\n chalk.green(\"Package key:\") +\n ` ${chalk.cyan(resolvedPackageKey)} (this company's existing widget package)`,\n );\n }\n console.log();\n console.log(chalk.yellow(\"Next steps:\"));\n console.log(` 1. Edit ${chalk.cyan(`src/widgets/${name}/index.tsx`)}`);\n console.log(\n ` Styles live in ${chalk.cyan(\"styles.css\")} at the project root — widgets must not import CSS.`,\n );\n console.log(\n ` 2. Run ${chalk.cyan(\"fluid portal deploy --dry-run\")} to verify the package`,\n );\n });\n\nexport function createWidgetSource(options: {\n readonly name: string;\n readonly widgetType: string;\n readonly componentName: string;\n readonly exportName: string;\n readonly displayName: string;\n readonly category: string;\n}): string {\n return `import { defineWidget } from \"@fluid-app/portal-sdk/widgets/worker\";\n\ninterface ${options.widgetType}Props {\n readonly title?: string;\n}\n\n// Styling: a stylesheet is the ONLY way to style a widget. The host sanitizes\n// every Remote DOM mutation before it reaches the shadow root, so a\n// \\`style={{ ... }}\\` prop is silently dropped and \\`<style>\\` is a banned tag.\n// Give elements classNames and put the rules in the project-root styles.css\n// (this widget's rules were appended there when it was scaffolded).\n//\n// Do NOT add \\`import \"./styles.css\"\\` here. In dev, Vite answers a CSS import\n// with a browser style-injector that loads \\`/@vite/client\\`, which touches\n// HTMLElement — absent in a Web Worker — and throws while the package is being\n// evaluated, taking down every widget in the project, not just this one.\nfunction ${options.componentName}({ title = ${JSON.stringify(options.displayName)} }: ${options.widgetType}Props) {\n return (\n <section className=\"${options.name}\">\n <h3 className=\"${options.name}__title\">{title}</h3>\n <p className=\"${options.name}__body\">\n Edit this widget in src/widgets/${options.name}/index.tsx\n </p>\n </section>\n );\n}\n\nexport const ${options.exportName} = defineWidget({\n name: ${JSON.stringify(options.componentName)},\n component: ${options.componentName},\n displayName: ${JSON.stringify(options.displayName)},\n description: ${JSON.stringify(`A custom ${options.displayName.toLowerCase()} widget`)},\n icon: \"puzzle-piece\",\n category: ${JSON.stringify(options.category)},\n propertySchema: {\n fields: [{ key: \"title\", label: \"Title\", type: \"text\" }],\n },\n defaultProps: {\n title: ${JSON.stringify(options.displayName)},\n },\n});\n`;\n}\n\n/**\n * Appends a scaffolded widget's rules to the project-root `styles.css`,\n * creating the file when a project has none yet.\n *\n * The root stylesheet is the single sheet the tooling delivers — dev serves it\n * as the descriptor's `cssUrls`, and the build entry imports it — so every\n * widget in a project shares it. Appending (rather than overwriting) keeps\n * earlier widgets' rules intact; re-scaffolding a name that is already present\n * is a no-op rather than a duplicate block.\n */\n/**\n * Removes a widget's appended rules from the project-root `styles.css`.\n *\n * Only used to undo a failed scaffold — `appendWidgetStyles` runs before the\n * config update, so a config failure would otherwise strand rules for a widget\n * directory that has just been deleted. Matches the exact block the append\n * writes and leaves everything else, including a project's own rules and other\n * widgets', untouched.\n */\nexport async function removeWidgetStyles(\n projectDir: string,\n name: string,\n): Promise<void> {\n const stylesPath = path.join(projectDir, \"styles.css\");\n if (!(await fs.pathExists(stylesPath))) return;\n const existing = await fs.readFile(stylesPath, \"utf-8\");\n const block = createWidgetStylesSource(name);\n if (!existing.includes(block)) return;\n const withoutBlock = existing.replace(block, \"\");\n await fs.writeFile(\n stylesPath,\n withoutBlock.replace(/\\n{3,}/gu, \"\\n\\n\"),\n \"utf-8\",\n );\n}\n\nexport async function appendWidgetStyles(\n projectDir: string,\n name: string,\n): Promise<void> {\n const stylesPath = path.join(projectDir, \"styles.css\");\n const block = createWidgetStylesSource(name);\n\n if (!(await fs.pathExists(stylesPath))) {\n await fs.writeFile(stylesPath, `${ROOT_STYLES_HEADER}\\n${block}`, \"utf-8\");\n return;\n }\n\n const existing = await fs.readFile(stylesPath, \"utf-8\");\n if (existing.includes(`.${name} {`)) return;\n const separator = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n await fs.appendFile(stylesPath, `${separator}${block}`, \"utf-8\");\n}\n\nconst ROOT_STYLES_HEADER = `/* Runtime styles for this portal's widgets.\n *\n * One sheet per project: dev serves it to the widget shadow root as the\n * package descriptor's cssUrls, and the package build imports it. Widget\n * modules must NOT import CSS themselves — in dev that pulls \\`/@vite/client\\`\n * into the Web Worker, which throws and stops every widget in the project from\n * registering.\n */\n`;\n\n/**\n * Starter rules for a scaffolded widget.\n *\n * Colors, type, and radii come from the host portal's semantic CSS variables so\n * the widget matches whatever portal it lands on, in both light and dark,\n * without a second hand-written palette. Each `var()` carries a fallback so the\n * widget stays legible in a host that has not adopted the portal theme.\n *\n * Selectors are prefixed with the widget name so nothing leaks into the host.\n */\nfunction createWidgetStylesSource(name: string): string {\n return `.${name} {\n padding: 24px;\n border: 1px solid var(--border, #e5e7eb);\n border-radius: var(--radius-lg, 12px);\n background: var(--card, #ffffff);\n color: var(--card-foreground, #111827);\n font-family: var(--font-body, ui-sans-serif, system-ui, sans-serif);\n}\n\n.${name}__title {\n margin: 0 0 8px;\n color: var(--card-foreground, #111827);\n font-family: var(--font-header, inherit);\n font-size: 1.25rem;\n font-weight: 700;\n}\n\n.${name}__body {\n margin: 0;\n color: var(--muted-foreground, #6b7280);\n}\n`;\n}\n\nfunction isEmptyWidgetConfig(source: string): boolean {\n return /export\\s+const\\s+widgetPackages\\s*=\\s*\\[\\]\\s+as\\s+const\\s*;/u.test(\n source,\n );\n}\n\n/**\n * The key written when the company's real one cannot be resolved.\n *\n * A guess, and one that fails for any company that has already published — a\n * company has exactly ONE widget package and its key is fixed by whichever\n * project published first, so this value is only ever correct by accident.\n */\nconst PLACEHOLDER_PACKAGE_KEY = \"portal-widgets\";\n\nexport function createWidgetConfigSource(\n importLine: string,\n exportName: string,\n packageKey: string = PLACEHOLDER_PACKAGE_KEY,\n): string {\n return `import { defineWidgetPackage } from \"@fluid-app/portal-sdk/widgets/worker\";\n${importLine}\n\nexport const widgetPackage = defineWidgetPackage({\n scope: \"company\",\n packageStableId: ${JSON.stringify(packageKey)},\n version: \"0.0.0-dev\",\n widgets: [${exportName}],\n});\n`;\n}\n\n/**\n * Reads the company's existing widget package key, for a config being CREATED.\n *\n * Scaffolding is the one moment this can be done safely. A widget's type is\n * `company.<key>.<Name>` and those strings are written into screen JSON, so\n * changing the key later orphans every screen using it — which is why\n * `fluid portal deploy` reports a mismatch rather than rewriting it. At\n * `widget create` no screen references the type yet, so resolving it here\n * prevents the mismatch instead of explaining it afterwards.\n *\n * Best-effort by design: this command made no network calls before, and a\n * scaffold must not fail because a lookup did. Every uncertainty — no token, no\n * network, a company that has never published, a slow answer — falls back to the\n * placeholder, which is exactly today's behaviour.\n *\n * The deadline is deliberate here, unlike the SDK's dev-server probe where a\n * timeout discarded a real answer. The fallback IS the correct result for an\n * offline scaffold, so bounding the wait costs nothing and stops a hung API\n * from wedging a local command.\n */\nasync function resolveCompanyPackageKey(): Promise<string | null> {\n try {\n const client = createAuthenticatedWidgetPackageClient();\n const packages = await Promise.race([\n readCompanyWidgetPackages(client),\n new Promise<CompanyWidgetPackage[]>((resolve) => {\n setTimeout(() => resolve([]), PACKAGE_KEY_LOOKUP_TIMEOUT_MS);\n }),\n ]);\n return packages[0]?.key ?? null;\n } catch {\n // Not logged in, or the CLI has no profile for this project. Neither is a\n // reason to refuse to scaffold a widget.\n return null;\n }\n}\n\nconst PACKAGE_KEY_LOOKUP_TIMEOUT_MS = 5000;\n\nexport const widgetCommand: Command = new Command(\"widget\")\n .description(\"Manage Remote DOM portal widgets\")\n .addCommand(createSubcommand);\n\nexport function registerWidgetCommand(ctx: PluginContext): void {\n ctx.program.addCommand(widgetCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport type { FluidOsHeyApiClient } from \"@fluid-app/fluidos-api-client\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport type { PortalDeployOptions } from \"../types.js\";\nimport {\n loadAndValidateWidgetPackage,\n preflightCompanyPackage,\n type CompanyPackagePreflight,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nimport {\n createAuthenticatedWidgetPackageClient,\n printRuntimeOnlyNotice,\n printDryRunSessionPayload,\n printWidgetPublishSummary,\n publishWidgetRuntimeArtifacts,\n} from \"./widget-package-publish.js\";\n\nconst DEFAULT_OUT_DIR = \".fluid/widget-dist\";\nconst DEFAULT_ENVIRONMENT = \"production\";\n\nexport const deployCommand: Command = new Command(\"deploy\")\n .description(\"Publish company-owned portal widget runtime artifacts\")\n .option(\n \"-e, --environment <name>\",\n \"Target environment label for output reporting\",\n DEFAULT_ENVIRONMENT,\n )\n .option(\n \"-o, --out-dir <dir>\",\n \"Widget artifact output directory\",\n DEFAULT_OUT_DIR,\n )\n .option(\"--dry-run\", \"Build and validate upload payload without publishing\")\n .action(async (options: PortalDeployOptions) => {\n const environment = options.environment ?? DEFAULT_ENVIRONMENT;\n const outDir = options.outDir ?? DEFAULT_OUT_DIR;\n const dryRun = options.dryRun === true;\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Portal Deploy\"));\n console.log();\n printRuntimeOnlyNotice();\n console.log(chalk.gray(\"Environment: \") + chalk.white(environment));\n console.log(chalk.gray(\"Output: \") + chalk.cyan(outDir));\n if (dryRun)\n console.log(chalk.yellow(\"Dry run: no upload will be created.\"));\n console.log();\n\n const spinner = ora(\"Building company-owned widget package...\").start();\n const result = await runPortalDeploy({\n projectDir: process.cwd(),\n outDir,\n environment,\n dryRun,\n onProgress: (label) => {\n spinner.text = label;\n },\n });\n\n if (!result.ok) {\n spinner.fail(\"Portal deploy failed\");\n console.log();\n if (result.code === \"PACKAGE_KEY_MISMATCH\") {\n console.log(chalk.red(\"Package key does not match this company.\"));\n } else {\n console.error(chalk.red(\"Error:\") + \" \" + result.message);\n }\n if (result.hint) {\n console.log();\n console.log(result.hint);\n }\n console.log();\n process.exit(1);\n }\n\n spinner.succeed(\"Built widget runtime artifacts\");\n\n if (dryRun) {\n console.log(\n chalk.yellow(\"Dry run complete — upload session was not requested.\"),\n );\n console.log();\n if (result.sessionPayload !== undefined) {\n printDryRunSessionPayload(result.sessionPayload);\n }\n } else {\n console.log(chalk.green(\"Published widget package version.\"));\n }\n\n if (result.requestedVersion) {\n console.log(\n chalk.yellow(\n `Version ${result.requestedVersion} is already published for this company, so this went out as ${result.version}.`,\n ),\n );\n console.log(\n chalk.gray(\n \"Every portal project in a company shares one widget package, so its versions are shared too.\",\n ),\n );\n console.log();\n }\n\n printWidgetPublishSummary({\n title: dryRun ? \"Portal deploy dry run\" : \"Portal deploy\",\n ownerLabel: \"company\",\n environment,\n outDir,\n packageId: result.packageId,\n version: result.version,\n artifactCount: result.artifactCount,\n });\n });\n\nexport interface RunPortalDeployOptions {\n readonly projectDir: string;\n readonly outDir: string;\n readonly environment: string;\n readonly dryRun: boolean;\n /** Lets an interactive caller keep a spinner honest during a multi-second run. */\n readonly onProgress?: (label: string) => void;\n}\n\n/** What a deploy produced, for this command's own rendering. */\nexport type RunPortalDeployResult =\n | {\n readonly ok: true;\n readonly packageId: string;\n readonly version: string;\n readonly artifactCount: number;\n /** Set when the config's version was taken and the publish moved past it. */\n readonly requestedVersion?: string;\n readonly sessionPayload?: unknown;\n }\n | {\n readonly ok: false;\n readonly code: string;\n readonly message: string;\n readonly hint?: string;\n };\n\n/**\n * The whole deploy, as one function returning a result.\n *\n * A result rather than a throw because two of its outcomes — a key that belongs\n * to another package, a version already published — are refusals with a fix to\n * show, not exceptions.\n */\nexport async function runPortalDeploy(\n options: RunPortalDeployOptions,\n): Promise<RunPortalDeployResult> {\n let client: FluidOsHeyApiClient | undefined;\n if (!options.dryRun) {\n try {\n client = createAuthenticatedWidgetPackageClient();\n } catch (error) {\n return {\n ok: false,\n code: \"NOT_AUTHENTICATED\",\n message: \"Not signed in to Fluid, so the widgets were not published.\",\n ...(error instanceof Error ? { hint: error.message } : {}),\n };\n }\n }\n\n // Preflight before building. Two failures live here, and both are refusals\n // whose fix is a line in a TypeScript file that the error names nowhere:\n // `422 Package key does not match this widget package` and `422 Package\n // version already exists`. Asking first turns both into something actionable.\n //\n // Skipped on --dry-run: that path never contacts the server, and requiring\n // auth to validate a local build would make the flag useless offline.\n let preflight: CompanyPackagePreflight = {};\n if (client) {\n options.onProgress?.(\"Checking this company's widget package...\");\n preflight = await preflightCompanyPackage({\n projectDir: options.projectDir,\n client,\n readConfiguredPackage: async (dir) => {\n const { validated } = await loadAndValidateWidgetPackage({\n projectDir: dir,\n owner: \"company\",\n });\n return { packageId: validated.packageId, version: validated.version };\n },\n });\n if (preflight.mismatch) {\n return {\n ok: false,\n code: \"PACKAGE_KEY_MISMATCH\",\n message: \"This project publishes under a different package key.\",\n hint: preflight.mismatch,\n };\n }\n }\n\n options.onProgress?.(\"Building company-owned widget package...\");\n try {\n const result = await publishWidgetRuntimeArtifacts({\n projectDir: options.projectDir,\n outDir: options.outDir,\n buildOwner: \"company\",\n uploadOwner: { kind: \"company\" },\n dryRun: options.dryRun,\n ...(preflight.versionOverride\n ? { version: preflight.versionOverride }\n : {}),\n ...(client ? { client } : {}),\n });\n\n return {\n ok: true,\n packageId: result.build.packageId,\n version: result.build.version,\n artifactCount: result.request.artifacts.length,\n // Reported rather than done quietly. The config still says the old\n // version, and a publish that silently disagreed with the file would be\n // its own small mystery later.\n ...(preflight.versionOverride && preflight.configuredVersion\n ? { requestedVersion: preflight.configuredVersion }\n : {}),\n sessionPayload: result.publish.sessionPayload,\n };\n } catch (error) {\n return {\n ok: false,\n code:\n error instanceof Error && \"code\" in error\n ? String((error as { code: unknown }).code)\n : \"PUBLISH_FAILED\",\n message:\n error instanceof Error\n ? error.message\n : \"Publishing the widgets failed.\",\n ...(error instanceof Error &&\n \"details\" in error &&\n typeof error.details === \"string\" &&\n error.details.length > 0\n ? { hint: error.details }\n : {}),\n };\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 { existsSync, readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Severity levels for drift diagnostics.\n * - ERROR: Security-critical or breaking drift (e.g., missing CSP)\n * - WARN: Stale config that may cause subtle issues\n * - INFO: Cosmetic or non-functional differences\n */\nexport type Severity = \"error\" | \"warn\" | \"info\";\n\nexport interface Diagnostic {\n file: string;\n severity: Severity;\n message: string;\n}\n\n/** Files that are managed by the SDK and should match the canonical template. */\nconst INFRASTRUCTURE_FILES = [\n \"index.html\",\n \"tsconfig.json\",\n \"vite.config.ts\",\n \".oxlintrc.json\",\n] as const;\n\n/** Entry files with expected content patterns after Phase 2 absorption. */\nconst ENTRY_CHECKS = [\n {\n file: \"src/main.tsx\",\n expectedPattern: \"createPortal\",\n hint: 'main.tsx should use createPortal() from @fluid-app/portal-sdk. Run \"fluid create\" to see the latest template.',\n },\n {\n file: \"src/index.css\",\n expectedPattern: \"@fluid-app/portal-sdk/globals.css\",\n hint: \"index.css should import @fluid-app/portal-sdk/globals.css instead of inline theme tokens.\",\n },\n] as const;\n\n/** Files that should NOT exist after Phase 2 (absorbed into SDK). */\nconst REMOVED_FILES = [\n {\n file: \"src/App.tsx\",\n hint: \"App.tsx is no longer needed — createPortal() handles the AppShell wiring internally.\",\n },\n {\n file: \"src/fluid.config.ts\",\n hint: \"fluid.config.ts is no longer needed — pass config overrides to createPortal() directly.\",\n },\n] as const;\n\nfunction readFileOrNull(filePath: string): string | null {\n try {\n return readFileSync(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nconst _currentFile = fileURLToPath(import.meta.url);\nconst _currentDir = dirname(_currentFile);\n\nfunction findTemplateDir(): string | null {\n // Walk up from current file to find package root (works in both dist/ and src/)\n let dir = _currentDir;\n while (!existsSync(join(dir, \"package.json\"))) {\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n const templateDir = join(dir, \"templates\");\n if (existsSync(join(templateDir, \"base\"))) return templateDir;\n return null;\n}\n\nexport function checkInfrastructureDrift(\n cwd: string,\n templateDir: string,\n): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n for (const file of INFRASTRUCTURE_FILES) {\n const portalPath = join(cwd, file);\n const templatePath = join(templateDir, \"base\", file);\n\n if (!existsSync(portalPath)) {\n diagnostics.push({\n file,\n severity: \"warn\",\n message: `Missing file: ${file}`,\n });\n continue;\n }\n\n if (!existsSync(templatePath)) {\n // Template doesn't have this file — skip\n continue;\n }\n\n const portalContent = readFileOrNull(portalPath);\n const templateContent = readFileOrNull(templatePath);\n\n if (portalContent !== null && templateContent !== null) {\n if (portalContent.trim() !== templateContent.trim()) {\n diagnostics.push({\n file,\n severity: \"warn\",\n message: `Content differs from canonical template. Review and update if needed.`,\n });\n }\n }\n }\n\n return diagnostics;\n}\n\nexport function checkEntryPatterns(cwd: string): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n for (const check of ENTRY_CHECKS) {\n const filePath = join(cwd, check.file);\n const content = readFileOrNull(filePath);\n\n if (content === null) {\n diagnostics.push({\n file: check.file,\n severity: \"error\",\n message: `Missing file. ${check.hint}`,\n });\n continue;\n }\n\n if (!content.includes(check.expectedPattern)) {\n diagnostics.push({\n file: check.file,\n severity: \"warn\",\n message: `Does not contain expected pattern \"${check.expectedPattern}\". ${check.hint}`,\n });\n }\n }\n\n return diagnostics;\n}\n\nexport function checkRemovedFiles(cwd: string): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n for (const check of REMOVED_FILES) {\n if (existsSync(join(cwd, check.file))) {\n diagnostics.push({\n file: check.file,\n severity: \"info\",\n message: `File can be removed. ${check.hint}`,\n });\n }\n }\n\n return diagnostics;\n}\n\nfunction formatDiagnostic(d: Diagnostic): string {\n const icon =\n d.severity === \"error\"\n ? chalk.red(\"ERROR\")\n : d.severity === \"warn\"\n ? chalk.yellow(\" WARN\")\n : chalk.blue(\" INFO\");\n\n return ` ${icon} ${chalk.bold(d.file)}\\n ${chalk.dim(d.message)}`;\n}\n\nexport const doctorCommand: Command = new Command(\"doctor\")\n .description(\n \"Check portal for scaffold drift and report stale infrastructure files\",\n )\n .action(async () => {\n const cwd = process.cwd();\n\n // Validate we're in a portal 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 portal project directory\"),\n );\n process.exit(1);\n }\n\n let packageJson: {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n try {\n packageJson = JSON.parse(readFileSync(packageJsonPath, \"utf-8\"));\n } catch {\n console.error(chalk.red(\"Error: Could not parse package.json\"));\n console.error(chalk.yellow(\"Ensure package.json contains valid JSON\"));\n process.exit(1);\n }\n const deps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n };\n\n if (!deps[\"@fluid-app/portal-sdk\"]) {\n console.error(\n chalk.red(\"Error: @fluid-app/portal-sdk not found in dependencies\"),\n );\n console.error(\n chalk.yellow(\n \"This command must be run from a Fluid portal project directory\",\n ),\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold(\"Fluid Portal Doctor\"));\n console.log(chalk.dim(\"Checking for scaffold drift...\\n\"));\n\n const diagnostics: Diagnostic[] = [];\n\n // Check entry file patterns\n diagnostics.push(...checkEntryPatterns(cwd));\n\n // Check for files that should be removed\n diagnostics.push(...checkRemovedFiles(cwd));\n\n // Check infrastructure drift against templates (if available)\n const templateDir = findTemplateDir();\n if (templateDir) {\n diagnostics.push(...checkInfrastructureDrift(cwd, templateDir));\n } else {\n console.log(\n chalk.dim(\n \" (Skipping infrastructure diff — template files not available)\\n\",\n ),\n );\n }\n\n // Report results\n if (diagnostics.length === 0) {\n console.log(chalk.green(\" All clear — no drift detected.\\n\"));\n return;\n }\n\n const errors = diagnostics.filter((d) => d.severity === \"error\");\n const warns = diagnostics.filter((d) => d.severity === \"warn\");\n const infos = diagnostics.filter((d) => d.severity === \"info\");\n\n for (const d of [...errors, ...warns, ...infos]) {\n console.log(formatDiagnostic(d));\n console.log();\n }\n\n // Summary\n const parts: string[] = [];\n if (errors.length > 0) parts.push(chalk.red(`${errors.length} error(s)`));\n if (warns.length > 0)\n parts.push(chalk.yellow(`${warns.length} warning(s)`));\n if (infos.length > 0) parts.push(chalk.blue(`${infos.length} info`));\n console.log(` ${parts.join(\", \")}\\n`);\n\n if (errors.length > 0) {\n process.exit(1);\n }\n });\n\nexport function registerDoctorCommand(ctx: PluginContext): void {\n ctx.program.addCommand(doctorCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport { createFluidOsClient, fluidOsApi } from \"../api/fluidos.js\";\nimport prompts from \"prompts\";\nimport path from \"node:path\";\nimport { readMappings } from \"../utils/mappings.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PORTAL_SYNC_DIR = \".portal-sync\";\n\nfunction getApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nfunction requireToken(): string {\n const token = getAuthToken();\n if (!token) {\n console.error(\n chalk.red(\"Error:\") +\n \" Not logged in. Run \" +\n chalk.cyan(\"`fluid login`\") +\n \" first.\",\n );\n process.exit(1);\n }\n return token;\n}\n\nfunction createClient(token: string) {\n return createFluidOsClient({\n baseUrl: getApiBase(),\n token,\n });\n}\n\nasync function requireDefinitionId(): Promise<number> {\n const cwd = process.cwd();\n const mappings = await readMappings(path.join(cwd, PORTAL_SYNC_DIR));\n if (!mappings) {\n console.error(\n chalk.red(\"Error:\") +\n \" No definition pulled. Run \" +\n chalk.cyan(\"`fluid portal pull`\") +\n \" first.\",\n );\n process.exit(1);\n }\n return mappings.definition.id;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Subcommands\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst createVersionCommand = new Command(\"create\")\n .description(\"Create a new version (snapshot) of the portal definition\")\n .option(\"--activate\", \"Activate the version immediately after creation\")\n .action(async (options: { activate?: boolean }) => {\n const token = requireToken();\n const client = createClient(token);\n const definitionId = await requireDefinitionId();\n\n console.log();\n console.log(chalk.bold(\"Creating version...\"));\n\n let result;\n try {\n result = await fluidOsApi.createVersion(client, definitionId);\n } catch (err) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to create version — \" +\n (err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n\n const version = result.version;\n\n if (!version?.id) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to create version — unexpected response.\",\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.green(\"Version created successfully.\"));\n console.log();\n console.log(chalk.gray(\"Version ID: \") + chalk.white(version.id));\n\n let active = version.active ?? false;\n\n if (options.activate) {\n console.log(\"Activating version...\");\n try {\n await fluidOsApi.updateVersion(client, definitionId, version.id, {\n version: { active: true },\n });\n } catch (err) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to activate version — \" +\n (err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n active = true;\n }\n\n console.log(\n chalk.gray(\"Active: \") +\n (active ? chalk.green(\"yes\") : chalk.gray(\"no\")),\n );\n console.log();\n });\n\nconst listVersionCommand = new Command(\"list\")\n .description(\"List all versions of the portal definition\")\n .action(async () => {\n const token = requireToken();\n const client = createClient(token);\n const definitionId = await requireDefinitionId();\n\n let result;\n try {\n result = await fluidOsApi.listVersions(client, definitionId);\n } catch (err) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to list versions — \" +\n (err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n\n const versions = result.version;\n\n if (!Array.isArray(versions) || versions.length === 0) {\n console.log();\n console.log(chalk.yellow(\"No versions found.\"));\n console.log(\n \"Run \" +\n chalk.cyan(\"`fluid portal version create`\") +\n \" to publish a version.\",\n );\n console.log();\n return;\n }\n\n console.log();\n\n // Print table header\n const COL_ID = \"Version ID\".padEnd(36);\n const COL_ACT = \"Active\".padEnd(6);\n const COL_PUB = \"Published\";\n console.log(chalk.gray(` ${COL_ID} ${COL_ACT} ${COL_PUB}`));\n\n for (const v of versions) {\n const id = String(v.id).padEnd(36);\n const active = v.active ? chalk.green(\"\\u2713\") + \" \" : \" \";\n const published = v.published_at\n ? new Date(v.published_at).toLocaleString()\n : \"\\u2014\";\n console.log(` ${id} ${active} ${published}`);\n }\n\n console.log();\n });\n\nconst activateVersionCommand = new Command(\"activate\")\n .description(\"Activate a specific version, making it the live version\")\n .argument(\"<version-id>\", \"The version ID to activate\")\n .option(\"-y, --yes\", \"Skip confirmation prompt\")\n .action(async (versionId: string, options: { yes?: boolean }) => {\n const numericVersionId = Number(versionId);\n if (!Number.isSafeInteger(numericVersionId) || numericVersionId < 1) {\n console.error(\n chalk.red(\"Error:\") + \" Version ID must be a positive integer.\",\n );\n process.exit(1);\n }\n\n const token = requireToken();\n const client = createClient(token);\n const definitionId = await requireDefinitionId();\n\n if (!options.yes) {\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: `Activate version ${versionId}? This will make it the live version.`,\n initial: false,\n });\n\n if (!confirm) {\n console.log(chalk.yellow(\"Aborted.\"));\n return;\n }\n }\n\n console.log();\n console.log(chalk.bold(\"Activating version...\"));\n\n try {\n await fluidOsApi.updateVersion(client, definitionId, numericVersionId, {\n version: { active: true },\n });\n } catch (err) {\n console.error(\n chalk.red(\"Error:\") +\n \" Failed to activate version — \" +\n (err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.green(\"Version \" + versionId + \" is now active.\"));\n console.log();\n });\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Version command group\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const versionCommand: Command = new Command(\"version\")\n .description(\"Manage portal definition versions\")\n .addCommand(createVersionCommand)\n .addCommand(listVersionCommand)\n .addCommand(activateVersionCommand);\n\nexport function registerVersionCommand(ctx: PluginContext): void {\n ctx.program.addCommand(versionCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport type { WidgetPublishOptions } from \"../types.js\";\nimport {\n createAuthenticatedWidgetPackageClient,\n printDryRunSessionPayload,\n printWidgetPublishError,\n printWidgetPublishSummary,\n printRuntimeOnlyNotice,\n publishWidgetRuntimeArtifacts,\n} from \"./widget-package-publish.js\";\n\nconst DEFAULT_OUT_DIR = \".fluid/widget-dist\";\n\nconst publishSubcommand = new Command(\"publish\")\n .description(\"Publish droplet-owned widget runtime artifacts\")\n .requiredOption(\n \"--droplet <uuid>\",\n \"Droplet UUID that owns the widget package\",\n )\n .option(\n \"-o, --out-dir <dir>\",\n \"Widget artifact output directory\",\n DEFAULT_OUT_DIR,\n )\n .option(\"--dry-run\", \"Build and validate upload payload without publishing\")\n .action(async (options: WidgetPublishOptions) => {\n const outDir = options.outDir ?? DEFAULT_OUT_DIR;\n const dryRun = options.dryRun === true;\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Widget Publish\"));\n console.log();\n printRuntimeOnlyNotice();\n console.log(chalk.gray(\"Droplet: \") + chalk.white(options.droplet));\n console.log(chalk.gray(\"Output: \") + chalk.cyan(outDir));\n if (dryRun)\n console.log(chalk.yellow(\"Dry run: no upload will be created.\"));\n console.log();\n\n const spinner = ora(\"Building droplet-owned widget package...\").start();\n\n try {\n const result = await publishWidgetRuntimeArtifacts({\n projectDir: process.cwd(),\n outDir,\n buildOwner: \"droplet\",\n uploadOwner: { kind: \"droplet\", uuid: options.droplet },\n dryRun,\n ...(dryRun ? {} : { client: createAuthenticatedWidgetPackageClient() }),\n });\n\n spinner.succeed(\"Built widget runtime artifacts\");\n\n if (dryRun) {\n console.log(\n chalk.yellow(\"Dry run complete — upload session was not requested.\"),\n );\n console.log();\n printDryRunSessionPayload(result.publish.sessionPayload);\n } else {\n console.log(chalk.green(\"Published widget package version.\"));\n }\n\n printWidgetPublishSummary({\n title: dryRun ? \"Widget publish dry run\" : \"Widget publish\",\n ownerLabel: `droplet ${options.droplet}`,\n outDir,\n packageId: result.build.packageId,\n version: result.build.version,\n artifactCount: result.request.artifacts.length,\n });\n } catch (err) {\n spinner.fail(\"Widget publish failed\");\n printWidgetPublishError(err);\n process.exit(1);\n }\n });\n\nexport const topLevelWidgetCommand: Command = new Command(\"widget\")\n .description(\"Publish Fluid widget packages\")\n .addCommand(publishSubcommand);\n\nexport function registerTopLevelWidgetCommand(ctx: PluginContext): void {\n ctx.program.addCommand(topLevelWidgetCommand);\n}\n","/**\n * @fluid-app/fluid-cli-portal\n *\n * Fluid CLI plugin for building portal applications.\n * Auto-discovered by @fluid-app/fluid-cli via the fluid-cli-* naming convention.\n */\n\nimport { Command } from \"commander\";\nimport type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { createCommand } from \"./commands/create.js\";\nimport { devCommand } from \"./commands/dev.js\";\nimport { buildCommand } from \"./commands/build.js\";\nimport { pullCommand } from \"./commands/pull.js\";\nimport { pushCommand } from \"./commands/push.js\";\nimport { cloneCommand } from \"./commands/clone.js\";\nimport { widgetCommand } from \"./commands/widget-create.js\";\nimport { deployCommand } from \"./commands/deploy.js\";\nimport { doctorCommand } from \"./commands/doctor.js\";\nimport { versionCommand } from \"./commands/version.js\";\n\nconst plugin: FluidPlugin = {\n name: \"fluid-cli-portal\",\n version: \"0.1.0\",\n async register(ctx: PluginContext) {\n const portal = new Command(\"portal\").description(\n \"Build and develop portal applications\",\n );\n\n portal.addCommand(createCommand);\n portal.addCommand(devCommand);\n portal.addCommand(buildCommand);\n portal.addCommand(pullCommand);\n portal.addCommand(pushCommand);\n portal.addCommand(cloneCommand);\n portal.addCommand(deployCommand);\n portal.addCommand(widgetCommand);\n portal.addCommand(doctorCommand);\n portal.addCommand(versionCommand);\n\n ctx.program.addCommand(portal);\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 TemplateVariables,\n SelectedPageTemplate,\n TemplateName,\n WidgetCreateOptions,\n PortalDeployOptions,\n WidgetPublishOptions,\n} from \"./types.js\";\n\nexport { TEMPLATES } 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\nexport {\n scaffoldFromGitHub,\n rewriteProjectName,\n writeFluidrcProfile,\n TEMPLATE_REPO_URL,\n CANONICAL_PROJECT_NAME,\n type ScaffoldFromGitHubOptions,\n} from \"./utils/scaffold-github.js\";\n\nexport {\n GitError,\n gitMissingError,\n isGitAvailable,\n ensureGitAvailable,\n gitClone,\n gitInit,\n isGitRepo,\n getOriginUrl,\n hasUncommittedChanges,\n gitFetch,\n gitAheadBehindVsFetchHead,\n gitMergeFastForward,\n gitAutoCommit,\n gitPush,\n} from \"./lib/git.js\";\n\nexport {\n createPortalGitCredential,\n portalGitCredentialPath,\n type PortalGitCredential,\n type GitCredentialOutcome,\n type CreatePortalGitCredentialOptions,\n} from \"./api/git-credential.js\";\n\nexport {\n runPortalGitSync,\n type PortalGitSyncResult,\n type PortalGitSyncOptions,\n} from \"./lib/git-sync.js\";\n\nexport { cloneCommand } from \"./commands/clone.js\";\n\n// Expose the standalone Command for create-portal-app\nexport {\n createCommand,\n createPortalProject,\n type CreatePortalProjectDependencies,\n} from \"./commands/create.js\";\nexport { pullCommand } from \"./commands/pull.js\";\nexport { pushCommand } from \"./commands/push.js\";\nexport { widgetCommand } from \"./commands/widget-create.js\";\nexport { topLevelWidgetCommand } from \"./commands/widget-publish.js\";\nexport { deployCommand } from \"./commands/deploy.js\";\n\n// The `fluid portal deploy --json` contract.\n//\n// Hosts that publish widgets (Mist) spawn that command and read one of these\n// off stdout. They must NOT import the publish library and run it in-process:\n// the publish path imports Vite, which drags chokidar and the native `fsevents`\n// binary into whatever bundles it — fatal for an Electron main bundle — and the\n// build has to resolve the target project's own toolchain anyway.\n//\n// These are types and small pure helpers. A consumer that only uses\n// `import type` links none of this package into its bundle.\nexport { publishWidgetRuntimeArtifacts } from \"./commands/widget-package-publish.js\";\nexport type { PublishWidgetRuntimeArtifactsOptions } from \"./commands/widget-package-publish.js\";\nexport {\n formatPackageKeyMismatch,\n preflightCompanyPackage,\n readCompanyWidgetPackage,\n resolvePublishVersion,\n type CompanyPackagePreflight,\n type CompanyWidgetPackage,\n type ConfiguredWidgetPackage,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nexport { versionCommand } from \"./commands/version.js\";\nexport { doctorCommand } from \"./commands/doctor.js\";\n\nexport {\n readMappings,\n writeMappings,\n deriveSlug,\n resolveSlugToId,\n resolveIdToSlug,\n updateMapping,\n removeMapping,\n type PortalMappings,\n type DefinitionMapping,\n type MappedResourceType,\n} from \"./utils/mappings.js\";\n\nexport {\n computeFileHash,\n readSnapshot,\n writeSnapshot,\n diffAgainstSnapshot,\n buildSnapshot,\n type Snapshot,\n type SnapshotDiff,\n type FileHash,\n type FileHashMap,\n} from \"./utils/snapshot.js\";\n\nexport {\n transformScreen,\n deriveScreenSlug,\n transformTheme,\n transformNavigation,\n transformNavigationItems,\n transformProfile,\n buildIdToSlugMap,\n buildNavigationIdToSlugMap,\n buildThemeIdToSlugMap,\n type LocalScreen,\n type LocalTheme,\n type LocalNavigation,\n type LocalNavigationItem,\n type LocalProfile,\n} from \"./utils/transform.js\";\n\nexport {\n categorizeChanges,\n validateCrossReferences,\n validatePortalSchemas,\n slugFromPath,\n type CategorizedChanges,\n type ValidationError,\n} from \"./utils/push-validation.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAOA,MAAa,YAAY,EACvB,SAAS,WACV;;;;;;;ACaD,MAAM,0BAA2D,EAKhE;;;;;AAMD,eAAsB,oBACpB,aACA,SAC+B;CAE/B,MAAM,YAAoC,EAAE;AAG5C,KAAI,wBAAwB,SAAS,EACnC,WAAU,KAAK;EACb,MAAM;EACN,MAAM;EACN,SAAS;EACT,cACE;EACF,SAAS,wBAAwB,KAAK,UAAU;GAC9C,OAAO,KAAK;GACZ,OAAO;IAAE,IAAI,KAAK;IAAI,MAAM,KAAK;IAAM,MAAM,KAAK;IAAM;GACxD,aAAa,KAAK;GACnB,EAAE;EACJ,CAAC;CAIJ,MAAM,mBAAmB,kBAAkB;AAC3C,KAAI,iBAAiB,SAAS,GAAG;EAC/B,MAAM,SAAS,kBAAkB;AACjC,YAAU,KAAK;GACb,MAAM;GACN,MAAM;GACN,SAAS;GACT,SAAS,iBAAiB,KAAK,UAAU;IACvC,OAAO,SAAS,QAAQ,OAAO,GAAG,KAAK,aAAa;IACpD,OAAO;IACR,EAAE;GACJ,CAAC;;AAIJ,KAAI,CAAC,QAAQ,YACX,WAAU,KAAK;EACb,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS;EACV,CAAC;AAKJ,KAAI,CAAC,QAAQ,MAAM,SAAS,UAAU,SAAS,EAC7C,QAAO;EACL,MAAM;EACN,aAAa;EACb,eAAe,EAAE;EACjB,aAAa,kBAAkB,EAAE,QAAQ;EAC1C;AAIH,KAAI,UAAU,WAAW,EACvB,QAAO;EACL,MAAM;EACN,aAAa,QAAQ,cAAc,QAAQ;EAC3C,eAAe,EAAE;EACjB,aAAa,kBAAkB,EAAE,QAAQ;EAC1C;CAIH,IAAI,YAAY;CAChB,MAAM,WAAW,MAAM,QAAQ,WAAW,EACxC,gBAAgB;AACd,cAAY;AACZ,SAAO;IAEV,CAAC;AAEF,KAAI,UACF,QAAO;CAIT,MAAM,gBACJ,SAAS,iBAAiB,EAAE;AAE9B,QAAO;EACL,MAAM;EACN,aAAa,QAAQ,cAAc,QAAS,SAAS,eAAe;EACpE;EACA,aACG,SAAS,eACV,kBAAkB,EAAE,QACpB;EACH;;;;ACrHH,MAAMA,gBAAc,QADC,cAAc,OAAO,KAAK,IAAI,CACV;AAMzC,MAAM,8BAAA;AAIN,MAAM,4BAAA;AAIN,MAAM,8BAAA;;;;;AASN,SAAS,kBAA0B;CACjC,IAAI,MAAMA;AACV,QAAO,CAAC,WAAW,KAAK,KAAK,eAAe,CAAC,EAAE;EAC7C,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK,OAAM,IAAI,MAAM,8BAA8B;AAClE,QAAM;;AAER,QAAO;;;;;AAUT,MAAa,qBAAqB;CAChC,mBAAmB;CACnB,cAAc;CACd,WAAW;CACX,YAAY;CACZ,eAAe;CAChB;;;;AAqBD,SAAS,cACP,MACA,SACA,MACA,OACiB;AACjB,QAAO;EAAE;EAAM;EAAS;EAAM;EAAO;;;;;;;;AAmBvC,SAAgB,iBAAiB,cAAqC;CAEpE,MAAM,eAAe,KADD,iBAAiB,EACE,YAAY;AACnD,QAAO;EACL,MAAM,KAAK,cAAc,OAAO;EAChC,SAAS,KAAK,cAAc,aAAa;EAC1C;;;;;AAMH,eAAe,SAAS,KAAa,UAAkB,KAAwB;CAC7E,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;CAC3D,MAAM,QAAkB,EAAE;AAE1B,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,KAAK,MAAM,KAAK;AACtC,MAAI,MAAM,aAAa,CACrB,OAAM,KAAK,GAAI,MAAM,SAAS,UAAU,QAAQ,CAAE;MAGlD,OAAM,KAAK,SAAS,MAAM,QAAQ,SAAS,EAAE,CAAC;;AAIlD,QAAO;;;;;;;AAQT,SAAS,gBACP,SACA,WACA,YACA,UACQ;AACR,KAAI,CAAC,WACH,QAAO;AAGT,KAAI;AAEF,SADiB,WAAW,QAAQ,QAAQ,CAC5B,UAAU;UACnB,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,QAAM,IAAI,MACR,6BAA6B,WAAW,QAAQ,aAAa,GAAG,IAAI,UACrE;;;AAaL,eAAe,8BAA4D;CAEzE,MAAM,aAAa,KADC,yBAAyB,EACR,kBAAkB;CACvD,MAAM,QAAQ,MAAM,SAAS,WAAW;AAExC,QAAO,QAAQ,IACb,MAAM,IAAI,OAAO,UAAU;EACzB,cAAc,KAAK,UAAU,KAAK;EAClC,SAAS,MAAM,SAAS,KAAK,YAAY,KAAK,EAAE,QAAQ;EACzD,EAAE,CACJ;;AAGH,SAAS,0BAAkC;CACzC,MAAM,uBAAuB,KAAK,iBAAiB,EAAE,MAAM,OAAO;AAClE,KAAI,WAAW,KAAK,sBAAsB,kBAAkB,CAAC,CAC3D,QAAO;CAGT,IAAI,MAAM,QAAQ,cAAc,OAAO,KAAK,QAAQ,uBAAuB,CAAC,CAAC;AAC7E,QAAO,CAAC,WAAW,KAAK,KAAK,eAAe,CAAC,EAAE;EAC7C,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IACb,OAAM,IAAI,MAAM,wCAAwC;AAC1D,QAAM;;AAER,QAAO;;AAGT,eAAe,iBACb,YACA,aACA,SACe;AACf,MAAK,MAAM,cAAc,aAAa;EACpC,MAAM,WAAW,KAAK,YAAY,WAAW;AAE7C,QAAM,MADU,QAAQ,SAAS,EACZ,EAAE,WAAW,MAAM,CAAC;AACzC,QAAM,UAAU,UAAU,SAAS,QAAQ;;;AAI/C,SAAS,mBAAmB,UAA4B;CACtD,MAAM,iBAAiB,kBAAkB,SAAS;CAClD,MAAM,aAAa,eAAe,QAAQ,OAAO,IAAI;AAErD,KAAI,eAAe,YAAa,QAAO,CAAC,aAAa,YAAY;AAEjE,KAAI,WAAW,WAAW,UAAU,CAClC,QAAO,CAAC,KAAK,WAAW,eAAe,EAAE,KAAK,WAAW,eAAe,CAAC;AAG3E,QAAO,CAAC,eAAe;;AAGzB,SAAS,kBAAkB,UAA0B;AACnD,KAAI,SAAS,SAAS,YAAY,CAChC,QAAO,SAAS,MAAM,GAAG,GAAoB;AAE/C,QAAO;;;;;;AAOT,eAAsB,aACpB,cACA,YACA,WACe;CACf,MAAM,QAAQ,MAAM,SAAS,aAAa;AAE1C,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,aAAa,KAAK,cAAc,KAAK;EAC3C,MAAM,aAAa,KAAK,SAAS,YAAY;EAC7C,MAAM,cAAc,mBAAmB,KAAK;EAM5C,MAAM,YAAY,gBAHF,MAAM,SAAS,YAAY,QAAQ,EAKjD,WACA,YACA,WACD;AAED,OAAK,MAAM,cAAc,aAAa;GACpC,MAAM,WAAW,KAAK,YAAY,WAAW;AAE7C,SAAM,MADU,QAAQ,SAAS,EACZ,EAAE,WAAW,MAAM,CAAC;AACzC,SAAM,UAAU,UAAU,WAAW,QAAQ;;;AAIjD,MAAK,MAAM,aAAa,MAAM,6BAA6B,CACzD,OAAM,iBACJ,YACA,mBAAmB,UAAU,aAAa,EAC1C,UAAU,QACX;;;;;AAOL,eAAsB,gBAAgB,MAAgC;AACpE,KAAI;AAEF,UADc,MAAM,KAAK,KAAK,EACjB,aAAa;SACpB;AACN,SAAO;;;;;;AAOX,eAAsB,WAAW,MAAgC;AAC/D,KAAI;AAEF,UADc,MAAM,KAAK,KAAK,EACjB,QAAQ;SACf;AACN,SAAO;;;;;;AAOX,eAAsB,WAAW,MAAgC;AAC/D,KAAI;AACF,QAAM,KAAK,KAAK;AAChB,SAAO;SACD;AACN,SAAO;;;;;;AAOX,eAAsB,gBAAgB,MAA6B;AACjE,OAAM,MAAM,MAAM,EAAE,WAAW,MAAM,CAAC;;;;;;AAOxC,eAAsB,gBAAiC;AACrD,QAAO,0BACL,CAAC,UAAU,MAAM,EACjB,4BACD;;;;;;;;;;;AAYH,SAAgB,2BAA2B,aAA6B;AAEtE,QAAO,QADU,QAAQ,YAAY,CAAC,QAAQ,OAAO,IAAI;;;;;;;;;AAW3D,eAAsB,gBAAiC;AACrD,QAAO,0BAA0B,CAAC,OAAO,OAAO,EAAE,0BAA0B;;;AAI9E,eAAsB,sBAAuC;AAC3D,QAAO,0BACL,CAAC,OAAO,SAAS,EACjB,4BACD;;AAGH,MAAM,mBAAmB;AACzB,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;AAqBhC,eAAsB,kCACpB,aACA,SACiB;AACjB,KAAI;AAKF,OAJc,MAAM,MAClB,GAAG,iBAAiB,GAAG,YAAY,GAAG,mBAAmB,QAAQ,IACjE,EAAE,QAAQ,YAAY,QAAQ,wBAAwB,EAAE,CACzD,EACS,WAAW,IAAK,QAAO;EAEjC,MAAM,SAAS,MAAM,MAAM,GAAG,iBAAiB,GAAG,YAAY,UAAU,EACtE,QAAQ,YAAY,QAAQ,wBAAwB,EACrD,CAAC;AACF,MAAI,CAAC,OAAO,GAAI,QAAO;EACvB,MAAM,MAAO,MAAM,OAAO,MAAM;AAChC,MAAI,OAAO,IAAI,YAAY,SAAU,QAAO;AAE5C,UAAQ,KACN,GAAG,YAAY,GAAG,QAAQ,wEAAwE,IAAI,QAAQ,WAC/G;AACD,SAAO,IAAI;SACL;AACN,SAAO;;;AAIX,eAAe,0BACb,qBACA,iBACiB;AACjB,KAAI,gBAAiB,QAAO;AAE5B,KAAI;EAGF,MAAM,kBAAkB,KADH,KADD,iBAAiB,EACE,MAAM,KAAK,EAGhD,GAAG,qBACH,eACD;EAED,MAAM,UAAU,MAAM,SAAS,iBAAiB,QAAQ;EACxD,MAAM,MAAM,KAAK,MAAM,QAAQ;AAC/B,MAAI,OAAO,IAAI,YAAY,SACzB,OAAM,IAAI,MAAM,sBAAsB,kBAAkB;AAE1D,SAAO,IAAI;UACJ,OAAO;AACd,QAAM,IAAI,MACR,4DAA4D,oBAAoB,KAAK,IAAI,IACzF,EAAE,OAAO,OAAO,CACjB;;;;;;AAWL,eAAsB,aACpB,MAC0C;AAC1C,KAAI;AAEF,SAAO,QADS,MAAM,SAAS,MAAM,QAAQ,CACtB;UAChB,KAAK;EACZ,MAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;AACjE,SAAO,QACL,cACE,mBAAmB,WACnB,wBAAwB,QACxB,MACA,MACD,CACF;;;;;;AAOL,eAAsB,cACpB,MACA,SACwC;AACxC,KAAI;AACF,QAAM,UAAU,MAAM,SAAS,QAAQ;AACvC,SAAO,QAAQ,KAAA,EAAU;UAClB,KAAK;EACZ,MAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;AACjE,SAAO,QACL,cACE,mBAAmB,YACnB,yBAAyB,QACzB,MACA,MACD,CACF;;;;;;AAOL,eAAsB,oBACpB,MACwC;AACxC,KAAI;AACF,QAAM,MAAM,MAAM,EAAE,WAAW,MAAM,CAAC;AACtC,SAAO,QAAQ,KAAA,EAAU;UAClB,KAAK;EACZ,MAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;AACjE,SAAO,QACL,cACE,mBAAmB,YACnB,+BAA+B,QAC/B,MACA,MACD,CACF;;;;;;AAOL,eAAsB,iBACpB,cACA,YACA,WACwC;AACxC,KAAI;EACF,MAAM,QAAQ,MAAM,SAAS,aAAa;AAE1C,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,aAAa,KAAK,cAAc,KAAK;GAC3C,MAAM,iBAAiB,KAAK,SAAS,YAAY;GACjD,MAAM,cAAc,mBAAmB,KAAK;GAM5C,MAAM,YAAY,gBAHF,MAAM,SAAS,YAAY,QAAQ,EAKjD,WACA,gBACA,WACD;AAED,QAAK,MAAM,cAAc,aAAa;IACpC,MAAM,WAAW,KAAK,YAAY,WAAW;AAE7C,UAAM,MADU,QAAQ,SAAS,EACZ,EAAE,WAAW,MAAM,CAAC;AACzC,UAAM,UAAU,UAAU,WAAW,QAAQ;;;AAIjD,OAAK,MAAM,aAAa,MAAM,6BAA6B,CACzD,OAAM,iBACJ,YACA,mBAAmB,UAAU,aAAa,EAC1C,UAAU,QACX;AAGH,SAAO,QAAQ,KAAA,EAAU;UAClB,KAAK;EACZ,MAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;AACjE,SAAO,QACL,cACE,mBAAmB,eACnB,gCAAgC,aAAa,MAAM,cACnD,cACA,MACD,CACF;;;;;;;AAQL,eAAsB,oBAEpB;AACA,KAAI;EAIF,MAAM,iBAAiB,KADF,KADD,iBAAiB,EACE,MAAM,KAAK,EACR,UAAU,OAAO,eAAe;EAE1E,MAAM,UAAU,MAAM,SAAS,gBAAgB,QAAQ;EAEvD,MAAM,UADM,KAAK,MAAM,QAAQ,CACX;AAEpB,MAAI,YAAY,KAAA,EACd,QAAO,QACL,cACE,mBAAmB,WACnB,qDACA,eACD,CACF;AAGH,SAAO,QAAQ,IAAI,UAAU;UACtB,KAAK;EACZ,MAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;AACjE,SAAO,QACL,cACE,mBAAmB,cACnB,mCACA,KAAA,GACA,MACD,CACF;;;;;;;;ACrlBL,SAAgB,oBAA4B;AAC1C,QAAO;;;;;AAMT,SAAgB,cAAc,QAAwB;AACpD,QAAO,YAAY;;;;;AAMrB,eAAsB,kBACpB,MACA,KACe;AACf,OAAM,MAAM,QAAQ,MAAM;EACxB;EACA,OAAO;EACR,CAAC;;;;;AAMJ,eAAsB,oBAAoB,KAA4B;AACpE,OAAM,kBAAkB,CAAC,UAAU,EAAE,IAAI;;;;;;;;;;;;;;;;;;;;;ACG3C,IAAa,WAAb,cAA8B,MAAM;CAClC;CAEA,YAAY,SAAiB,WAAW,GAAG;AACzC,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,WAAW;;;;;;;;;AAUpB,SAAgB,gBAAgB,KAA+B;AAE7D,KADc,KAA2C,SAC5C,SAAU,QAAO;AAC9B,QAAO,IAAI,SACT,uRAKA,IACD;;;;;;;AAQH,eAAsB,iBAAmC;AACvD,KAAI;AACF,QAAM,MAAM,OAAO,CAAC,YAAY,CAAC;AACjC,SAAO;SACD;AACN,SAAO;;;;AAKX,eAAsB,qBAAoC;AACxD,KAAI,CAAE,MAAM,gBAAgB,CAC1B,OAAM,IAAI,SACR,oKAED;;;;;;;;;;AAYL,SAAS,uBAAuB,WAI9B;CACA,MAAM,IAAI,IAAI,IAAI,UAAU;CAC5B,MAAM,WAAW,mBAAmB,EAAE,SAAS;CAC/C,MAAM,WAAW,mBAAmB,EAAE,SAAS;AAC/C,GAAE,WAAW;AACb,GAAE,WAAW;AACb,QAAO;EAAE,UAAU,EAAE,UAAU;EAAE;EAAU;EAAU;;;AAIvD,SAAgB,kBAAkB,WAA2B;AAC3D,QAAO,uBAAuB,UAAU,CAAC;;;;;;;;;;;;;AAc3C,IAAI,cAA6B;AACjC,SAAS,sBAA8B;AACrC,KAAI,eAAe,WAAW,YAAY,CAAE,QAAO;CACnD,MAAM,MAAM,KAAK,KAAK,GAAG,QAAQ,EAAE,wBAAwB,QAAQ,MAAM;AACzE,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;CACtE,MAAM,IAAI,KAAK,KAAK,KAAK,aAAa;AACtC,eACE,GACA,uIACA,EAAE,MAAM,KAAO,CAChB;AACD,WAAU,GAAG,IAAM;AACnB,eAAc;AACd,QAAO;;;;;;;;;AAUT,SAAS,aAAa,WAGpB;CACA,MAAM,EAAE,UAAU,aAAa,uBAAuB,UAAU;AAChE,QAAO;EACL,UAAU,kBAAkB,UAAU;EACtC,KAAK;GACH,GAAG,QAAQ;GACX,aAAa,qBAAqB;GAClC,qBAAqB;GACrB,uBAAuB;GACvB,uBAAuB;GACxB;EACF;;;;AAKH,SAAS,WAAW,KAAc,MAAwB;CACxD,MAAM,UAAU,gBAAgB,IAAI;AACpC,KAAI,QAAS,QAAO;CACpB,MAAM,IAAI;AACV,QAAO,IAAI,SACT,OAAO,KAAK,SAAS,EAAE,YAAY,OAAO,UAAU,EAAE,SAAS,KAAK,MACpE,EAAE,YAAY,EACf;;;;;;;;;;;AAYH,eAAsB,SACpB,WACA,WACA,OAA2B,EAAE,EACd;CACf,MAAM,EAAE,UAAU,QAAQ,aAAa,UAAU;CACjD,MAAM,YAAY,KAAK,QAAQ,CAAC,WAAW,OAAO,KAAK,MAAM,CAAC,GAAG,EAAE;AACnE,KAAI;AACF,QAAM,MACJ,OACA;GACE;GACA;GACA;GACA;GACA,GAAG;GACH;GACA;GACD,EACD;GAAE;GAAK,OAAO;IAAC;IAAU;IAAW;IAAU;GAAE,CACjD;UACM,KAAK;AACZ,QAAM,WAAW,KAAK,QAAQ;;;;;;AAOlC,eAAsB,QAAQ,KAA+B;AAC3D,KAAI;AACF,QAAM,MAAM,OAAO,CAAC,QAAQ,UAAU,EAAE,EAAE,KAAK,CAAC;AAChD,SAAO;SACD;AACN,SAAO;;;;AAKX,eAAsB,UAAU,KAA+B;AAC7D,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MACvB,OACA,CAAC,aAAa,wBAAwB,EACtC,EAAE,KAAK,CACR;AACD,SAAO,OAAO,MAAM,KAAK;SACnB;AACN,SAAO;;;;AAKX,eAAsB,aAAa,KAAqC;AACtE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MAAM,OAAO;GAAC;GAAU;GAAW;GAAS,EAAE,EACrE,KACD,CAAC;AACF,SAAO,OAAO,MAAM;SACd;AACN,SAAO;;;;;;;;AASX,eAAsB,sBAAsB,KAA+B;AACzE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MAAM,OAAO,CAAC,UAAU,cAAc,EAAE,EAAE,KAAK,CAAC;AACzE,SAAO,OAAO,MAAM,CAAC,SAAS;SACxB;AACN,SAAO;;;;;;;;AASX,eAAsB,SACpB,KACA,WACA,SAAS,QACM;CACf,MAAM,EAAE,UAAU,QAAQ,aAAa,UAAU;AACjD,KAAI;AACF,QAAM,MACJ,OACA;GAAC;GAAM;GAAsB;GAAS;GAAW;GAAU;GAAO,EAClE;GAAE;GAAK;GAAK,CACb;UACM,KAAK;AACZ,QAAM,WAAW,KAAK,QAAQ;;;;;;;AAQlC,eAAsB,0BACpB,KAC4C;AAC5C,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MACvB,OACA;GAAC;GAAY;GAAgB;GAAW;GAAoB,EAC5D,EAAE,KAAK,CACR;EACD,MAAM,CAAC,GAAG,KAAK,OAAO,MAAM,CAAC,MAAM,MAAM;EACzC,MAAM,QAAQ,OAAO,SAAS,KAAK,KAAK,GAAG;EAC3C,MAAM,SAAS,OAAO,SAAS,KAAK,KAAK,GAAG;AAC5C,SAAO;GACL,OAAO,OAAO,SAAS,MAAM,GAAG,QAAQ;GACxC,QAAQ,OAAO,SAAS,OAAO,GAAG,SAAS;GAC5C;SACK;AACN,SAAO;GAAE,OAAO;GAAG,QAAQ;GAAG;;;;AAKlC,eAAsB,WAAW,KAA+B;AAC9D,KAAI;AACF,QAAM,MAAM,OAAO;GAAC;GAAa;GAAY;GAAO,EAAE,EAAE,KAAK,CAAC;AAC9D,SAAO;SACD;AACN,SAAO;;;;;;;;AASX,eAAsB,sBACpB,KACA,WACA,SAAS,QACS;CAClB,MAAM,EAAE,UAAU,QAAQ,aAAa,UAAU;AACjD,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MACvB,OACA;GACE;GACA;GACA;GACA;GACA;GACA,cAAc;GACf,EACD;GAAE;GAAK;GAAK,CACb;AACD,SAAO,OAAO,MAAM,CAAC,SAAS;UACvB,KAAK;AACZ,QAAM,WAAW,KAAK,YAAY;;;;;;;;;;;;;;;;;;;;AAqBtC,eAAsB,kBAAkB,KAA8B;AACpE,KAAI;AACF,QAAM,MAAM,OAAO;GAAC;GAAS;GAAW;GAAa,EAAE,EAAE,KAAK,CAAC;EAC/D,MAAM,EAAE,WAAW,MAAM,MACvB,OACA;GAAC;GAAY;GAAW;GAAa,EACrC,EAAE,KAAK,CACR;EACD,MAAM,QAAQ,OAAO,SAAS,OAAO,MAAM,EAAE,GAAG;AAChD,SAAO,OAAO,SAAS,MAAM,GAAG,QAAQ;UACjC,KAAK;AACZ,QAAM,WAAW,KAAK,uBAAuB;;;;;;;;;AAUjD,SAAgB,uBAAuB,WAAkC;CAIvE,MAAM,MAAM,UAAU,MAAM,sCAAsC;AAClE,KAAI,MAAM,MAAM,IAAI,MAAM,CAAC,UAAU,SAAS,MAAM,EAAE;EACpD,MAAM,OAAO,IAAI,GAAG,QAAQ,UAAU,GAAG,CAAC,QAAQ,QAAQ,GAAG;AAC7D,SAAO,GAAG,IAAI,GAAG,GAAG,OAAO,aAAa;;AAI1C,KAAI,UAAU,WAAW,IAAI,IAAI,UAAU,WAAW,IAAI,CACxD,QAAO,UACJ,QAAQ,UAAU,GAAG,CACrB,QAAQ,QAAQ,GAAG,CACnB,aAAa;AAElB,KAAI;EACF,MAAM,IAAI,IAAI,IAAI,UAAU;EAC5B,MAAM,OAAO,EAAE,SACZ,QAAQ,UAAU,GAAG,CACrB,QAAQ,QAAQ,GAAG,CACnB,QAAQ,QAAQ,GAAG;AACtB,SAAO,GAAG,EAAE,KAAK,GAAG,OAAO,aAAa;SAClC;AACN,SAAO;;;;;;;;AASX,eAAsB,+BACpB,KACkB;AAClB,KAAI;AACF,QAAM,MAAM,OAAO;GAAC;GAAc;GAAQ;GAAa,EAAE,EAAE,KAAK,CAAC;AACjE,SAAO;SACD;AACN,SAAO;;;;;AAMX,eAAsB,oBAAoB,KAA4B;AACpE,KAAI;AACF,QAAM,MAAM,OAAO;GAAC;GAAS;GAAa;GAAa,EAAE,EAAE,KAAK,CAAC;UAC1D,KAAK;AACZ,QAAM,WAAW,KAAK,qBAAqB;;;;;;;;;;;;;;;;;;;AAoB/C,MAAM,uBAAuB,CAAC,SAAS,iBAAiB;;;;;;;;;;;;;;;;;;;;AAqBxD,SAAgB,yBAAyB,KAAmB;AAC1D,KAAI;EACF,MAAM,SAAS,KAAK,KAAK,KAAK,OAAO;AACrC,MAAI,CAAC,WAAW,OAAO,CAAE;EACzB,MAAM,UAAU,KAAK,KAAK,QAAQ,OAAO;AACzC,MAAI,CAAC,WAAW,QAAQ,CAAE,WAAU,SAAS,EAAE,WAAW,MAAM,CAAC;EACjE,MAAM,cAAc,KAAK,KAAK,SAAS,UAAU;EACjD,MAAM,WAAW,WAAW,YAAY,GACpC,aAAa,aAAa,OAAO,GACjC;EACJ,MAAM,gBAAgB,IAAI,IACxB,SACG,MAAM,KAAK,CACX,KAAK,SAAS,KAAK,MAAM,CAAC,CAC1B,OAAO,QAAQ,CACnB;EACD,MAAM,UAAU,qBAAqB,QAClC,UAAU,CAAC,cAAc,IAAI,MAAM,CACrC;AACD,MAAI,QAAQ,WAAW,EAAG;AAE1B,gBACE,aACA,GAAG,WAHuB,SAAS,SAAS,KAAK,CAAC,SAAS,SAAS,KAAK,GAGrC,OAAO,KAAK,QAAQ,KAAK,KAAK,CAAC,KACnE,OACD;SACK;;;;;;;;;;;;;AAiBV,eAAe,gBAAgB,KAA8B;AAC3D,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MAAM,OAAO,CAAC,aAAa,kBAAkB,EAAE,EACtE,KACD,CAAC;EACF,MAAM,OAAO,OAAO,MAAM;AAC1B,MAAI,KAAK,SAAS,EAAG,QAAO;SACtB;AAGR,QAAO;;;;;;;;;;;;;AAcT,eAAsB,yBAAyB,KAA4B;AACzE,0BAAyB,MAAM,gBAAgB,IAAI,CAAC;;;AAItD,eAAe,uBAAuB,UAAqC;AACzE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MAAM,OAAO;GAAC;GAAY;GAAM;GAAgB,EAAE,EACzE,KAAK,UACN,CAAC;AACF,SAAO,OAAO,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE;SACtD;AACN,SAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4Bb,eAAsB,yBACpB,KACA,SACkB;CAClB,MAAM,WAAW,MAAM,gBAAgB,IAAI;AAC3C,MAAK,MAAM,uBAAuB,SAAS,EAAE,WAAW,EAAG,QAAO;CAIlE,IAAI;AACJ,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MAAM,OAAO,CAAC,aAAa,OAAO,EAAE,EAC3D,KAAK,UACN,CAAC;AACF,SAAO,OAAO,MAAM;AACpB,MAAI,CAAC,KAAM,QAAO;SACZ;AACN,SAAO;;CAGT,MAAM,WAAW,KAAK,KACpB,GAAG,QAAQ,EACX,sBAAsB,QAAQ,IAAI,GAAG,YAAY,CAAC,QACnD;CACD,MAAM,WAAW;EAAE,GAAG,QAAQ;EAAK,gBAAgB;EAAU;CAC7D,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,OAAO,CAAC,aAAa,KAAK,EAAE;GAAE,KAAK;GAAU,KAAK;GAAU,CAAC;AACzE,QAAM,MACJ,OACA;GAAC;GAAM;GAAM;GAAM;GAAY;GAAoB;GAAM;GAAgB,EACzE;GAAE,KAAK;GAAU,KAAK;GAAU,CACjC;EACD,MAAM,EAAE,QAAQ,SAAS,MAAM,MAAM,OAAO,CAAC,aAAa,EAAE;GAC1D,KAAK;GACL,KAAK;GACN,CAAC;EACF,MAAM,EAAE,QAAQ,QAAQ,MAAM,MAC5B,OACA;GAAC;GAAe,KAAK,MAAM;GAAE;GAAM;GAAM;GAAM;GAAQ,EACvD;GAAE,KAAK;GAAU,KAAK;IAAE,GAAG;IAAU,GAAI,MAAM,YAAY,SAAS;IAAG;GAAE,CAC1E;AACD,cAAY,IAAI,MAAM;AACtB,MAAI,CAAC,UAAW,QAAO;UAChB,KAAK;EACZ,MAAM,UAAU,gBAAgB,IAAI;AACpC,MAAI,QAAS,OAAM;AACnB,QAAM,WAAW,KAAK,oBAAoB;WAClC;AACR,SAAO,UAAU,EAAE,OAAO,MAAM,CAAC;;AAMnC,KAAI;AACF,QAAM,MAAM,OAAO;GAAC;GAAc;GAAQ;GAAW;GAAK,EAAE,EAC1D,KAAK,UACN,CAAC;UACK,KAAK;AACZ,QAAM,WAAW,KAAK,oBAAoB;;AAE5C,KAAI;AACF,QAAM,MACJ,OACA;GAAC;GAAM;GAAM;GAAM;GAAY;GAAoB;GAAM;GAAgB,EACzE,EAAE,KAAK,UAAU,CAClB;SACK;AAGR,QAAO;;;;;AAMT,eAAe,YAAY,KAAyC;CAClE,MAAM,MAAyB,EAAE;CACjC,MAAM,OAAO,OAAO,QAAkC;AACpD,MAAI;AAEF,WADU,MAAM,MAAM,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,KAAK,CAAC,EAC7C,OAAO,MAAM,CAAC,SAAS;UAC1B;AACN,UAAO;;;AAGX,KAAI,CAAE,MAAM,KAAK,YAAY,EAAG;AAC9B,MAAI,qBAAqB;AACzB,MAAI,wBAAwB;;AAE9B,KAAI,CAAE,MAAM,KAAK,aAAa,EAAG;AAC/B,MAAI,sBAAsB;AAC1B,MAAI,yBAAyB;;AAE/B,QAAO;;;;AAKT,eAAe,oBAAoB,KAAgC;CACjE,IAAI,UAAU;CACd,IAAI,WAAW;AACf,KAAI;AAEF,aADU,MAAM,MAAM,OAAO,CAAC,UAAU,YAAY,EAAE,EAAE,KAAK,CAAC,EAClD,OAAO,MAAM,CAAC,SAAS;SAC7B;AAGR,KAAI;AAEF,cADU,MAAM,MAAM,OAAO,CAAC,UAAU,aAAa,EAAE,EAAE,KAAK,CAAC,EAClD,OAAO,MAAM,CAAC,SAAS;SAC9B;AAGR,QAAO,CACL,GAAI,UAAU,EAAE,GAAG,CAAC,MAAM,6BAA6B,EACvD,GAAI,WAAW,EAAE,GAAG,CAAC,MAAM,8BAA8B,CAC1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EH,eAAe,cAAc,KAAqC;AAChE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,MACvB,OACA;GAAC;GAAQ;GAAY;GAAiB;GAAK,EAC3C,EAAE,KAAK,CACR;AACD,SAAO,iBAAiB,OAAO;SACzB;AACN,SAAO,EAAE;;;AAIb,eAAsB,cACpB,KACA,SACA,UAAgC,EAAE,EACnB;CACf,MAAM,WAAW,MAAM,gBAAgB,IAAI;AAG3C,0BAAyB,SAAS;CAElC,MAAM,UAAU,QAAQ,iBACpB;EAAC;EAAO;EAAoB;EAAI,GAChC,CAAC,OAAO,KAAK;AACjB,KAAI;AACF,QAAM,MAAM,OAAO,SAAS,EAAE,KAAK,CAAC;UAC7B,KAAK;AACZ,QAAM,WAAW,KAAK,MAAM;;AAS9B,KAAI;AACF,QAAM,MAAM,OAAO;GAAC;GAAS;GAAM;GAAM;GAAe;GAAQ,EAAE,EAAE,KAAK,CAAC;SACpE;AAKR,KAAI;AACF,QAAM,MACJ,OACA;GAAC;GAAM;GAAM;GAAM;GAAY;GAAoB;GAAM;GAAgB,EACzE,EAAE,KAAK,UAAU,CAClB;SACK;CAOR,MAAM,UACJ,OAAO,YAAY,WACf,UACA,MAAM,QAAQ,MAAM,cAAc,IAAI,CAAC;CAC7C,MAAM,eAAe,MAAM,oBAAoB,IAAI;AACnD,KAAI;AAQF,QAAM,MAAM,OAAO;GAAC,GAAG;GAAc;GAAU;GAAM;GAAQ,EAAE;GAC7D;GACA,OAAO;IAAC;IAAU;IAAQ;IAAO;GAClC,CAAC;UACK,KAAK;EACZ,MAAM,UAAU,gBAAgB,IAAI;AACpC,MAAI,QAAS,OAAM;EACnB,MAAM,IAAI;EAMV,MAAM,UAAU,GAAG,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS,GAAG,IAAI,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS;AAC9G,MAAI,gDAAgD,KAAK,QAAQ,CAC/D;AAEF,QAAM,WAAW,KAAK,SAAS;;;;;;;;AASnC,eAAsB,QACpB,KACA,WACA,SAAS,QACM;CACf,MAAM,EAAE,UAAU,QAAQ,aAAa,UAAU;AACjD,KAAI;AACF,QAAM,MACJ,OACA;GACE;GACA;GACA;GACA;GACA;GACA;GACA,QAAQ;GACT,EACD;GAAE;GAAK;GAAK,OAAO;IAAC;IAAU;IAAW;IAAU;GAAE,CACtD;UACM,KAAK;AACZ,QAAM,WAAW,KAAK,OAAO;;;;;;;;;;;;;;;;;;;ACx2BjC,MAAa,oBACX;;;;;;;AAQF,MAAa,yBAAyB;;;;;;AAOtC,MAAM,oBAAoB,IAAI,IAAI;CAAC;CAAQ;CAAgB;CAAiB,CAAC;;;;AAK7E,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,iBAAiB,MAAuB;CAC/C,MAAM,MAAM,KAAK,YAAY,IAAI;AACjC,KAAI,MAAM,EAAG,QAAO;AACpB,QAAO,gBAAgB,IAAI,KAAK,MAAM,IAAI,CAAC;;;AAI7C,eAAe,UAAU,KAAgC;CACvD,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;CAC3D,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,kBAAkB,IAAI,MAAM,KAAK,CAAE;EACvC,MAAM,OAAO,KAAK,KAAK,MAAM,KAAK;AAClC,MAAI,MAAM,aAAa,CACrB,OAAM,KAAK,GAAI,MAAM,UAAU,KAAK,CAAE;MAEtC,OAAM,KAAK,KAAK;;AAGpB,QAAO;;;AAIT,SAAS,aAAa,OAAuB;AAC3C,QAAO,MAAM,QAAQ,uBAAuB,OAAO;;;;;;;AAQrD,eAAsB,mBACpB,YACA,SACe;AACf,KAAI,YAAA,uBAAoC;CACxC,MAAM,QAAQ,IAAI,OAAO,aAAa,uBAAuB,EAAE,IAAI;CAEnE,MAAM,QAAQ,MAAM,UAAU,WAAW;AACzC,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,iBAAiB,KAAK,CAAE;EAC7B,MAAM,UAAU,MAAM,SAAS,MAAM,QAAQ;AAC7C,MAAI,CAAC,QAAQ,SAAA,uBAAgC,CAAE;AAG/C,QAAM,UACJ,MACA,QAAQ,QAAQ,aAAa,QAAQ,EACrC,QACD;;;;;AAML,eAAsB,oBACpB,YACA,aACe;CACf,MAAM,cAAc,KAAK,YAAY,WAAW;AAChD,KAAI,CAAC,WAAW,YAAY,CAAE;AAC9B,OAAM,UACJ,aACA,KAAK,UAAU,EAAE,SAAS,aAAa,EAAE,MAAM,EAAE,GAAG,MACpD,QACD;;;;;;;;;;AAmBH,eAAsB,mBACpB,SACe;CACf,MAAM,EAAE,YAAY,SAAS,gBAAgB;AAI7C,OAAM,SAHU,QAAQ,WAAA,2DAGA,YAAY,EAAE,OAAO,GAAG,CAAC;AAGjD,OAAM,GAAG,KAAK,YAAY,OAAO,EAAE;EAAE,WAAW;EAAM,OAAO;EAAM,CAAC;AAEpE,OAAM,mBAAmB,YAAY,QAAQ;AAC7C,OAAM,oBAAoB,YAAY,YAAY;CAIlD,MAAM,aAAa,KAAK,YAAY,eAAe;AACnD,KAAI;AACF,OAAK,MAAM,KAAK,WAAW,EAAE,QAAQ,CACnC,OAAM,SAAS,YAAY,KAAK,YAAY,OAAO,CAAC;SAEhD;;;;AC/HV,MAAM,yCAAyC;CAC7C;CACA;CACA;CACD;AAED,MAAa,gBAAyB,IAAI,QAAQ,SAAS,CACxD,YAAY,wCAAwC,CACpD,SAAS,cAAc,oCAAoC,CAC3D,OACC,kBACA,yDACD,CACA,OACC,0BACA,uDACD,CACA,OACC,WACA,wEACD,CACA,OACC,qBACA,kEACD,CACA,OAAO,OAAO,SAAiB,YAA2B;AACzD,KAAI;AACF,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,KAAK,0CAA0C,CAAC;AAClE,UAAQ,KAAK;AAGb,MAAI,CAAC,eAAe,KAAK,QAAQ,EAAE;AACjC,WAAQ,MACN,MAAM,IACJ,4EACD,CACF;AACD,WAAQ,KAAK,EAAE;;AAQjB,MAAI,MAAM,gBAJS,KACjB,QAAQ,QAAQ,aAAa,QAAQ,KAAK,CAAC,EAC3C,QACD,CACoC,EAAE;AACrC,WAAQ,MACN,MAAM,IAAI,qBAAqB,QAAQ,kBAAkB,CAC1D;AACD,WAAQ,KAAK,EAAE;;EAIjB,MAAM,SAAS,MAAM,oBAAoB,SAAS,QAAQ;AAC1D,MAAI,CAAC,QAAQ;AACX,WAAQ,KAAK;AACb,WAAQ,IAAI,MAAM,OAAO,YAAY,CAAC;AACtC,WAAQ,KAAK,EAAE;;AAGjB,UAAQ,KAAK;AACb,QAAM,oBAAoB,SAAS,SAAS,OAAO;UAC5C,OAAO;AACd,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAC1D;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;EAEjB;AAEJ,eAAsB,oBACpB,SACA,SACA,QACA,eAAgD,wCACjC;CACf,MAAM,aAAa,KAAK,QAAQ,QAAQ,aAAa,QAAQ,KAAK,CAAC,EAAE,QAAQ;CAC7E,MAAM,UAAU,QAAQ,UAAU;AAElC,KAAI,QAAQ,mBAAmB,MAAM;AACnC,MAAI,QACF,OAAM,IAAI,MAAM,oDAAoD;AAEtE,MAAI,OAAO,cAAc,SAAS,EAChC,OAAM,IAAI,MACR,iEACD;AAEH,MAAI,CAAE,MAAM,aAAa,gBAAgB,CACvC,OAAM,IAAI,MACR,0FACD;EAGH,MAAM,UAAU,IAAI,2CAA2C,CAAC,OAAO;AACvE,MAAI;AACF,SAAM,aAAa,mBAAmB;IACpC;IACA,SAAS,OAAO;IAChB,aAAa,OAAO;IACrB,CAAC;AACF,WAAQ,QAAQ,2BAA2B;WACpC,OAAO;AACd,WAAQ,KAAK,8CAA8C;AAC3D,SAAM,GAAG,YAAY;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC,CAAC,YAAY,GAAG;AACtE,SAAM;;OAGR,OAAM,oBAAoB,YAAY,QAAQ,SAAS,KAAK;AAG9D,KAAI,OAAO,aAAa;EACtB,MAAM,iBAAiB,IAAI,uCAAuC,CAAC,OAAO;AAC1E,MAAI;AACF,SAAM,aAAa,oBAAoB,WAAW;AAClD,kBAAe,QAAQ,yBAAyB;WACzC,OAAO;AACd,kBAAe,KAAK,iCAAiC;AACrD,WAAQ,KAAK;AACb,WAAQ,IAAI,MAAM,OAAO,gCAAgC,WAAW,GAAG,CAAC;AACxE,WAAQ,IAAI,MAAM,OAAO,6CAA6C,CAAC;AACvE,WAAQ,IAAI,MAAM,KAAK,QAAQ,aAAa,WAAW,GAAG,CAAC;AAC3D,WAAQ,IAAI,MAAM,KAAK,iBAAiB,CAAC;AACzC,SAAM;;;AAIV,SAAQ,KAAK;AACb,KAAI,CAAC,OAAO,aAAa;AACvB,UAAQ,IACN,MAAM,OACJ,2FACD,CACF;AACD,UAAQ,KAAK;;AAEf,SAAQ,IAAI,MAAM,MAAM,KAAK,WAAW,GAAG,YAAY,MAAM,KAAK,QAAQ,GAAG;AAC7E,SAAQ,KAAK;AACb,SAAQ,IAAI,cAAc;AAC1B,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,QAAQ,aAAa,WAAW,GAAG,CAAC;AAC3D,KAAI,CAAC,OAAO,YACV,SAAQ,IAAI,MAAM,KAAK,iBAAiB,CAAC;AAE3C,SAAQ,IAAI,MAAM,KAAK,KAAK,cAAc,MAAM,GAAG,CAAC;AACpD,SAAQ,KAAK;AACb,SAAQ,IACN,eAAe,MAAM,KAAK,wBAAwB,GAAG,oBACtD;AACD,SAAQ,IACN,MAAM,IACJ,sEACD,CACF;AACD,SAAQ,KAAK;AACb,KAAI,CAAC,OAAO,aAAa;AACvB,UAAQ,IACN,MAAM,OACJ,WACE,MAAM,KAAK,cAAc,GACzB,iBACA,MAAM,KAAK,WAAW,GACtB,2BACH,CACF;AACD,UAAQ,KAAK;;AAEf,SAAQ,IACN,SACE,MAAM,KAAK,YAAY,GACvB,eACA,MAAM,KAAK,UAAU,GACrB,6CACH;AACD,SAAQ,KAAK;;AAGf,SAAS,aAAa,YAA4B;AAChD,QAAO,SAAS,QAAQ,KAAK,EAAE,WAAW,IAAI;;;;;;;;;;AAWhD,eAAe,oBACb,YACA,QACA,SACA,cACe;CAEf,MAAM,gBAAgB,iBAAiB,UAAU;AACjD,KAAI,CAAE,MAAM,gBAAgB,cAAc,KAAK,CAC7C,OAAM,IAAI,MAAM,0BAA0B;AAE5C,KAAI,CAAE,MAAM,gBAAgB,cAAc,QAAQ,CAChD,OAAM,IAAI,MAAM,6BAA6B;CAM/C,IAAI;CACJ,IAAI;CACJ,MAAM,CAAC,YAAY,oBAAoB,MAAM,QAAQ,IAAI,CACvD,eAAe,CAAC,MAAM,YACpB,kCAAkC,wBAAwB,QAAQ,CACnE,EACD,qBAAqB,CAAC,MAAM,YAC1B,kCAAkC,+BAA+B,QAAQ,CAC1E,CACF,CAAC;AAEF,KAAI,SAAS;EAYX,MAAM,eAAe,KADF,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACpB,MAAM,MAAM,MAAM,KAAK;EAC7D,MAAM,UAAU,KAAK,cAAc,YAAY,UAAU,MAAM;EAC/D,MAAM,WAAW,KAAK,cAAc,YAAY,UAAU,OAAO;AAEjE,MACE,CAAE,MAAM,gBAAgB,QAAQ,IAChC,CAAE,MAAM,gBAAgB,SAAS,CAEjC,OAAM,IAAI,MACR,wHAED;AAGH,eAAa,2BAA2B,QAAQ;AAChD,qBAAmB,2BAA2B,SAAS;AACvD,UAAQ,IAAI,MAAM,KAAK,wCAAwC,CAAC;OAEhE,cAAa,MAAM,eAAe,CAAC,MAAM,YACvC,kCAAkC,yBAAyB,QAAQ,CACpE;CAGH,MAAM,UAAU,IACd,eACI,kCACA,2CACL,CAAC,OAAO;AACT,KAAI,aACF,KAAI;AACF,QAAM,gBAAgB,WAAW;AACjC,UAAQ,QAAQ,4BAA4B;UACrC,OAAO;AACd,UAAQ,KAAK,qCAAqC;AAClD,QAAM;;UAEC,CAAE,MAAM,gBAAgB,WAAW,CAC5C,OAAM,IAAI,MAAM,mCAAmC,aAAa;CAIlE,MAAM,oBAAoB;EACxB,aAAa,OAAO;EACpB;EACA;EACA;EACA;EACA,eAAe,OAAO;EACtB,kBAAkB,OAAO,cAAc,SAAS;EAChD,aAAa,OAAO;EACrB;AAED,SAAQ,MAAM,4BAA4B;AAC1C,KAAI;AACF,QAAM,aAAa,cAAc,MAAM,YAAY,kBAAkB;AACrE,QAAM,aAAa,cAAc,SAAS,YAAY,kBAAkB;EAGxE,MAAM,iBAAiB,KAAK,YAAY,eAAe;AACvD,MAAI,MAAM,WAAW,eAAe,CAClC,OAAM,SAAS,gBAAgB,KAAK,YAAY,OAAO,CAAC;AAG1D,UAAQ,QAAQ,wBAAwB;UACjC,OAAO;AACd,UAAQ,KAAK,gCAAgC;AAC7C,QAAM;;;;;;;;;;AAWV,eAAsB,2CACpB,YACA,QACe;CACf,MAAM,gBAAgB,KAAK,YAAY,+BAA+B;CACtE,MAAM,cAAc,MAAM,QACxB,KAAK,QAAQ,EAAE,+BAA+B,CAC/C;CACD,MAAM,gBAAgB,KAAK,aAAa,SAAS;CACjD,IAAI,YAAY;AAChB,KAAI;AAIF,QAAM,UAAU,eAAe,kCAAkC;AACjE,QAAM,oBAAoB,eAAe,QAAQ,OAAO,KAAK;EAC7D,MAAM,YAAY,MAAM,yBAAyB,eAAe,WAAW;AAC3E,MAAI,UAAU,SAAS,EACrB,SAAQ,IACN,MAAM,IACJ,eAAe,UAAU,OAAO,uBAAuB,UAAU,WAAW,IAAI,SAAS,QAAQ,sCAClG,CACF;AAEH,cAAY;WACJ;AACR,MAAI,UAAW,OAAM,GAAG,eAAe,EAAE,OAAO,MAAM,CAAC;AACvD,QAAM,GAAG,aAAa;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;;;AAI3D,MAAa,iCAAiC;AAE9C,eAAe,yBACb,YACA,YACA,oBAAoB,IACD;CACnB,MAAM,YAAsB,EAAE;CAE9B,MAAM,UAAU,MAAM,QADE,KAAK,YAAY,kBAAkB,EACZ,EAAE,eAAe,MAAM,CAAC;AAEvE,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,eAAe,KAAK,mBAAmB,MAAM,KAAK;EACxD,MAAM,aAAa,KAAK,YAAY,aAAa;EACjD,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAI,MAAM,aAAa,EAAE;AACvB,OAAI,CAAE,MAAM,wBAAwB,WAAW,CAK7C,OAAM,IAAI,MACR,wEAAwE,aAAa,6EACtF;AAEH,aAAU,KACR,GAAI,MAAM,yBACR,YACA,YACA,aACD,CACF;AACD;;AAEF,MAAI,CAAC,MAAM,QAAQ,CACjB,OAAM,IAAI,MACR,wDAAwD,aAAa,GACtE;AAEH,MAAI;AAGF,SAAM,SAAS,YAAY,YAAY,UAAU,cAAc;WACxD,OAAO;AACd,OAAK,MAAgC,SAAS,SAAU,OAAM;AAC9D,aAAU,KAAK,aAAa;;;AAIhC,QAAO;;AAGT,eAAe,wBAAwB,YAAsC;AAC3E,KAAI;AACF,UAAQ,MAAM,MAAM,WAAW,EAAE,aAAa;UACvC,OAAO;EACd,MAAM,OAAQ,MAAgC;AAC9C,MAAI,SAAS,UAAU;AAGrB,OAAI,SAAS,UAAW,QAAO;AAC/B,SAAM;;;AAIV,KAAI;AACF,QAAM,MAAM,WAAW;AACvB,SAAO;UACA,OAAO;EACd,MAAM,OAAQ,MAAgC;AAC9C,MAAI,SAAS,YAAY,SAAS,UAAW,OAAM;AACnD,MAAI;AACF,WAAQ,MAAM,MAAM,WAAW,EAAE,aAAa;WACvC,WAAW;AAClB,OAAK,UAAoC,SAAS,UAAW,QAAO;AACpE,SAAM;;;;;;;;;;;;;;;;AC3bZ,MAAMC,eAAa;AACnB,MAAM,sBAAsB;CAAC;CAAU;CAAU;CAAM;AACvD,MAAM,qBAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAOF,SAAgB,yBAAyB,KAAa;CACpD,MAAM,cAAc,QAAQ,IAAI;AAEhC,SAAQ,aAA8B;EAIpC,MAAM,cAAc,SAAS,aAHR,WAAW,SAAS,GACrC,WACA,QAAQ,aAAa,SAAS,CACqB;AAEvD,MAAI,gBAAgB,GAAI,QAAO;AAC/B,MAAI,gBAAgB,QAAQ,YAAY,WAAW,KAAK,MAAM,CAAE,QAAO;EAEvE,MAAM,CAAC,gBAAgB,YAAY,MAAM,IAAI;AAC7C,MAAI,oBAAoB,MAAM,cAAc,iBAAiB,UAAU,CACrE,QAAO;EAGT,MAAM,oBACJ,gBAAgB,UAAU,YAAY,WAAW,QAAQ;AAE3D,SAAO,CAAC,mBAAmB,IAAI,YAAY,IAAI,CAAC;;;AAIpD,SAAgB,uBACd,MACA,iBACA,MAAM,QAAQ,KAAK,EACnB;AACA,QAAO;EACL;EACA,MAAM,kBAAkB,OAAO,KAAA;EAC/B,OAAO,EACL,SAAS,yBAAyB,IAAI,EACvC;EACF;;;;;;AAOH,SAAS,iBAAiB,KAAsB;AAC9C,QAAO,WAAW,KAAK,KAAKA,cAAY,kBAAkB,CAAC;;;;;;;;;;;;;;;AAgB7D,SAAgB,2BACd,KAC+C;CAC/C,MAAM,kBAAkB,KAAK,KAAK,eAAe;CACjD,MAAM,UAAU,WAAW,KAAK,iBAAiB,OAAO,CAAC;CACzD,MAAM,eAAe,WACnB,KAAK,iBAAiB,cAAc,aAAa,CAClD;AAED,KAAI,WAAW,gBAAgB,IAAI,WAAW,aAC5C,QAAO,EAAE,IAAI,MAAM;AAGrB,QAAO;EACL,IAAI;EACJ,SACE,oCAAoC,IAAI;EAE3C;;;;;;AAOH,eAAe,SAAS,KAA+B;AACrD,SAAQ,KAAK;AACb,SAAQ,IACN,MAAM,OAAO,8BAA8B,GACzC,iCACH;AACD,SAAQ,KAAK;AAEb,KAAI;EAOF,MAAM,EAAE,gBAAgB,MAAM,OAAO,uBAAA,MAAA,MAAA,EAAA,EAAA;AAErC,QAAM,YAAY,WAAW,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAGlD,SAAO,iBAAiB,IAAI;UACrB,KAAK;AACZ,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,qBAAqB,IAC5B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;AACb,UAAQ,IACN,SACE,MAAM,KAAK,oBAAoB,GAC/B,qCACH;AACD,UAAQ,KAAK;AACb,SAAO;;;AAIX,MAAa,aAAsB,IAAI,QAAQ,MAAM,CAClD,YAAY,iEAAiE,CAC7E,OAAO,qBAAqB,iCAAiC,OAAO,CACpE,OAAO,UAAU,uCAAuC,CACxD,OAAO,eAAe,iDAAiD,CACvE,OAAO,OAAO,YAAiD;CAC9D,MAAM,MAAM,QAAQ,KAAK;AAIzB,KAAI,CAAC,WADmB,KAAK,KAAK,eAAe,CACjB,EAAE;AAChC,UAAQ,MACN,MAAM,IAAI,oDAAoD,CAC/D;AACD,UAAQ,MACN,MAAM,OAAO,gDAAgD,CAC9D;AACD,UAAQ,KAAK,EAAE;;CAIjB,MAAM,iBAAiB,KAAK,KAAK,iBAAiB;AAClD,KAAI,CAAC,WAAW,eAAe,EAAE;AAC/B,UAAQ,MAAM,MAAM,IAAI,iCAAiC,CAAC;AAC1D,UAAQ,MACN,MAAM,OAAO,0DAA0D,CACxE;AACD,UAAQ,KAAK,EAAE;;CAOjB,MAAM,YAAY,2BAA2B,IAAI;AACjD,KAAI,CAAC,UAAU,IAAI;AACjB,UAAQ,MAAM,MAAM,IAAI,UAAU,UAAU,UAAU,CAAC;AACvD,UAAQ,KAAK,EAAE;;AAIjB,KAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,QAAQ;MAEjC,CADW,MAAM,SAAS,IAAI,EACrB;AACX,WAAQ,MACN,MAAM,IAAI,kDAAkD,CAC7D;AACD,WAAQ,MACN,MAAM,OACJ,SACE,MAAM,KAAK,oBAAoB,GAC/B,8BACH,CACF;AACD,WAAQ,KAAK,EAAE;;;AAInB,KAAI,iBAAiB,IAAI,EAAE;AACzB,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,MAAM,oBAAoB,GAC9B,wBACA,MAAM,KAAK,UAAU,GACrB,kBACH;AACD,UAAQ,IACN,MAAM,KACJ,4FACD,CACF;AACD,UAAQ,IACN,MAAM,KAAK,uDAAuD,CACnE;;AAGH,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,iCAAiC,CAAC;AACzD,SAAQ,KAAK;CAGb,MAAM,WAAW,CAAC,OAAO;AACzB,KAAI,QAAQ,KAAM,UAAS,KAAK,UAAU,OAAO,QAAQ,KAAK,CAAC;AAC/D,KAAI,QAAQ,KAAM,UAAS,KAAK,SAAS;CAUzC,MAAM,aAAa,OAAO,QAAQ,KAAK;CACvC,MAAM,OAAO,OAAO,SAAS,WAAW,GAAG,aAAa;AAExD,KAAI;EACF,MAAM,EAAE,iBAAiB,MAAM,OAAO;EACtC,MAAM,EAAE,oBACN,MAAM,OAAO,oCAAA,MAAA,MAAA,EAAA,EAAA;EACf,MAAM,EAAE,qBACN,MAAM,OAAO,qCAAA,MAAA,MAAA,EAAA,EAAA;EAGf,MAAM,EAAE,0BACN,MAAM,OAAO,2CAAA,MAAA,MAAA,EAAA,EAAA;EAQf,MAAM,SAAS,MAAM,aAAa;GAChC,YAAY;GACZ,WAAW,CAAC,SAAS,eAAe;GACpC,SAAS;IACP,iBAAiB;IACjB,kBAAkB;IAClB,GAAG,uBAAuB;IAC3B;GACD,QAAQ,uBAAuB,MAAM,QAAQ,SAAS,MAAM,IAAI;GACjE,CAAC;AAEF,QAAM,OAAO,QAAQ;AACrB,SAAO,WAAW;EAElB,MAAM,iBAAiB;AAChB,UAAO,OAAO,CAAC,cAAc,QAAQ,KAAK,EAAE,CAAC;;AAEpD,UAAQ,GAAG,UAAU,SAAS;AAC9B,UAAQ,GAAG,WAAW,SAAS;AAG/B,QAAM,IAAI,cAAqB,GAAG;UAC3B,OAAO;AACd,UAAQ,KACN,MAAM,OACJ,uJAED,EACD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;AACD,MAAI;AACF,SAAM,MAAM,QAAQ,UAAU;IAAE;IAAK,OAAO;IAAW,CAAC;WACjD,aAAa;GAIpB,MAAM,aAAa;AACnB,OAAI,WAAW,WAAW,YAAY,WAAW,WAAW,UAC1D;AAEF,WAAQ,MAAM,MAAM,IAAI,0CAA0C,CAAC;AACnE,WAAQ,KAAK,EAAE;;;EAGnB;;;ACpTJ,MAAa,eAAwB,IAAI,QAAQ,QAAQ,CACtD,YAAY,uCAAuC,CACnD,OAAO,uBAAuB,oBAAoB,OAAO,CACzD,OAAO,OAAO,YAA0B;CACvC,MAAM,MAAM,QAAQ,KAAK;AAIzB,KAAI,CAAC,WADmB,KAAK,KAAK,eAAe,CACjB,EAAE;AAChC,UAAQ,MACN,MAAM,IAAI,oDAAoD,CAC/D;AACD,UAAQ,MACN,MAAM,OAAO,gDAAgD,CAC9D;AACD,UAAQ,KAAK,EAAE;;AAKjB,KAAI,CAAC,WADkB,KAAK,KAAK,iBAAiB,CACnB,EAAE;AAC/B,UAAQ,MAAM,MAAM,IAAI,iCAAiC,CAAC;AAC1D,UAAQ,MACN,MAAM,OAAO,0DAA0D,CACxE;AACD,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,6BAA6B,CAAC;AACrD,SAAQ,KAAK;CAEb,MAAM,UAAU,IAAI,cAAc,CAAC,OAAO;AAE1C,KAAI;AAEF,QAAM,MAAM,QAAQ,CAAC,OAAO,QAAQ,EAAE;GACpC;GACA,OAAO;GACR,CAAC;AAEF,UAAQ,QAAQ,kBAAkB;EAElC,MAAM,SAAS,QAAQ,UAAU;AACjC,UAAQ,KAAK;AACb,UAAQ,IAAI,qBAAqB,MAAM,KAAK,OAAO,CAAC,GAAG;AACvD,UAAQ,KAAK;AACb,UAAQ,IAAI,gCAAgC;AAC5C,UAAQ,IAAI,MAAM,KAAK,sBAAsB,CAAC;AAC9C,UAAQ,KAAK;UACN,OAAO;AACd,UAAQ,KAAK,eAAe;EAC5B,MAAM,aAAa;AACnB,MAAI,WAAW,OACb,SAAQ,MAAM,WAAW,OAAO;AAElC,UAAQ,KAAK,EAAE;;EAEjB;;;;;;ACvDJ,MAAa,6BAAgD,IAAI,IAAI;CACnE,CACE,gBACA;EACE,aAAa;EACb,cAAc,UAAW,MAAM,YAAwC,EAAE;EACzE,cAAc,OAAO,cAAc;GAAE,GAAG;GAAO;GAAU;EAC1D,CACF;CACD,CACE,cACA;EACE,aAAa;EACb,cAAc,UAAW,MAAM,YAAwC,EAAE;EACzE,cAAc,OAAO,cAAc;GAAE,GAAG;GAAO;GAAU;EAC1D,CACF;CACD,CACE,mBACA;EACE,aAAa;EACb,cAAc,UAAW,MAAM,YAAwC,EAAE;EACzE,cAAc,OAAO,cAAc;GAAE,GAAG;GAAO;GAAU;EAC1D,CACF;CACF,CAAC;;;;AAKF,SAAgB,kBACd,YACA,mBACS;AACT,QAAO,kBAAkB,IAAI,WAAW;;;;;;;;;;ACW1C,MAAM,uBAAuB;CAC3B,CAAC,UAAU,UAAU;CACrB,CAAC,SAAS,SAAS;CACnB,CAAC,cAAc,cAAc;CAC7B,CAAC,WAAW,WAAW;CACxB;;;;AASD,SAAgB,aAAa,UAA0B;AACrD,QAAO,SAAS,UAAU,QAAQ;;;;;AAMpC,SAAS,qBACP,UAC0D;CAC1D,MAAM,MAAM,SAAS,MAAM,IAAI,CAAC;AAChC,KACE,QAAQ,aACR,QAAQ,YACR,QAAQ,iBACR,QAAQ,WAER,QAAO;AAET,QAAO;;;;;AAMT,eAAeC,iBACb,WACA,cACY;CAEZ,MAAM,UAAU,MAAM,SADL,KAAK,WAAW,aAAa,EACL,QAAQ;AACjD,QAAO,KAAK,MAAM,QAAQ;;AAG5B,SAAS,iBACP,WACA,gBAAgB,WACN;CACV,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,SAAS,YAAY,WAAW,EAAE,eAAe,MAAM,CAAC,EAAE;EACnE,MAAM,eAAe,KAAK,WAAW,MAAM,KAAK;AAIhD,MAHoB,MAAM,gBAAgB,GACtC,SAAS,aAAa,CAAC,aAAa,GACpC,MAAM,aAAa,CAErB,OAAM,KAAK,GAAG,iBAAiB,cAAc,cAAc,CAAC;WACnD,MAAM,KAAK,SAAS,QAAQ,CACrC,OAAM,KAAK,SAAS,eAAe,aAAa,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI,CAAC;;AAG1E,QAAO,MAAM,MAAM;;;AAIrB,eAAsB,sBACpB,WAC4B;CAC5B,MAAM,QAA2D,EAAE;AAEnE,KAAI,WADmB,KAAK,WAAW,kBAAkB,CAC3B,CAC5B,OAAM,KAAK;EAAE,MAAM;EAAc,MAAM;EAAmB,CAAC;KAE3D,QAAO,CACL;EACE,MAAM;EACN,SAAS;EACV,CACF;AAGH,MAAK,MAAM,CAAC,MAAM,cAAc,sBAAsB;EACpD,MAAM,oBAAoB,KAAK,WAAW,UAAU;AACpD,MAAI,CAAC,WAAW,kBAAkB,CAAE;AACpC,OAAK,MAAM,QAAQ,iBAAiB,kBAAkB,CACpD,OAAM,KAAK;GAAE;GAAM,MAAM,GAAG,UAAU,GAAG;GAAQ,CAAC;;CAItD,MAAM,SAA4B,EAAE;AACpC,MAAK,MAAM,EAAE,MAAM,UAAU,OAAO;EAClC,IAAI;AACJ,MAAI;AACF,WAAQ,MAAMA,iBAAwB,WAAW,KAAK;UAChD;AACN,UAAO,KAAK;IAAE;IAAM,SAAS;IAA2B,CAAC;AACzD;;EAGF,MAAM,SAAS,uBAAuB,MAAM,MAAM;AAClD,MAAI,CAAC,OAAO,QACV,MAAK,MAAM,SAAS,OAAO,OACzB,QAAO,KAAK;GACV;GACA,SAAS,GAAG,MAAM,KAAK,GAAG,MAAM;GACjC,CAAC;;AAIR,QAAO;;;;;AAUT,SAAgB,kBAAkB,MAAwC;CACxE,MAAM,SAA6B;EACjC,SAAS;GAAE,KAAK,EAAE;GAAE,SAAS,EAAE;GAAE,SAAS,EAAE;GAAE;EAC9C,QAAQ;GAAE,KAAK,EAAE;GAAE,SAAS,EAAE;GAAE,SAAS,EAAE;GAAE;EAC7C,aAAa;GAAE,KAAK,EAAE;GAAE,SAAS,EAAE;GAAE,SAAS,EAAE;GAAE;EAClD,UAAU;GAAE,KAAK,EAAE;GAAE,SAAS,EAAE;GAAE,SAAS,EAAE;GAAE;EAChD;AAED,MAAK,MAAM,QAAQ,KAAK,KAAK;EAC3B,MAAM,OAAO,qBAAqB,KAAK;AACvC,MAAI,KAAM,QAAO,MAAM,IAAI,KAAK,KAAK;;AAEvC,MAAK,MAAM,QAAQ,KAAK,SAAS;EAC/B,MAAM,OAAO,qBAAqB,KAAK;AACvC,MAAI,KAAM,QAAO,MAAM,QAAQ,KAAK,KAAK;;AAE3C,MAAK,MAAM,QAAQ,KAAK,SAAS;EAC/B,MAAM,OAAO,qBAAqB,KAAK;AACvC,MAAI,KAAM,QAAO,MAAM,QAAQ,KAAK,KAAK;;AAG3C,QAAO;;;AAQT,SAAS,wBAAwB,OAAqC;AACpE,QAAO,gBAAgB,OAAO,kCAAkB,IAAI,KAAK,CAAC;;AAG5D,SAAS,gBACP,OACA,MACA,SACU;CACV,MAAM,SAAmB,EAAE;AAE3B,OAAM,SAAS,MAAM,UAAU;EAC7B,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM;AAE5B,MAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAClE;EAEF,MAAM,SAAS;AACf,MAAI,OAAO,OAAO,OAAO,YAAY,OAAO,GAAG,MAAM,CAAC,SAAS,GAAG;GAChE,MAAM,aAAa,QAAQ,IAAI,OAAO,GAAG;AACzC,OAAI,eAAe,KAAA,EACjB,SAAQ,IAAI,OAAO,IAAI,GAAG;OAE1B,QAAO,KACL,GAAG,GAAG,iBAAiB,KAAK,UAAU,OAAO,GAAG,CAAC,oBAAoB,WAAW,gLACjF;;EAIL,MAAM,QAAQ,OAAO;AACrB,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE;EAEF,MAAM,WAAY,MAAkC;AACpD,MAAI,MAAM,QAAQ,SAAS,CACzB,QAAO,KACL,GAAG,gBAAgB,UAAU,GAAG,GAAG,kBAAkB,QAAQ,CAC9D;GAEH;AAEF,QAAO;;;;;;;;;;;AAgBT,eAAsB,wBACpB,WACA,UACA,SAC4B;CAC5B,MAAM,SAA4B,EAAE;CAGpC,MAAM,mBAAmB,mBAAmB,WAAW,WAAW,SAAS;CAC3E,MAAM,gBAAgB,mBAAmB,WAAW,eAAe,SAAS;CAC5E,MAAM,kBAAkB,mBAAmB,WAAW,UAAU,SAAS;AAGzE,MAAK,MAAM,QAAQ,QAAQ,QAAQ,QACjC,kBAAiB,OAAO,aAAa,KAAK,CAAC;AAE7C,MAAK,MAAM,QAAQ,QAAQ,YAAY,QACrC,eAAc,OAAO,aAAa,KAAK,CAAC;AAE1C,MAAK,MAAM,QAAQ,QAAQ,OAAO,QAChC,iBAAgB,OAAO,aAAa,KAAK,CAAC;CAG5C,MAAM,qBAAqB,CACzB,GAAG,QAAQ,QAAQ,KACnB,GAAG,QAAQ,QAAQ,QACpB;AACD,MAAK,MAAM,QAAQ,mBACjB,KAAI;EACF,MAAM,SAAS,MAAMA,iBAA4B,WAAW,KAAK;AACjE,MAAI,MAAM,QAAQ,OAAO,eAAe,CACtC,MAAK,MAAM,SAAS,wBAAwB,OAAO,eAAe,CAChE,QAAO,KAAK;GAAE;GAAM,SAAS;GAAO,CAAC;SAGnC;AACN,SAAO,KAAK;GAAE;GAAM,SAAS;GAA8B,CAAC;;CAKhE,MAAM,kBAAkB,CACtB,GAAG,QAAQ,YAAY,KACvB,GAAG,QAAQ,YAAY,QACxB;AACD,MAAK,MAAM,QAAQ,gBACjB,KAAI;EACF,MAAM,MAAM,MAAMA,iBAAgC,WAAW,KAAK;AAClE,MAAI,MAAM,QAAQ,IAAI,iBAAiB,CACrC,yBACE,IAAI,kBACJ,MACA,kBACA,OACD;SAEG;AACN,SAAO,KAAK;GAAE;GAAM,SAAS;GAAkC,CAAC;;CAKpE,MAAM,sBAAsB,CAC1B,GAAG,QAAQ,SAAS,KACpB,GAAG,QAAQ,SAAS,QACrB;AACD,MAAK,MAAM,QAAQ,oBACjB,KAAI;AAEF,4BADgB,MAAMA,iBAA6B,WAAW,KAAK,EAGjE,MACA,eACA,iBACA,OACD;SACK;AACN,SAAO,KAAK;GAAE;GAAM,SAAS;GAA+B,CAAC;;AAIjE,KAAI,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtC,MAAM,iBAAiB,IAAI,IAAI,gBAAgB;AAC/C,OAAK,MAAM,QAAQ,kBAAkB,WAAW,cAAc,EAAE;AAC9D,OAAI,eAAe,IAAI,KAAK,CAAE;GAC9B,MAAM,MAAM,MAAM,qBAAsC,WAAW,KAAK;AACxE,OAAI,CAAC,OAAO,CAAC,MAAM,QAAQ,IAAI,iBAAiB,CAAE;AAClD,2BACE,IAAI,kBACJ,MACA,kBACA,OACD;;;AAIL,KACE,QAAQ,YAAY,QAAQ,SAAS,KACrC,QAAQ,OAAO,QAAQ,SAAS,GAChC;EACA,MAAM,iBAAiB,IAAI,IAAI,oBAAoB;AACnD,OAAK,MAAM,QAAQ,kBAAkB,WAAW,WAAW,EAAE;AAC3D,OAAI,eAAe,IAAI,KAAK,CAAE;GAC9B,MAAM,UAAU,MAAM,qBAAmC,WAAW,KAAK;AACzE,OAAI,CAAC,QAAS;AACd,6BACE,SACA,MACA,eACA,iBACA,OACD;;;AAIL,QAAO;;AAGT,SAAS,kBACP,WACA,cACU;CACV,MAAM,YAAY,KAAK,WAAW,aAAa;AAC/C,KAAI,CAAC,WAAW,UAAU,CAAE,QAAO,EAAE;AACrC,KAAI;AACF,SAAO,iBAAiB,UAAU,CAAC,KAAK,SAAS,GAAG,aAAa,GAAG,OAAO;SACrE;AACN,SAAO,EAAE;;;AAIb,eAAe,qBACb,WACA,cACmB;AACnB,KAAI;AACF,SAAO,MAAMA,iBAAkB,WAAW,aAAa;SACjD;AACN,SAAO;;;AAIX,SAAS,0BACP,SACA,MACA,eACA,iBACA,QACM;AACN,KAAI,QAAQ,cAAc,CAAC,cAAc,IAAI,QAAQ,WAAW,CAC9D,QAAO,KAAK;EACV;EACA,SAAS,0BAA0B,QAAQ,WAAW;EACvD,CAAC;AAGJ,KACE,QAAQ,qBACR,CAAC,cAAc,IAAI,QAAQ,kBAAkB,CAE7C,QAAO,KAAK;EACV;EACA,SAAS,iCAAiC,QAAQ,kBAAkB;EACrE,CAAC;AAGJ,MAAK,MAAM,aAAa,QAAQ,UAAU,EAAE,CAC1C,KAAI,CAAC,gBAAgB,IAAI,UAAU,CACjC,QAAO,KAAK;EACV;EACA,SAAS,qBAAqB,UAAU;EACzC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AA+BR,eAAsB,wBACpB,WACA,SAC8B;CAC9B,MAAM,WAAgC,EAAE;CACxC,MAAM,QAAQ,CAAC,GAAG,QAAQ,QAAQ,KAAK,GAAG,QAAQ,QAAQ,QAAQ;AAElE,MAAK,MAAM,QAAQ,OAAO;EACxB,IAAI;AACJ,MAAI;AACF,YAAS,MAAMA,iBAA4B,WAAW,KAAK;UACrD;AACN;;EAGF,MAAM,OAAO,OAAO;AACpB,MAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,WAAW,EAAG;AAM/C,MAAI,KAAK,WAAW,EAAG;EAEvB,MAAM,OAAO,KAAK;EAClB,MAAM,WACJ,QAAQ,OAAO,SAAS,WACnB,KAA4B,OAC7B,KAAA;AAIN,MAFE,OAAO,aAAa,YACpB,kBAAkB,UAAU,mBAAmB,CAC3B;AAEtB,WAAS,KAAK;GACZ;GACA,SACE,mIACyB,oBAAoB;GAChD,CAAC;;AAGJ,QAAO;;;;;;;AAQT,MAAM,qBAAqB;;AAG3B,MAAM,sBAAsB,CAAC,GAAG,2BAA2B,MAAM,CAAC,CAC/D,KAAK,SAAS,IAAI,KAAK,GAAG,CAC1B,KAAK,MAAM;;;;;AAMd,SAAS,mBACP,WACA,cACA,UACa;CACb,MAAM,wBAAQ,IAAI,KAAa;AAG/B,MAAK,MAAM,QAAQ,OAAO,KAAK,SAAS,cAAc,CACpD,OAAM,IAAI,KAAK;CAIjB,MAAM,MAAM,KAAK,WAAW,aAAa;AACzC,KAAI,WAAW,IAAI,CACjB,KAAI;EACF,MAAM,UAAU,YAAY,IAAI;AAChC,OAAK,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,QAAQ,CACzB,OAAM,IAAI,SAAS,OAAO,QAAQ,CAAC;SAGjC;AAKV,QAAO;;;;;AAMT,SAAS,wBACP,OACA,MACA,kBACA,QACM;AACN,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,KAAK,UAAU,CAAC,iBAAiB,IAAI,KAAK,OAAO,CACnD,QAAO,KAAK;GACV;GACA,SAAS,oBAAoB,KAAK,SAAS,cAAc,uBAAuB,KAAK,OAAO;GAC7F,CAAC;AAEJ,MAAI,KAAK,YAAY,KAAK,SAAS,SAAS,EAC1C,yBAAwB,KAAK,UAAU,MAAM,kBAAkB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5f5E,SAAgB,wBAAwB,cAA8B;AACpE,QAAO,qCAAqC,aAAa;;AA6B3D,MAAM,0BAA0B;CAC9B;CAAK;CAAO;CAAO;CAAO;CAAO;CAAQ;CAAQ;CAClD;AACD,MAAM,6BAA6B;AAMnC,MAAM,2BAA2B,IAAI,IAAI,CAAC,mBAAmB,gBAAgB,CAAC;AAK9E,MAAM,wBAAwB,IAAI,IAAI,CAAC,eAAe,CAAC;AAEvD,SAAS,eAAe,OAAoC;AAC1D,QAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;;AAGjE,SAAS,YAAY,OAAoC;AACvD,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,eAAe,MAAM,GAAG;AACzD,QAAO,eAAe,MAAM;;AAG9B,SAAS,aAAa,OAA0B;AAC9C,KACE,MAAM,SAAS,QACf,OAAO,MAAM,SAAS,aACrB,WAAW,MAAM,QAChB,mBAAmB,MAAM,QACzB,YAAY,MAAM,MAEpB,QAAO,MAAM;AAEf,QAAO;EAAE,eAAe,MAAM;EAAS,QAAQ,MAAM;EAAM;;AAG7D,SAAS,oBACP,MACA,UAKA;AACA,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,EAAE,QAAQ,UAAU;CAElE,MAAM,WAAW;CAKjB,MAAM,QACJ,SAAS,SAAS,OAAO,SAAS,UAAU,WACvC,SAAS,QAIV,KAAA;CACN,MAAM,gBACJ,OAAO,WAAW,OAAO,MAAM,YAAY,WACtC,MAAM,UAIP,KAAA;CACN,MAAM,SACJ,SAAS,UAAU,OAAO,SAAS,WAAW,WACzC,SAAS,SAIV,KAAA;CACN,MAAM,SACJ,eAAe,SAAS,cAAc,IACtC,eAAe,OAAO,QAAQ,IAC9B,eAAe,SAAS,MAAM,IAC9B;CACF,MAAM,cACJ,YAAY,QAAQ,KAAK,IAAI,eAAe,eAAe,KAAK;CAClE,MAAM,aACJ,YAAY,QAAQ,YAAY,IAChC,eAAe,eAAe,YAAY;AAE5C,QAAO;EACL;EACA,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;EACtC,GAAI,aAAa,EAAE,YAAY,GAAG,EAAE;EACrC;;AAGH,SAAS,MAAM,SAAgC;AAC7C,QAAO,IAAI,SAAS,YAAY;AAC9B,aAAW,SAAS,QAAQ;GAC5B;;;;;;;;AASJ,eAAsB,mCACpB,SAC+B;CAC/B,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,iBAAmD;EACvD,SAAS,QAAQ;EACjB,OAAO,QAAQ;EACf,cAAc,QAAQ;EACtB,GAAI,QAAQ,cAAc,KAAA,IACtB,EAAE,WAAW,QAAQ,WAAW,GAChC,EAAE;EACN,GAAI,QAAQ,qBAAqB,KAAA,IAC7B,EAAE,kBAAkB,QAAQ,kBAAkB,GAC9C,EAAE;EACP;CACD,IAAI,UAAU,MAAM,0BAA0B,eAAe;AAE7D,MAAK,MAAM,CAAC,OAAO,YAAY,cAAc,SAAS,EAAE;AACtD,MAAI,QAAQ,WAAW,iBAAiB,CAAC,QAAQ,UAAW,QAAO;AAEnE,UAAQ,UAAU;GAChB,SAAS,QAAQ;GACjB,eAAe,cAAc,SAAS;GACtC,QAAQ,QAAQ;GACjB,CAAC;AACF,QAAM,UAAU,QAAQ;AACxB,YAAU,MAAM,0BAA0B,eAAe;;AAG3D,QAAO;;;;;;;;AAST,eAAsB,0BACpB,SAC+B;CAC/B,MAAM,EAAE,SAAS,OAAO,iBAAiB;CACzC,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,mBACJ,QAAQ,oBAAoB;CAC9B,MAAM,kBAAkB,IAAI,iBAAiB;CAC7C,IAAI,kBAAkB;CACtB,MAAM,UAAU,iBAAiB;AAC/B,oBAAkB;AAClB,kBAAgB,OAAO;IACtB,iBAAiB;AAEpB,KAAI;EAYF,MAAM,cANW,MAAM,uCAAuC;GAC5D,QANa,0BAA0B;IACvC;IACA,OAAO;IACP,oBAAoB;IACrB,CAAC;GAGA,MAAM,EAAE,IAAI,cAAc;GAC1B,QAAQ,gBAAgB;GACzB,CAAC,EAE0B;AAC5B,MAAI,CAAC,YAAY,WACf,QAAO;GACL,QAAQ;GACR,QAAQ;GACT;AAGH,SAAO;GAAE,QAAQ;GAAM;GAAY;UAC5B,KAAK;AACZ,MAAI,gBACF,QAAO;GACL,QAAQ;GACR,QAAQ,0CAA0C,iBAAiB;GACpE;AAGH,MAAI,eAAe,UAAU;AAC3B,OAAI,IAAI,WAAW,IACjB,QAAO;IACL,QAAQ;IACR,QAAQ;IACR,WAAW;IACZ;GAGH,MAAM,UAAU,oBACd,aAAa,IAAI,EACjB,uCAAuC,IAAI,OAAO,GACnD;AACD,OAAI,IAAI,WAAW,KAAK;IACtB,MAAM,UACJ,QAAQ,eAAe,KAAA,KACvB,sBAAsB,IAAI,QAAQ,WAAW;AAC/C,WAAO;KACL,QAAQ;KACR,QAAQ,QAAQ;KAChB,WACE,CAAC,WACD,QAAQ,gBAAgB,KAAA,KACxB,yBAAyB,IAAI,QAAQ,YAAY;KACnD,GAAI,QAAQ,gBAAgB,KAAA,IACxB,EAAE,cAAc,QAAQ,aAAa,GACrC,EAAE;KACN,GAAI,QAAQ,eAAe,KAAA,IACvB,EAAE,YAAY,QAAQ,YAAY,GAClC,EAAE;KACP;;AAEH,UAAO;IAAE,QAAQ;IAAS,QAAQ,QAAQ;IAAQ;;AAGpD,SAAO;GACL,QAAQ;GACR,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACzD;WACO;AACR,eAAa,QAAQ;;;;;;;;;;AC1RzB,MAAM,6BACJ,qBACE,gEACA,KACD;;;;;;AA4BH,eAAsB,iBACpB,SAC8B;CAC9B,MAAM,EAAE,KAAK,SAAS,OAAO,cAAc,kBAAkB;CAC7D,MAAM,SAAS,QAAQ,UAAU;AAMjC,KAAI,CAAE,MAAM,UAAU,IAAI,IAAK,CAAE,MAAM,QAAQ,IAAI,CACjD,QAAO;EAAE,QAAQ;EAAW,QAAQ;EAAwB;AAS9D,OAAM,yBAAyB,IAAI;CAQnC,MAAM,UAAU,MAAM,0BAA0B;EAC9C;EACA;EACA;EACA,WAAW,QAAQ;EACpB,CAAC;AACF,KAAI,QAAQ,WAAW,KACrB,QAAO;EAAE,QAAQ;EAAW,QAAQ,QAAQ;EAAQ;CAEtD,MAAM,YAAY,QAAQ,WAAW;CAMrC,MAAM,SAAS,MAAM,aAAa,IAAI;AACtC,KAAI,QAAQ;EACV,MAAM,WAAW,uBAAuB,OAAO;EAC/C,MAAM,WAAW,uBAAuB,UAAU;AAKlD,MAAI,YAAY,aAAa,SAC3B,QAAO;GACL,QAAQ;GACR,QACE,gCAAgC,YAAY,OAAO,gDACf,SAAS;GAEhD;;AAIL,KAAI;AAcF,MAAI,CAAE,MAAM,WAAW,IAAI,EAAG;AAC5B,OAAI,MAAM,sBAAsB,KAAK,WAAW,OAAO,EAAE;AACvD,UAAM,SAAS,KAAK,WAAW,OAAO;AACtC,UAAM,kBAAkB,IAAI;;AAI9B,SAAM,yBAAyB,KAAK,sBAAsB,CAAC;AAC3D,OAAI,MAAM,sBAAsB,IAAI,CASlC,OAAM,cAAc,KAAK,eAAe,EAAE,gBAAgB,MAAM,CAAC;AAEnE,UAAO,MAAM,cAAc,KAAK,WAAW,OAAO;;AAGpD,QAAM,SAAS,KAAK,WAAW,OAAO;AAItC,MAAI,CAAE,MAAM,+BAA+B,IAAI,CAC7C,QAAO;GACL,QAAQ;GACR,QACE;GAEH;EAEH,MAAM,EAAE,OAAO,WAAW,MAAM,0BAA0B,IAAI;AAC9D,MAAI,SAAS,KAAK,QAAQ,EACxB,QAAO;GACL,QAAQ;GACR,QACE;GACH;AAEH,MAAI,SAAS,GAAG;AACd,OAAI,MAAM,sBAAsB,IAAI,CAClC,QAAO;IACL,QAAQ;IACR,QACE;IAEH;AAEH,SAAM,oBAAoB,IAAI;;AAUhC,QAAM,yBAAyB,KAAK,sBAAsB,CAAC;AAI3D,MAAI,MAAM,sBAAsB,IAAI,CAClC,OAAM,cAAc,KAAK,cAAc;AAGzC,SAAO,MAAM,cAAc,KAAK,WAAW,OAAO;UAC3C,KAAK;AAKZ,SAAO;GACL,QAAQ;GACR,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACzD;;;;;;;;;;;;AAaL,eAAe,cACb,KACA,WACA,QAC8B;AAC9B,KAAI;AACF,QAAM,QAAQ,KAAK,WAAW,OAAO;UAC9B,KAAK;AAEZ,SAAO;GACL,QAAQ;GACR,QACE,8DAJY,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAKhD;GAEf;;AAEH,QAAO;EAAE,QAAQ;EAAU,QAAQ;EAAM;;;;;;;;;;;;;ACpP3C,MAAM,gBAAgB;AAEtB,eAAsB,iBACpB,SACA,OACA,YAAY,eACkB;AAG9B,QAAO,0BAA0B,aAAa,SAAS,OAAO,UAAU,CAAC;;AAG3E,eAAe,aACb,SACA,OACA,WAC8B;CAC9B,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,QAAQ,iBAAiB,WAAW,OAAO,EAAE,UAAU;AAC7D,KAAI;EACF,MAAM,OAAO,MAAM,MAAM,GAAG,QAAQ,QAAQ,QAAQ,GAAG,CAAC,UAAU;GAChE,SAAS;IAAE,eAAe,UAAU;IAAS,QAAQ;IAAoB;GACzE,QAAQ,WAAW;GACpB,CAAC;AACF,MAAI,CAAC,KAAK,GAAI,QAAO;AACrB,SAAO,mBAAmB,MAAM,KAAK,MAAM,CAAC;SACtC;AACN,SAAO;WACC;AACR,eAAa,MAAM;;;;;ACrCvB,MAAa,4CAA4C,CACvD,iBACA,SACD;AAKD,MAAa,mDAAmD;CAC9D,eAAe;CACf,QAAQ;CACT;AAID,MAAM,8BAA8B,IAAI,IACtC,0CACD;AAYD,SAAgB,sCACd,YACmD;AACnD,QAAO,4BAA4B,IAAI,WAAW;;AAGpD,SAAgB,kCACd,eAIA,QACA,YACyC;AACzC,KAAI,CAAC,sCAAsC,WAAW,CAAE,QAAO,KAAA;CAC/D,MAAM,cAAc,OAAO,aAAa,MACrC,EAAE,WAAW,SAAS,WACxB;CACD,MAAM,mBACJ,iDAAiD;AACnD,KAAI,CAAC,eAAe,YAAY,YAAY,iBAC1C;AAGF,QAAO;EACL,WAAW,cAAc;EACzB,gBAAgB,cAAc;EAC9B,mBAAmB,YAAY;EAChC;;;;AC5CH,SAAgB,sCACd,YACA,SAIS;AACT,QACE,QAAQ,4BAA4B,QACnC,eAAe,mBAAmB,QAAQ,wBAAwB;;AAIvE,eAAsB,4BAA4B,SAKA;CAIhD,MAAM,gBAAgB,iCAHN,QAAQ,kBAAkB,OACxC,mCACD,CAC8D;CAC/D,MAAM,eAA4C,EAAE;AAEpD,MAAK,MAAM,QAAQ,QAAQ,aAAa;EACtC,MAAM,OAAO,KAAK,QAAQ,WAAW,KAAK;EAE1C,MAAM,UAAU,8BADD,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC,CACF;EACrD,MAAM,gBAAgB,IAAI,KACvB,QAAQ,WAAW,SAAS,EAAE,EAAE,KAAK,UAAU,CAAC,SAAS,MAAM,EAAE,MAAM,CAAC,CAC1E;AACD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,WAAW,cAAc,IAAI,WAAW,MAAM,CAAC;AACrD,OAAI,CAAC,SACH,OAAM,IAAI,MACR,qBAAqB,MAAM,WAAW,gBAAgB,MAAM,WAAW,OAAO,KAAK,6CACpF;GAEH,MAAM,WAAW,cAAc,IAAI,SAAS,MAAM,CAAC;GACnD,MAAM,qBACJ,UAAU,cAAc,SAAS,aACjC,SAAS,mBAAmB,SAAS,kBACrC,SAAS,sBAAsB,SAAS;AAC1C,OAAI,CAAC,sBAAsB,CAAC,UAAU,OAAO,SAAS,IAAI,CAAC,SACzD,OAAM,IAAI,MACR,qBAAqB,MAAM,WAAW,gBAAgB,MAAM,WAAW,OAAO,KAAK,6EACpF;AAEH,OAAI,CAAC,mBACH,cAAa,KAAK;IAChB,GAAG;IACH,UAAU,MAAM;IAChB,YAAY,MAAM;IAClB;IACD,CAAC;;;AAKR,QAAO;;AAGT,eAAsB,0CAA0C,SAG9C;CAChB,MAAM,+BAAe,IAAI,KAA0C;AACnE,MAAK,MAAM,SAAS,QAAQ,QAAQ;EAClC,MAAM,aAAa,aAAa,IAAI,MAAM,KAAK,IAAI,EAAE;AACrD,aAAW,KAAK,MAAM;AACtB,eAAa,IAAI,MAAM,MAAM,WAAW;;AAE1C,MAAK,MAAM,CAAC,MAAM,WAAW,cAAc;EACzC,MAAM,OAAO,KAAK,QAAQ,WAAW,KAAK;EAC1C,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC;AACvD,OAAK,MAAM,SAAS,OAClB,YAAW,QAAQ,MAAM;AAE3B,QAAM,gBAAgB,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,IAAI;;;AAIvE,SAAS,iCACP,UACoD;CACpD,MAAM,+BAAe,IAAI,KAA4C;AACrE,MAAK,MAAM,iBAAiB,SAC1B,MAAK,MAAM,UAAU,cAAc,QACjC,MAAK,MAAM,eAAe,OAAO,cAAc;EAC7C,MAAM,aAAa,kCACjB,eACA,QACA,YAAY,KACb;AACD,MAAI,CAAC,WAAY;EACjB,MAAM,QAAuC;GAC3C,YAAY,YAAY;GACxB,UAAU;GACV,YAAY,OAAO;GACnB,GAAG;GACJ;EACD,MAAM,MAAM,WAAW,MAAM;AAC7B,MAAI,aAAa,IAAI,IAAI,CACvB,OAAM,IAAI,MACR,gBAAgB,OAAO,KAAK,iDAAiD,YAAY,KAAK,IAC/F;AAEH,eAAa,IAAI,KAAK,MAAM;;AAIlC,QAAO;;AAGT,SAAS,WACP,OACQ;AACR,QAAO,GAAG,MAAM,WAAW,IAAI,MAAM;;AAGvC,SAAS,SACP,OAIQ;AACR,QAAO,GAAG,MAAM,WAAW,IAAI,MAAM,SAAS,IAAI,MAAM,UAAU,IAAI,MAAM;;AAG9E,SAAS,UACP,QACA,UACS;AACT,QACE,OAAO,cAAc,SAAS,aAC9B,OAAO,mBAAmB,SAAS,kBACnC,OAAO,sBAAsB,SAAS;;AAI1C,SAAS,WACP,OACA,OACS;AACT,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,MAAM,UAAU,WAAW,OAAO,MAAM,CAAC;CAExD,MAAM,SAAS;AACf,KAAI,OAAO,OAAO,MAAM,YAAY,OAAO,SAAS,MAAM,YAAY;AAKpE,SAAO,mBAAmB;GACxB,GAJA,OAAO,oBAAoB,OAAO,OAAO,qBAAqB,WACzD,OAAO,mBACR,EAAE;IAGL,MAAM,aAAa;IAClB,WAAW,MAAM;IACjB,gBAAgB,MAAM;IACtB,mBAAmB,MAAM;IAC1B;GACF;AACD,SAAO;;AAET,QAAO,OAAO,OAAO,OAAO,CAAC,MAAM,UAAU,WAAW,OAAO,MAAM,CAAC;;;;;;;;;;;ACpExE,IAAM,oCAAN,cAAgD,MAAM;CACpD,OAAyB;;AA+B3B,MAAM,aAAa;AACnB,MAAMC,oBAAkB;;;;;;AAOxB,SAAS,mBACP,MACgC;AAChC,KAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,KAAI,KAAK,WAAW,EAAG,QAAO,EAAE,GAAG,KAAK,IAAK;AAE7C,QAAO,EAAE,UAAU,MAAM;;;;;AAU3B,SAASC,iBAA8B;CACrC,MAAM,QAAQ,cAAc;AAC5B,KAAI,CAAC,OAAO;EACV,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,QACH,OAAM,IAAI,MACR,wBAAwB,MAAM,KAAK,cAAc,GAAG,UACrD;AAEH,QAAM,IAAI,MACR,qCACE,MAAM,KAAK,QAAQ,KAAK,GACxB,WACA,MAAM,KAAK,cAAc,GACzB,uBACH;;AAKH,QAAO,oBAAoB;EACzB,SAHc,QAAQ,IAAI,qBAAqB;EAI/C;EACD,CAAC;;AAGJ,eAAe,yBACb,QACA,cACiB;CAEjB,MAAM,aADS,MAAM,WAAW,cAAc,QAAQ,aAAa,EAC1C,SAAS;AAClC,KAAI,CAAC,UACH,OAAM,IAAI,MAAM,yCAAyC;AAE3D,OAAM,WAAW,cAAc,QAAQ,cAAc,WAAW,EAC9D,SAAS,EAAE,QAAQ,MAAM,EAC1B,CAAC;AACF,QAAO,OAAO,UAAU;;AAG1B,eAAe,cACb,cACA,gBACkB;CAClB,MAAM,UAAU,IAAI,4CAA4C;AAChE,SAAQ,OAAO;AACf,KAAI;EACF,MAAM,YAAY,MAAM,yBACtB,kBAAkBA,gBAAc,EAChC,aACD;AACD,UAAQ,QAAQ,gBAAgB,UAAU,YAAY;AACtD,SAAO;UACA,KAAK;AACZ,UAAQ,KAAK,oCAAoC;AACjD,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;AACb,UAAQ,WAAW;AACnB,SAAO;;;;;;;AAQX,SAAS,qBAAqB,KAAsB;CAClD,IAAI,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC1D,KAAI,OAAO,OAAO,QAAQ,YAAY,UAAU,IAC9C,QAAO,MAAM,KAAK,UAAW,IAA0B,KAAK;AAE9D,QAAO;;AAGT,SAAS,sBACP,KACA,uBAC+C;CAC/C,MAAM,QAAQ,qBAAqB,IAAI;AACvC,KAAI,eAAe,kCACjB,QAAO;EAAE,iBAAiB;EAAM;EAAO;AAEzC,KAAI,CAAC,sBAAuB,QAAO,EAAE,OAAO;AAE5C,QAAO;EACL,iBAAiB;EACjB,OAAO,uDAAuD,MAAM;EACrE;;;;;;;;;;AAWH,eAAe,oBACb,KACA,cACA,gBACA,iBACkB;CAClB,MAAM,QAAQ,cAAc;AAC5B,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,UAAU,QAAQ,IAAI,qBAAqB;CAgBjD,MAAM,eAAe,iBAAiB,SAAS,MAAM;CACrD,MAAM,WAAW,kBAAkB,gBAAgB,eAAe;CAClE,MAAM,WAAW,iBAAiB,MAAM;CACxC,MAAM,gBAAgB,OACpB,YAEA,qBACE,YAAY,iBAAiB,QAAQ,IAAI,UACzC,MAAM,aACP;CAEH,MAAM,UAAU,IAAI,0BAA0B,CAAC,OAAO;CACtD,MAAM,SAAS,MAAM,iBAAiB;EACpC;EACA;EACA;EACA;EACA;EACD,CAAC;AACF,KAAI,OAAO,WAAW,SACpB,SAAQ,QAAQ,wBAAwB;UAC/B,OAAO,WAAW,UAAU;AAIrC,UAAQ,KAAK,kBAAkB;AAG/B,UAAQ,MAAM,MAAM,IAAI,OAAO,OAAO,CAAC;AACvC,UAAQ,KAAK;AACb,SAAO;OAEP,SAAQ,KAAK,MAAM,KAAK,sBAAsB,OAAO,SAAS,CAAC;AAEjE,SAAQ,KAAK;AACb,QAAO;;;;;AAMT,eAAe,eACb,WACA,cACY;CAEZ,MAAM,UAAU,MAAM,SADL,KAAK,WAAW,aAAa,EACL,QAAQ;AACjD,QAAO,KAAK,MAAM,QAAQ;;;;;AAU5B,eAAe,YACb,QACA,OACA,WACA,SACA,UAC8D;CAC9D,MAAM,UAAwB,EAAE;CAChC,IAAI,kBAAkB;AAGtB,MAAK,MAAM,QAAQ,QAAQ,KAAK;EAC9B,MAAM,OAAO,aAAa,KAAK;AAC/B,MAAI;GACF,MAAM,QAAQ,MAAM,eAA4B,WAAW,KAAK;GAUhE,MAAM,SATW,MAAM,WAAW,aAAa,QAAQ,OAAO,EAC5D,QAAQ;IACN,MAAM,MAAM;IACZ;IACA,gBAAgB,uBACd,mBAAmB,MAAM,eAAe,CACzC;IACF,EACF,CAAC,EACqB,QAAQ;AAC/B,OAAI,SAAS,KACX,mBAAkB,cAChB,iBACA,WACA,MACA,MACD;AAEH,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAKjD,MAAK,MAAM,QAAQ,QAAQ,SAAS;EAClC,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,WAAW,gBAAgB,iBAAiB,WAAW,KAAK;AAClE,MAAI,YAAY,MAAM;AACpB,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qCAAqC,KAAK;IAClD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;AAE/C,MAAI;GACF,MAAM,QAAQ,MAAM,eAA4B,WAAW,KAAK;AAChE,SAAM,WAAW,aAAa,QAAQ,OAAO,UAAU,EACrD,QAAQ;IACN,MAAM,MAAM;IACZ;IACA,gBAAgB,uBACd,mBAAmB,MAAM,eAAe,CACzC;IACF,EACF,CAAC;AACF,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAKjD,MAAK,MAAM,QAAQ,QAAQ,SAAS;EAClC,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,WAAW,gBAAgB,iBAAiB,WAAW,KAAK;AAClE,MAAI,YAAY,MAAM;AACpB,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qCAAqC,KAAK;IAClD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;AAE/C,MAAI;AACF,SAAM,WAAW,aAAa,QAAQ,OAAO,SAAS;AACtD,qBAAkB,cAAc,iBAAiB,WAAW,KAAK;AACjE,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAIjD,QAAO;EAAE;EAAS,UAAU;EAAiB;;;;;AAM/C,eAAe,WACb,QACA,OACA,WACA,SACA,UAC8D;CAC9D,MAAM,UAAwB,EAAE;CAChC,IAAI,kBAAkB;AAGtB,MAAK,MAAM,QAAQ,QAAQ,KAAK;EAC9B,MAAM,OAAO,aAAa,KAAK;AAC/B,MAAI;GACF,MAAM,QAAQ,MAAM,eAA2B,WAAW,KAAK;GAQ/D,MAAM,SAPW,MAAM,WAAW,YAAY,QAAQ,OAAO,EAC3D,OAAO;IACL,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,QAAQ,uBAAuB,EAAE,GAAG,MAAM,QAAQ,CAAC;IACpD,EACF,CAAC,EACqB,OAAO;AAC9B,OAAI,SAAS,KACX,mBAAkB,cAAc,iBAAiB,UAAU,MAAM,MAAM;AAEzE,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAKjD,MAAK,MAAM,QAAQ,QAAQ,SAAS;EAClC,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,UAAU,gBAAgB,iBAAiB,UAAU,KAAK;AAChE,MAAI,WAAW,MAAM;AACnB,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,oCAAoC,KAAK;IACjD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;AAE/C,MAAI;GACF,MAAM,QAAQ,MAAM,eAA2B,WAAW,KAAK;AAC/D,SAAM,WAAW,YAAY,QAAQ,OAAO,SAAS,EACnD,OAAO;IACL,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,QAAQ,uBAAuB,EAAE,GAAG,MAAM,QAAQ,CAAC;IACpD,EACF,CAAC;AACF,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAKjD,MAAK,MAAM,QAAQ,QAAQ,SAAS;EAClC,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,UAAU,gBAAgB,iBAAiB,UAAU,KAAK;AAChE,MAAI,WAAW,MAAM;AACnB,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,oCAAoC,KAAK;IACjD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;AAE/C,MAAI;AACF,SAAM,WAAW,YAAY,QAAQ,OAAO,QAAQ;AACpD,qBAAkB,cAAc,iBAAiB,UAAU,KAAK;AAChE,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAIjD,QAAO;EAAE;EAAS,UAAU;EAAiB;;;;;;;AAQ/C,SAAS,+BACP,OACA,UACsB;AACtB,QAAO,MAAM,KAAK,SAAS;EACzB,MAAM,WAAW,KAAK,SACjB,gBAAgB,UAAU,WAAW,KAAK,OAAO,IAAI,KAAA,IACtD,KAAA;AAYJ,SAXmC;GACjC,GAAI,KAAK,KAAK,EAAE,IAAI,KAAK,IAAI,GAAG,EAAE;GAClC,OAAO,KAAK,SAAS;GACrB,UAAU,KAAK,YAAY;GAC3B,MAAM,KAAK;GACX,WAAW,YAAY;GACvB,MAAM,KAAK;GACX,QAAS,KAAK,UAAyC;GACvD,WAAW,KAAK;GAChB,UAAU,+BAA+B,KAAK,YAAY,EAAE,EAAE,SAAS;GACxE;GAED;;;;;;;AAQJ,SAAS,uBACP,OACA,iBACA,OAAkC,EAAE,EACT;AAC3B,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,EAAE,UAAU,GAAG,SAAS;EAC9B,MAAM,YAAY,KAAK;AACvB,OAAK,KAAK;GAAE,GAAG;GAAM;GAAiB,CAAC;AACvC,MAAI,YAAY,SAAS,SAAS,EAChC,wBAAuB,UAAU,WAAW,KAAK;;AAGrD,QAAO;;;;;;;AAQT,SAAS,6BACP,OACA,OAA4B,EAAE,EACT;AACrB,MAAK,MAAM,QAAQ,OAAO;AACxB,+BAA6B,KAAK,YAAY,EAAE,EAAE,KAAK;AACvD,OAAK,KAAK,KAAK;;AAEjB,QAAO;;AAGT,SAAS,8BACP,MACA,qBACe;AACf,KAAI,KAAK,mBAAmB,MAAM;EAChC,MAAM,iBAAiB,oBAAoB,IAAI,KAAK,gBAAgB;AACpE,MAAI,kBAAkB,KACpB,OAAM,IAAI,MACR,wEACD;AAEH,SAAO;;AAMT,QAAO;;AAGT,eAAe,+BACb,QACA,OACA,cACA,YACA,gBACA,eACA,MACiB;AACjB,KAAI,cAAc,MAAM;AACtB,iBAAe,IAAI,WAAW;AAC9B,SAAO;;CAOT,IAAI;AACJ,KAAI;AACF,aAAW,MAAM,WAAW,oBAC1B,QACA,OACA,aACD;UACM,eAAe;AACtB,QAAM,IAAI,kCACR,uIAAuI,qBAAqB,cAAc,CAAC,YAAY,KAAK,2EAC7L;;CAEH,MAAM,cAAc,6BAClB,SAAS,oBAAoB,EAAE,CAChC,CAAC,QAAQ,SAAS,CAAC,eAAe,IAAI,KAAK,GAAG,CAAC;CAChD,MAAM,gBAAgB,YAAY,QAC/B,UACE,KAAK,SAAS,QAAQ,cAAc,UACpC,KAAK,YAAY,OAAO,cAAc,aACtC,KAAK,QAAQ,KAAA,OAAe,cAAc,SAC1C,KAAK,aAAa,KAAA,OAAe,cAAc,cAC/C,KAAK,QAAQ,KAAA,OAAe,cAAc,SAC1C,KAAK,UAAU,aAAa,cAAc,UAAU,YACpD,KAAK,aAAa,WAAW,cAAc,aAAa,MAC5D;AAED,KAAI,YAAY,WAAW,KAAK,cAAc,WAAW,EACvD,OAAM,IAAI,kCACR,4HAA4H,cAAc,OAAO,wBAAwB,YAAY,OAAO,6BAA6B,KAAK,2EAC/N;CAGH,MAAM,cAAc,cAAc,IAAI;AACtC,KAAI,eAAe,KACjB,OAAM,IAAI,kCACR,iHAAiH,KAAK,2EACvH;AAEH,gBAAe,IAAI,YAAY;AAC/B,QAAO;;AAGT,SAAS,uBACP,YACA,aACA,UACiB;CAGjB,IAAI,eAAe;CACnB,IAAI;CAEJ,SAAS,OAAO,OAAqD;AACnE,SAAO,MAAM,KAAK,SAAS;GACzB,MAAM,YAAY;GAClB,MAAM,WAAW,OAAO,KAAK,YAAY,EAAE,CAAC;AAC5C,OAAI,cAAc,YAAa,QAAO;IAAE,GAAG;IAAM;IAAU;AAE3D,gBAAa,KAAK;AAClB,UAAO;IAAE,GAAG;IAAM,IAAI;IAAU;IAAU;IAC1C;;CAGJ,SAAS,iBACP,OACuB;AACvB,SAAO,MAAM,KAAK,UAAU;GAC1B,GAAG;GACH,WAAW,KAAK,cAAc,aAAa,WAAW,KAAK;GAC3D,UAAU,iBAAiB,KAAK,YAAY,EAAE,CAAC;GAChD,EAAE;;CAGL,MAAM,kBAAkB,OAAO,WAAW,iBAAiB;AAC3D,QAAO;EACL,GAAG;EACH,kBACE,cAAc,QAAQ,eAAe,WACjC,kBACA,iBAAiB,gBAAgB;EACxC;;AAGH,eAAe,+BACb,QACA,OACA,cACA,WACA,MACA,YACA,WACA,UAC0B;CAC1B,MAAM,UAAU,uBAAuB,YAAY,WAAW,SAAS;AACvE,KAAI;AACF,QAAM,gBACJ,KAAK,WAAW,KAAK,EACrB,KAAK,UAAU,SAAS,MAAM,EAAE,GAAG,KACpC;UACM,cAAc;AACrB,MAAI;AACF,SAAM,WAAW,qBACf,QACA,OACA,cACA,SACD;WACM,eAAe;AACtB,SAAM,IAAI,kCACR,qCAAqC,SAAS,YAAY,qBAAqB,aAAa,CAAC,oCAAoC,qBAAqB,cAAc,CAAC,YAAY,KAAK,2EACvL;;AAEH,QAAM;;AAER,QAAO;;;;;;;AAQT,eAAe,wBACb,QACA,OACA,OACA,WACA,MACA,OACA,UACe;CACf,MAAM,WAAW,MAAM,WAAW,oBAAoB,QAAQ,OAAO,MAAM;CAC3E,MAAM,aAA8B;EAClC,GAAG;EACH,kBAAkB,yBAChB,SAAS,oBAAoB,EAAE,EAC/B,iBAAiB,SAAS,QAAQ,CACnC;EACF;AAGD,OAAM,gBADW,KAAK,WAAW,KAAK,EACN,KAAK,UAAU,YAAY,MAAM,EAAE,GAAG,KAAK;;;;;AAM7E,eAAsB,gBACpB,QACA,OACA,WACA,SACA,UAC8D;CAC9D,MAAM,UAAwB,EAAE;CAChC,IAAI,kBAAkB;CAItB,MAAM,WAAW,QAAQ,IAAI,QAC1B,SACC,gBAAgB,iBAAiB,eAAe,aAAa,KAAK,CAAC,IACnE,KACH;CACD,MAAM,eAAe,CACnB,GAAG,QAAQ,SACX,GAAG,QAAQ,IAAI,QACZ,SACC,gBAAgB,iBAAiB,eAAe,aAAa,KAAK,CAAC,IACnE,KACH,CACF;AAGD,MAAK,MAAM,QAAQ,UAAU;EAC3B,MAAM,OAAO,aAAa,KAAK;EAC/B,IAAI,wBAAwB;AAC5B,MAAI;GACF,IAAI,QAAQ,MAAM,eAAgC,WAAW,KAAK;GAOlE,MAAM,SANW,MAAM,WAAW,iBAAiB,QAAQ,OAAO,EAChE,YAAY;IACV,MAAM,MAAM;IACZ,UAAU,MAAM;IACjB,EACF,CAAC,EACqB,YAAY;AACnC,OAAI,SAAS,MAAM;AACjB,sBAAkB,cAChB,iBACA,eACA,MACA,MACD;IAID,MAAM,gBAAgB,uBACpB,+BACE,MAAM,kBACN,gBACD,CACF;IACD,MAAM,sCAAsB,IAAI,KAAqB;IACrD,MAAM,iCAAiB,IAAI,KAAa;AAExC,SAAK,MAAM,CAAC,WAAW,SAAS,cAAc,SAAS,EAAE;KACvD,MAAM,mBAAmB,8BACvB,MACA,oBACD;KAED,MAAM,aAAwC;MAC5C,OAAO,KAAK,SAAS;MACrB,UAAU,KAAK,YAAY;MAC3B,MAAM,KAAK,QAAQ,KAAA;MACnB,WAAW,KAAK,aAAa,KAAA;MAC7B,MAAM,KAAK,QAAQ,KAAA;MACnB,QAAQ,KAAK,UAAU,KAAA;MACvB,WAAW;MACZ;KAUD,MAAM,YAAY,MAAM,+BACtB,QACA,OACA,QAZc,MAAM,WAAW,qBAC/B,QACA,OACA,OACA,EACE,iBAAiB,YAClB,CACF,EAMS,iBAAiB,IACzB,gBACA,YACA,KACD;AACD,aAAQ,MAAM,+BACZ,QACA,OACA,OACA,WACA,MACA,OACA,WACA,UACD;AACD,yBAAoB,IAAI,WAAW,UAAU;;AAG/C,4BAAwB;AACxB,UAAM,wBACJ,QACA,OACA,OACA,WACA,MACA,OACA,gBACD;;AAEH,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,GAAG,sBAAsB,KAAK,sBAAsB;IACrD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAKjD,MAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,QAAQ,gBAAgB,iBAAiB,eAAe,KAAK;AACnE,MAAI,SAAS,MAAM;AACjB,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,yCAAyC,KAAK;IACtD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;EAE/C,IAAI,wBAAwB;AAC5B,MAAI;GACF,IAAI,QAAQ,MAAM,eAAgC,WAAW,KAAK;AAGlE,SAAM,WAAW,iBAAiB,QAAQ,OAAO,OAAO,EACtD,YAAY;IACV,MAAM,MAAM;IACZ,UAAU,MAAM;IACjB,EACF,CAAC;GAMF,MAAM,gBAAgB,uBACpB,+BAA+B,MAAM,kBAAkB,gBAAgB,CACxE;GAOD,MAAM,cAAc,8BALG,MAAM,WAAW,oBACtC,QACA,OACA,MACD,EAEgB,oBAAoB,EAAE,CACtC;GACD,MAAM,aAAa,IAAI,IAAI,YAAY,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;GAC7D,MAAM,iBAAiB,IAAI,IAAI,WAAW,MAAM,CAAC;GACjD,MAAM,WAAW,IAAI,IACnB,cAAc,QAAQ,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM,EAAE,GAAG,CACnD;GAKD,MAAM,sCAAsB,IAAI,KAAqB;AAErD,QAAK,MAAM,CAAC,WAAW,SAAS,cAAc,SAAS,EAAE;IACvD,MAAM,mBAAmB,8BACvB,MACA,oBACD;IAED,MAAM,OAAO;KACX,OAAO,KAAK;KACZ,UAAU,KAAK;KACf,MAAM,KAAK,QAAQ,KAAA;KACnB,WAAW,KAAK,aAAa,KAAA;KAC7B,MAAM,KAAK,QAAQ,KAAA;KACnB,QAAQ,KAAK,UAAU,KAAA;KACvB,WAAW;KACZ;AAED,QAAI,KAAK,MAAM,WAAW,IAAI,KAAK,GAAG,EAAE;AAEtC,WAAM,WAAW,qBAAqB,QAAQ,OAAO,OAAO,KAAK,IAAI,EACnE,iBAAiB,MAClB,CAAC;AACF,yBAAoB,IAAI,WAAW,KAAK,GAAG;WACtC;KAEL,MAAM,aAAwC;MAC5C,GAAG;MACH,OAAO,KAAK,SAAS;MACrB,UAAU,KAAK,YAAY;MAC5B;KAUD,MAAM,YAAY,MAAM,+BACtB,QACA,OACA,QAZc,MAAM,WAAW,qBAC/B,QACA,OACA,OACA,EACE,iBAAiB,YAClB,CACF,EAMS,iBAAiB,IACzB,gBACA,YACA,KACD;AACD,aAAQ,MAAM,+BACZ,QACA,OACA,OACA,WACA,MACA,OACA,WACA,UACD;AACD,yBAAoB,IAAI,WAAW,UAAU;;;AAOjD,QAAK,MAAM,cAAc,YACvB,KAAI,CAAC,SAAS,IAAI,WAAW,GAAG,CAC9B,OAAM,WAAW,qBACf,QACA,OACA,OACA,WAAW,GACZ;AAIL,2BAAwB;AACxB,SAAM,wBACJ,QACA,OACA,OACA,WACA,MACA,OACA,gBACD;AAED,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,GAAG,sBAAsB,KAAK,sBAAsB;IACrD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAKjD,MAAK,MAAM,QAAQ,QAAQ,SAAS;EAClC,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,QAAQ,gBAAgB,iBAAiB,eAAe,KAAK;AACnE,MAAI,SAAS,MAAM;AACjB,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,yCAAyC,KAAK;IACtD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;AAE/C,MAAI;AACF,SAAM,WAAW,iBAAiB,QAAQ,OAAO,MAAM;AACvD,qBAAkB,cAAc,iBAAiB,eAAe,KAAK;AACrE,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAIjD,QAAO;EAAE;EAAS,UAAU;EAAiB;;;;;AAM/C,eAAe,aACb,QACA,OACA,WACA,SACA,UAC8D;CAC9D,MAAM,UAAwB,EAAE;CAChC,IAAI,kBAAkB;AAGtB,MAAK,MAAM,QAAQ,QAAQ,KAAK;EAC9B,MAAM,OAAO,aAAa,KAAK;AAC/B,MAAI;GAEF,MAAM,OAAO,mBADC,MAAM,eAA6B,WAAW,KAAK,EAC1B,gBAAgB;GAIvD,MAAM,SAHW,MAAM,WAAW,cAAc,QAAQ,OAAO,EAC7D,SAAS,MACV,CAAC,EACqB,SAAS;AAChC,OAAI,SAAS,KACX,mBAAkB,cAChB,iBACA,YACA,MACA,MACD;AAEH,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAKjD,MAAK,MAAM,QAAQ,QAAQ,SAAS;EAClC,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,YAAY,gBAAgB,iBAAiB,YAAY,KAAK;AACpE,MAAI,aAAa,MAAM;AACrB,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,sCAAsC,KAAK;IACnD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;AAE/C,MAAI;GAEF,MAAM,OAAO,mBADC,MAAM,eAA6B,WAAW,KAAK,EAC1B,gBAAgB;AACvD,SAAM,WAAW,cAAc,QAAQ,OAAO,WAAW,EACvD,SAAS,MACV,CAAC;AACF,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAKjD,MAAK,MAAM,QAAQ,QAAQ,SAAS;EAClC,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,YAAY,gBAAgB,iBAAiB,YAAY,KAAK;AACpE,MAAI,aAAa,MAAM;AACrB,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,sCAAsC,KAAK;IACnD,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;AAE/C,MAAI;AACF,SAAM,WAAW,cAAc,QAAQ,OAAO,UAAU;AACxD,qBAAkB,cAAc,iBAAiB,YAAY,KAAK;AAClE,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAW,SAAS;IAAM,CAAC;WACjD,KAAK;AACZ,WAAQ,KAAK;IACX;IACA,QAAQ;IACR,SAAS;IACT,OAAO,qBAAqB,IAAI;IACjC,CAAC;AACF,UAAO;IAAE;IAAS,UAAU;IAAiB;;;AAIjD,QAAO;EAAE;EAAS,UAAU;EAAiB;;;;;AAqB/C,SAAS,mBACP,OACA,UACa;CACb,MAAM,OAAoB;EACxB,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,aAAa,MAAM;EACpB;AAED,KAAI,MAAM,YAAY;EACpB,MAAM,QAAQ,gBAAgB,UAAU,eAAe,MAAM,WAAW;AACxE,MAAI,SAAS,KACX,MAAK,gBAAgB;;AAIzB,KAAI,MAAM,mBAAmB;EAC3B,MAAM,cAAc,gBAClB,UACA,eACA,MAAM,kBACP;AACD,MAAI,eAAe,KACjB,MAAK,uBAAuB;;AAOhC,MAAK,YAHY,MAAM,OACpB,KAAK,SAAS,gBAAgB,UAAU,UAAU,KAAK,CAAC,CACxD,QAAQ,OAAqB,MAAM,KAAK;AAG3C,QAAO;;AAOT,SAAS,oBAAoB,MAAoB,gBAA8B;AAC7E,SAAQ,IACN,MAAM,KAAK,uBAAuB,GAChC,MAAM,MAAM,KAAK,IAAI,eAAe,GAAG,GACvC,MAAM,KAAK,IAAI,CAClB;AACD,SAAQ,KAAK;AAEb,KAAI,KAAK,IAAI,SAAS,EACpB,SAAQ,IAAI,MAAM,MAAM,cAAc,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC;AAE/D,KAAI,KAAK,QAAQ,SAAS,EACxB,SAAQ,IAAI,MAAM,OAAO,cAAc,GAAG,KAAK,QAAQ,KAAK,KAAK,CAAC;AAEpE,KAAI,KAAK,QAAQ,SAAS,EACxB,SAAQ,IAAI,MAAM,IAAI,cAAc,GAAG,KAAK,QAAQ,KAAK,KAAK,CAAC;AAEjE,SAAQ,KAAK;;AAGf,SAAS,gBAAgB,SAAuB,eAA+B;CAC7E,MAAM,YAAY,QAAQ,QAAQ,MAAM,EAAE,QAAQ;CAClD,MAAM,SAAS,QAAQ,QAAQ,MAAM,CAAC,EAAE,QAAQ;AAEhD,KAAI,UAAU,SAAS,GAAG;AACxB,UAAQ,IAAI,MAAM,MAAM,KAAK,aAAa,CAAC;AAC3C,OAAK,MAAM,KAAK,UACd,SAAQ,IAAI,MAAM,MAAM,OAAO,EAAE,SAAS,KAAK,GAAG,EAAE,KAAK;;AAI7D,KAAI,OAAO,SAAS,GAAG;AACrB,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,IAAI,KAAK,UAAU,CAAC;AACtC,OAAK,MAAM,KAAK,OACd,SAAQ,IACN,MAAM,IAAI,OAAO,EAAE,SAAS,KAAK,GAC/B,EAAE,OACF,MAAM,KAAK,SAAS,EAAE,SAAS,iBAAiB,CACnD;;AAIL,KAAI,cAAc,SAAS,GAAG;AAC5B,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,OAAO,KAAK,kBAAkB,CAAC;AACjD,OAAK,MAAM,SAAS,cAClB,SAAQ,IAAI,MAAM,OAAO,OAAO,MAAM,CAAC;;;AAS7C,MAAa,cAAuB,IAAI,QAAQ,OAAO,CACpD,YAAY,wDAAwD,CACpE,OAAO,SAAS,2BAA2B,CAC3C,OACC,2BACA,6DACD,CACA,OACC,+BACA,4DACD,CACA,OACC,cACA,6DACD,CACA,OACC,2BACA,8EACD,CACA,OAAO,OAAO,YAAyB;CACtC,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,YAAY,KAAK,KAAK,WAAW;CACvC,MAAM,gBAAgB,KAAK,KAAKD,kBAAgB;AAEhD,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,KAAK,oBAAoB,CAAC;AACjD,SAAQ,KAAK;AAGb,KAAI,CAAC,WAAW,UAAU,EAAE;AAC1B,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,sCACA,MAAM,KAAK,oBAAoB,GAC/B,UACH;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;CAIjB,MAAM,WAAW,MAAM,aAAa,cAAc;AAClD,KAAI,CAAC,UAAU;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,8CACA,MAAM,KAAK,oBAAoB,GAC/B,UACH;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;CAIjB,MAAM,WAAW,MAAM,aAAa,cAAc;AAClD,KAAI,CAAC,UAAU;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,8CACA,MAAM,KAAK,oBAAoB,GAC/B,UACH;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;CAGjB,MAAM,eAAe,SAAS;CAC9B,MAAM,iBAAiB,SAAS;AAEhC,SAAQ,IACN,MAAM,KAAK,eAAe,GACxB,MAAM,MAAM,eAAe,GAC3B,MAAM,KAAK,SAAS,aAAa,GAAG,CACvC;AACD,SAAQ,KAAK;AASb,KACE,CAAE,MAAM,oBACN,KACA,cACA,gBACA,QAAQ,QACT,EACD;AACA,UAAQ,WAAW;AACnB;;CAIF,MAAM,UAAU,KAAK;AACrB,SAAQ,MAAM,uBAAuB;CAErC,MAAM,OAAO,MAAM,oBAAoB,WAAW,SAAS;CAC3D,MAAM,eACJ,KAAK,IAAI,SAAS,KAAK,QAAQ,SAAS,KAAK,QAAQ;AAEvD,KAAI,iBAAiB,GAAG;AACtB,UAAQ,QAAQ,mBAAmB;AACnC,UAAQ,KAAK;AACb,MAAI,QAAQ,UAAU;AACpB,SAAM,cAAc,aAAa;AACjC,WAAQ,KAAK;;AAEf;;AAGF,SAAQ,QAAQ,SAAS,aAAa,YAAY;AAClD,SAAQ,KAAK;AAGb,qBAAoB,MAAM,eAAe;AAEzC,KAAI,CAAC,QAAQ,KAAK;EAChB,MAAM,EAAE,cAAc,MAAM,QAAQ;GAClC,MAAM;GACN,MAAM;GACN,SAAS,QAAQ,aAAa;GAC9B,SAAS;GACV,CAAC;AAEF,MAAI,CAAC,WAAW;AACd,WAAQ,KAAK;AACb,WAAQ,IAAI,MAAM,KAAK,kBAAkB,CAAC;AAC1C,WAAQ,KAAK;AACb;;AAEF,UAAQ,KAAK;;CAIf,MAAM,UAAU,kBAAkB,KAAK;AAGvC,SAAQ,MAAM,6BAA6B;CAC3C,MAAM,eAAe,MAAM,sBAAsB,UAAU;CAC3D,MAAM,mBACJ,aAAa,SAAS,IAClB,eACA,MAAM,wBAAwB,WAAW,UAAU,QAAQ;AAEjE,KAAI,iBAAiB,SAAS,GAAG;AAC/B,UAAQ,KAAK,gCAAgC;AAC7C,UAAQ,KAAK;AACb,OAAK,MAAM,OAAO,iBAChB,SAAQ,IAAI,MAAM,IAAI,OAAO,IAAI,OAAO,KAAK,GAAG,IAAI,QAAQ;AAE9D,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;AAEjB,SAAQ,QAAQ,qBAAqB;CAKrC,MAAM,oBAAoB,MAAM,wBAAwB,WAAW,QAAQ;AAC3E,KAAI,kBAAkB,SAAS,GAAG;AAChC,UAAQ,KAAK;AACb,OAAK,MAAM,WAAW,kBACpB,SAAQ,IACN,MAAM,OAAO,cAAc,GACzB,MAAM,MAAM,QAAQ,OAAO,KAAK,GAChC,QAAQ,QACX;AAEH,UAAQ,KAAK;;AAIf,SAAQ,MAAM,oBAAoB;CAElC,IAAI;AACJ,KAAI;AACF,WAASC,gBAAc;AACvB,UAAQ,QAAQ,gBAAgB;UACzB,KAAK;AACZ,UAAQ,KAAK,wBAAwB;AACrC,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;CAIjB,MAAM,qBAAqB,CACzB,GAAG,QAAQ,QAAQ,KACnB,GAAG,QAAQ,QAAQ,QACpB;AAYD,MAVE,MAAM,QAAQ,IACZ,mBAAmB,IAAI,OAAO,SAC5B,8BACE,KAAK,MACH,MAAM,SAAS,KAAK,WAAW,KAAK,EAAE,OAAO,CAC9C,CACF,CACF,CACF,EACD,MAAM,WAAW,OAAO,SAAS,EAAE,CAEnC,KAAI;EACF,MAAM,UAAU,MAAM,WAAW,mBAAmB,OAAO;EAC3D,MAAM,eAAe,MAAM,4BAA4B;GACrD;GACA,aAAa;GACb,UAAU,SAAS;GACnB,mBAAmB,QAAQ;GAC5B,CAAC;AACF,MAAI,aAAa,SAAS,GAAG;AAC3B,WAAQ,KAAK;AACb,WAAQ,IAAI,MAAM,OAAO,KAAK,mCAAmC,CAAC;AAClE,QAAK,MAAM,SAAS,aAClB,SAAQ,IACN,KAAK,MAAM,MAAM,MAAM,WAAW,CAAC,IAAI,MAAM,OAAO,MAAM,WAAW,CAAC,IAAI,MAAM,KAAK,GAAG,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,IAAI,MAAM,KAAK,GAAG,MAAM,KAAK,IAAI,MAAM,SAAS,GAAG,GACvL;AAEH,WAAQ,KAAK;AAMb,OAJsB,aAAa,MAChC,EAAE,iBACD,CAAC,sCAAsC,YAAY,QAAQ,CAC9D,EACkB;AACjB,QAAI,CAAC,QAAQ,MAAM,MACjB,OAAM,IAAI,MACR,gMACD;IAEH,MAAM,EAAE,aAAa,MAAM,QAAQ;KACjC,MAAM;KACN,MAAM;KACN,SACE;KACF,SAAS;KACV,CAAC;AACF,QAAI,CAAC,UAAU;AACb,aAAQ,KAAK;AACb,aAAQ,IAAI,MAAM,KAAK,kBAAkB,CAAC;AAC1C,aAAQ,KAAK;AACb;;AAEF,YAAQ,KAAK;;AAEf,SAAM,0CAA0C;IAC9C;IACA,QAAQ;IACT,CAAC;;UAEG,OAAO;AACd,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAC1D;AACD,UAAQ,KAAK;AACb,UAAQ,WAAW;AACnB;;CAKJ,MAAM,aAA2B,EAAE;CACnC,MAAM,gBAA0B,EAAE;CAClC,IAAI,kBAAkB;CACtB,IAAI,UAAU;CAEd,MAAM,mBACJ,QAAQ,QAAQ,IAAI,SAAS,KAC7B,QAAQ,QAAQ,QAAQ,SAAS,KACjC,QAAQ,QAAQ,QAAQ,SAAS;CACnC,MAAM,kBACJ,QAAQ,OAAO,IAAI,SAAS,KAC5B,QAAQ,OAAO,QAAQ,SAAS,KAChC,QAAQ,OAAO,QAAQ,SAAS;AAElC,KAAI,oBAAoB,iBAAiB;AACvC,UAAQ,MAAM,yCAAyC;EAEvD,MAAM,cAGC,EAAE;AAET,MAAI,iBACF,aAAY,KACV,YACE,QACA,cACA,WACA,QAAQ,SACR,gBACD,CACF;AAEH,MAAI,gBACF,aAAY,KACV,WACE,QACA,cACA,WACA,QAAQ,QACR,gBACD,CACF;EAGH,MAAM,gBAAgB,MAAM,QAAQ,IAAI,YAAY;AAIpD,OAAK,MAAM,UAAU,cACnB,YAAW,KAAK,GAAG,OAAO,QAAQ;AAEpC,MAAI,iBACF,mBAAkB;GAChB,GAAG;GACH,SAAS,cAAc,GAAI,SAAS;GACrC;AAEH,MAAI,iBAAiB;GACnB,MAAM,MAAM,mBAAmB,IAAI;AACnC,qBAAkB;IAChB,GAAG;IACH,QAAQ,cAAc,KAAM,SAAS;IACtC;;AAOH,MAJqB,cAAc,MAAM,MACvC,EAAE,QAAQ,MAAM,QAAQ,CAAC,IAAI,QAAQ,CACtC,EAEiB;AAChB,WAAQ,KAAK,iBAAiB;AAC9B,aAAU;AACV,iBAAc,KAAK,wBAAwB,oBAAoB;QAE/D,SAAQ,QAAQ,mBAAmB;;CAKvC,MAAM,gBACJ,QAAQ,YAAY,IAAI,SAAS,KACjC,QAAQ,YAAY,QAAQ,SAAS,KACrC,QAAQ,YAAY,QAAQ,SAAS;AAEvC,KAAI,CAAC,WAAW,eAAe;AAC7B,UAAQ,MAAM,kCAAkC;EAEhD,MAAM,YAAY,MAAM,gBACtB,QACA,cACA,WACA,QAAQ,aACR,gBACD;AAED,aAAW,KAAK,GAAG,UAAU,QAAQ;AACrC,oBAAkB;GAChB,GAAG;GACH,aAAa,UAAU,SAAS;GACjC;AAGD,MADqB,UAAU,QAAQ,MAAM,MAAM,CAAC,EAAE,QAAQ,EAC5C;AAChB,WAAQ,KAAK,iBAAiB;AAC9B,aAAU;AACV,iBAAc,KAAK,oBAAoB;QAEvC,SAAQ,QAAQ,mBAAmB;YAE5B,WAAW,eAAe;CAKrC,MAAM,oBACJ,QAAQ,SAAS,IAAI,SAAS,KAC9B,QAAQ,SAAS,QAAQ,SAAS,KAClC,QAAQ,SAAS,QAAQ,SAAS;AAEpC,KAAI,CAAC,WAAW,mBAAmB;AACjC,UAAQ,MAAM,+BAA+B;EAE7C,MAAM,gBAAgB,MAAM,aAC1B,QACA,cACA,WACA,QAAQ,UACR,gBACD;AAED,aAAW,KAAK,GAAG,cAAc,QAAQ;AACzC,oBAAkB;GAChB,GAAG;GACH,UAAU,cAAc,SAAS;GAClC;AAGD,MADqB,cAAc,QAAQ,MAAM,MAAM,CAAC,EAAE,QAAQ,CAEhE,SAAQ,KAAK,iBAAiB;MAE9B,SAAQ,QAAQ,mBAAmB;;AAKvC,OAAM,cAAc,eAAe,gBAAgB;CAGnD,MAAM,kBAAkB,IAAI,IAC1B,WACG,QAAQ,MAAM,EAAE,WAAW,EAAE,gBAAgB,CAC7C,KAAK,MAAM,EAAE,KAAK,CACtB;AAED,KAAI,gBAAgB,OAAO,GAAG;EAK5B,MAAM,gBAAgB,EAAE,GAAG,SAAS,OAAO;AAC3C,OAAK,MAAM,QAAQ,iBAAiB;GAClC,MAAM,WAAW,KAAK,WAAW,KAAK;AACtC,OAAI,WAAW,SAAS,CACtB,eAAc,QAAQ,MAAM,gBAAgB,SAAS;OAGrD,QAAO,cAAc;;EAGzB,MAAM,0BAA0B,EAC9B,GAAG,SAAS,0BACb;EACD,MAAM,0BACJ,MAAM,sCACJ,WACA,CAAC,GAAG,gBAAgB,CAAC,QAClB,SACC,KAAK,WAAW,WAAW,IAC3B,KAAK,SAAS,QAAQ,IACtB,WAAW,KAAK,WAAW,KAAK,CAAC,CACpC,CACF;AACH,OAAK,MAAM,QAAQ,iBAAiB;AAClC,OAAI,CAAC,KAAK,WAAW,WAAW,IAAI,CAAC,KAAK,SAAS,QAAQ,CAAE;GAC7D,MAAM,SAAS,wBAAwB;AACvC,OAAI,UAAU,OAAO,SAAS,EAAG,yBAAwB,QAAQ;OAC5D,QAAO,wBAAwB;;AAEtC,QAAM,cAAc,eAAe;GACjC,GAAG;GACH,OAAO;GACP,0BAA0B;GAC1B,uBAAuB,gCACrB,wBACD;GACF,CAAC;;AAIJ,SAAQ,KAAK;CACb,MAAM,eAAe,WAAW,OAAO,MAAM,EAAE,QAAQ;AAEvD,KAAI,gBAAgB,CAAC,QACnB,SAAQ,IAAI,MAAM,MAAM,KAAK,iBAAiB,CAAC;KAE/C,SAAQ,IAAI,MAAM,OAAO,KAAK,8BAA8B,CAAC;AAE/D,SAAQ,KAAK;AACb,iBAAgB,YAAY,cAAc;AAC1C,SAAQ,KAAK;AAEb,KAAI,CAAC,gBAAgB,SAAS;AAC5B,UAAQ,WAAW;AACnB;;AAEF,KAAI,QAAQ,UAAU;AACpB,QAAM,cAAc,cAAc,OAAO;AACzC,UAAQ,KAAK;;EAEf;;;;;;;;;;;;;;;;;;;AC9vDJ,SAAS,aAAqB;AAC5B,QAAO,QAAQ,IAAI,qBAAqB;;AAG1C,eAAe,yBACb,WACwB;AACxB,KAAI;AACF,QAAM,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AACrD,SAAO;UACA,OAAO;AACd,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;;;AAIjE,SAASC,eAAa,OAA8B;AAClD,QAAO,oBAAoB;EACzB,SAAS,YAAY;EACrB;EACD,CAAC;;;;;;;;AASJ,SAAgB,yBAAyB,SAI9B;CACT,MAAM,aAAa,gFAAgF,MAAM,KAAK,oBAAoB,CAAC,GAAG,MAAM,KAAK,OAAO,CAAC,mEAAmE,MAAM,KAAK,oBAAoB,CAAC;AAE5P,KAAI,QAAQ,eAAe,eACzB,QAAO,+EAA+E;AAExF,KAAI,QAAQ,eAAe,QACzB,QAAO,oDAAoD;AAE7D,KAAI,QAAQ,iBAAiB,gBAG3B,QACE,6FACA;AAGJ,KAAI,QAAQ,iBAAiB,kBAC3B,QAAO,sDAAsD;AAE/D,QAAO,6BAA6B,QAAQ,OAAO,KAAK;;;;;;;AAQ1D,eAAsB,+BACpB,WACkB;AAClB,QACG,MAAM,WAAW,KAAK,WAAA,iCAA0C,CAAC,IAClE,CAAC,WAAW,KAAK,WAAW,OAAO,CAAC;;AAIxC,eAAsB,wBACpB,WACA,qBACkB;AAClB,KACE,CAAE,MAAM,WAAW,KAAK,WAAA,iCAA0C,CAAC,IACnE,CAAE,MAAM,UAAU,UAAU,CAE5B,QAAO;CAET,MAAM,SAAS,MAAM,aAAa,UAAU;AAC5C,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,WAAW,kBAAkB,oBAAoB;AACvD,QAAO,OAAO,QAAQ,OAAO,GAAG,KAAK,SAAS,QAAQ,OAAO,GAAG;;AAalE,SAAgB,kBACd,aACA,SACA,iBAC2B;AAC3B,KAAI,oBAAoB,KAAA,GAAW;AACjC,MAAI,CAAC,aAAa,KAAK,gBAAgB,CACrC,OAAM,IAAI,MAAM,kCAAkC;EAEpD,MAAM,eAAe,OAAO,SAAS,iBAAiB,GAAG;EACzD,MAAM,QAAQ,YAAY,MACvB,eAAe,WAAW,OAAO,aACnC;AACD,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,+BAA+B,aAAa,GAAG;AAEjE,SAAO;GACL,IAAI;GACJ,MAAM,MAAM,QAAQ,UAAU;GAC/B;;AAGH,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,QAAQ,YAAY,MACvB,eAAe,WAAW,MAAM,aAAa,KAAK,QAAQ,aAAa,CACzE;AACD,KAAI,CAAC,SAAS,MAAM,MAAM,MAAM;EAC9B,MAAM,YAAY,YACf,KAAK,eAAe,MAAM,KAAK,WAAW,QAAQ,YAAY,CAAC,CAC/D,KAAK,KAAK;AACb,QAAM,IAAI,MACR,iCAAiC,QAAQ,gBAAgB,YAC1D;;AAEH,QAAO;EAAE,IAAI,MAAM;EAAI,MAAM,MAAM,QAAQ,UAAU,MAAM;EAAM;;;AAInE,eAAe,iBACb,QACA,SACA,iBAC6B;CAC7B,MAAM,cAAmC,EAAE;CAC3C,IAAI,OAAO;AACX,QAAO,MAAM;EACX,MAAM,WAAW,MAAM,WAAW,gBAAgB,QAAQ;GACxD;GACA,UAAU;GACX,CAAC;EACF,MAAM,QAAQ,SAAS,eAAe,EAAE;AACxC,cAAY,KAAK,GAAG,MAAM;EAC1B,MAAM,aAAa,SAAS,MAAM,eAAe;AACjD,MAAI,QAAQ,cAAc,MAAM,WAAW,EAAG;AAC9C;;AAGF,KAAI,YAAY,WAAW,EACzB,OAAM,IAAI,MACR,0EACD;CAGH,MAAM,WAAW,kBAAkB,aAAa,SAAS,gBAAgB;AACzE,KAAI,SAAU,QAAO;CAErB,MAAM,EAAE,iBAAiB,MAAM,QAAQ;EACrC,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS,YACN,QAAQ,MAAM,EAAE,MAAM,KAAK,CAC3B,KAAK,OAAO;GAAE,OAAO,EAAE,QAAQ;GAAa,OAAO,EAAE;GAAI,EAAE;EAC/D,CAAC;AACF,KAAI,gBAAgB,KAAM,OAAM,IAAI,MAAM,0BAA0B;CACpE,MAAM,WAAW,YAAY,MAAM,MAAM,EAAE,OAAO,aAAa;AAC/D,KAAI,CAAC,YAAY,SAAS,MAAM,KAC9B,OAAM,IAAI,MAAM,iCAAiC;AAEnD,QAAO;EAAE,IAAI,SAAS;EAAI,MAAM,SAAS,QAAQ,UAAU,SAAS;EAAM;;AAG5E,MAAa,eAAwB,IAAI,QAAQ,QAAQ,CACtD,YACC,qFACD,CACA,SAAS,SAAS,mDAAmD,CACrE,OAAO,gBAAgB,oDAAoD,CAC3E,OAAO,aAAa,qDAAqD,CACzE,OAAO,eAAe,2CAA2C,CACjE,OAAO,OAAO,QAA4B,YAA0B;AACnE,KAAI;EACF,MAAM,QAAQ,cAAc;AAC5B,MAAI,CAAC,OAAO;GACV,MAAM,UAAU,kBAAkB;AAClC,SAAM,IAAI,MACR,UACI,6BAA6B,MAAM,KAAK,QAAQ,KAAK,CAAC,QAAQ,MAAM,KAAK,cAAc,CAAC,KACxF,sBAAsB,MAAM,KAAK,cAAc,CAAC,SACrD;;EAEH,MAAM,SAASA,eAAa,MAAM;EAElC,MAAM,UAAU,IAAI,wBAAwB,CAAC,OAAO;EACpD,MAAM,aAAa,MAAM,iBACvB,QACA,QAAQ,OAAO,QACf,QAAQ,GACT;AAED,UAAQ,OAAO;EACf,MAAM,UAAU,MAAM,mCAAmC;GACvD,SAAS,YAAY;GACrB;GACA,cAAc,WAAW;GACzB,UAAU,EAAE,SAAS,oBAAoB;AACvC,YAAQ,OAAO,uCAAuC,QAAQ,GAAG,cAAc;;GAElF,CAAC;AACF,MAAI,QAAQ,WAAW,SAAS;AAC9B,WAAQ,KAAK,iBAAiB,QAAQ,SAAS;AAC/C,WAAQ,KAAK,EAAE;;EAGjB,MAAM,OAAO,WAAW,WAAW,KAAK;EACxC,MAAM,YAAY,QAAQ,QAAQ,OAAO,KAAK;AAE9C,MAAI,QAAQ,WAAW,eAAe;AAKpC,WAAQ,KAAK,yBAAyB,QAAQ,CAAC;GAC/C,MAAM,gBACJ,WAAW,UAAU,IACpB,MAAM,+BAA+B,UAAU;AAClD,OAAI,WAAW,UAAU,IAAI,CAAC,eAAe;AAC3C,YAAQ,KACN,aAAa,UAAU,iDACxB;AACD,YAAQ,KAAK,EAAE;;AAEjB,OAAI,CAAC,eAAe;AAClB,UAAM,MAAM,QAAQ,UAAU,EAAE,EAAE,WAAW,MAAM,CAAC;AACpD,QAAI;AAGF,WAAM,MAAM,UAAU;aACf,OAAO;AACd,SAAK,MAAgC,SAAS,UAAU;AACtD,cAAQ,KACN,aAAa,UAAU,uFACxB;AACD,cAAQ,KAAK,EAAE;;AAEjB,WAAM;;;AAGV,OAAI;AACF,YAAQ,OAAO,gBACX,8BACA;AACJ,UAAM,2CAA2C,WAAW;KAC1D,MAAM;KACN,aAAa;KACb,eAAe,EAAE;KACjB,aAAa,kBAAkB,EAAE,QAAQ;KAC1C,CAAC;YACK,KAAK;AACZ,YAAQ,KACN,4DAA4D,UAAU,2DACvE;AACD,UAAM;;AAER,WAAQ,QACN,WAAW,MAAM,KAAK,UAAU,CAAC,iDAClC;AACD,WAAQ,IAAI,MAAM,IAAI,QAAQ,YAAY,CAAC;AAC3C,WAAQ,IACN,MAAM,IAAI,KAAK,MAAM,KAAK,oBAAoB,CAAC,kBAAkB,CAClE;AACD;;AAGF,QAAM,oBAAoB;EAC1B,MAAM,iBACJ,WAAW,UAAU,IACpB,MAAM,wBACL,WACA,QAAQ,WAAW,WACpB;AACH,MAAI,WAAW,UAAU,IAAI,CAAC,gBAAgB;AAC5C,WAAQ,KACN,aAAa,UAAU,iDACxB;AACD,WAAQ,KAAK,EAAE;;AAGjB,MAAI,CAAC,gBAAgB;AACnB,SAAM,MAAM,QAAQ,UAAU,EAAE,EAAE,WAAW,MAAM,CAAC;AACpD,OAAI;AAIF,UAAM,MAAM,UAAU;YACf,OAAO;AACd,QAAK,MAAgC,SAAS,UAAU;AACtD,aAAQ,KACN,aAAa,UAAU,uFACxB;AACD,aAAQ,KAAK,EAAE;;AAEjB,UAAM;;AAGR,WAAQ,OAAO,gBAAgB,KAAK;AACpC,OAAI;AAEF,UAAM,SAAS,QAAQ,WAAW,YAAY,UAAU;YACjD,KAAK;IACZ,MAAM,eAAe,MAAM,yBAAyB,UAAU;AAC9D,YAAQ,KACN,eACI,yEAAyE,iBACzE,oDACL;AACD,UAAM;;AAQR,4BAAyB,UAAU;;AAGrC,MACE,kBACA,CAAE,MAAM,WAAW,KAAK,WAAW,eAAe,CAAC,CAEnD,KAAI;AACF,WAAQ,OAAO,iBACX,uCACA;AACJ,SAAM,2CAA2C,WAAW;IAC1D,MAAM;IACN,aAAa;IACb,eAAe,EAAE;IACjB,aAAa,kBAAkB,EAAE,QAAQ;IAC1C,CAAC;WACK,KAAK;AACZ,WAAQ,KACN,wBAAwB,UAAU,2KACnC;AACD,SAAM;;AAGV,UAAQ,QAAQ,eAAe,MAAM,KAAK,UAAU,GAAG;AACvD,UAAQ,IAAI,MAAM,IAAI,QAAQ,YAAY,CAAC;AAC3C,UAAQ,IACN,MAAM,IAAI,KAAK,MAAM,KAAK,oBAAoB,CAAC,kBAAkB,CAClE;UACM,OAAO;AACd,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAC1D;AACD,UAAQ,KAAK,EAAE;;EAEjB;;;ACzYJ,eAAsB,8BACpB,SAC8C;CAC9C,MAAM,WAAW,QAAQ,WAAW;CACpC,MAAM,SAAS,QAAQ;AAEvB,KAAI,CAAC,YAAY,CAAC,OAChB,OAAM,IAAI,MAAM,yDAAyD;CAG3E,MAAM,aAAa,MAAM,qBAAqB;AAQ9C,KAAI,SACF,QAAO,MAAM,qBAAqB;EAChC,YAAY,QAAQ;EACpB,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,aAAa,QAAQ;EACrB;EACA,QAAQ;EACT,CAAC;AAGJ,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,yDAAyD;AAG3E,QAAO,MAAM,qBAAqB;EAChC,YAAY,QAAQ;EACpB,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,aAAa,QAAQ;EACrB;EACA,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;EACvD;EACD,CAAC;;;;;;AAOJ,SAAgB,wBAAwB,OAAsB;AAC5D,SAAQ,MACN,MAAM,IAAI,SAAS,GACjB,OACC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAC1D;AACD,KACE,OAAO,UAAU,YACjB,UAAU,QACV,aAAa,SACb,OAAO,MAAM,YAAY,YACzB,MAAM,QAAQ,SAAS,EAEvB,SAAQ,MAAM,MAAM,QAAQ;;AAIhC,MAAM,mBAAmB;;;;;;;;;;;;;;;;;AAkBzB,SAAgB,yCAA8D;CAC5E,MAAM,WACJ,QAAQ,IAAI,kBAAkB,QAAQ,IAAI,sBAAsB;AAClE,KAAI,SAAS,SAAS,EACpB,QAAO,0BAA0B;EAC/B,SAAS,QAAQ,IAAI,qBAAqB;EAC1C,oBAAoB;EACrB,CAAC;CAGJ,MAAM,EAAE,SAAS,aAAa,UAAU,6BAA6B;AACrE,KAAI,CAAC,OAAO;AACV,MAAI,CAAC,YACH,OAAM,IAAI,MACR,wBAAwB,MAAM,KAAK,cAAc,GAAG,UACrD;AAEH,QAAM,IAAI,MACR,qCACE,MAAM,KAAK,YAAY,GACvB,WACA,MAAM,KAAK,cAAc,GACzB,uBACH;;AAGH,QAAO,0BAA0B;EAC/B,SACE,SAAS,WAAW,QAAQ,IAAI,qBAAqB;EACvD,oBAAoB;EACrB,CAAC;;AAOJ,SAAS,8BAIP;CACA,MAAM,SAAS,YAAY;CAC3B,MAAM,gBAAgB,kBAAkB,QAAQ,KAAK,CAAC;AAEtD,KAAI,eAAe,SAAS;EAC1B,MAAM,UAAU,OAAO,SAAS,cAAc;AAG9C,MAAI,QACF,QAAO;GACL;GACA,aAAa,cAAc;GAC3B,OAAO,QAAQ;GAChB;;AAIL,KAAI,CAAC,OAAO,cAAe,QAAO,EAAE;CACpC,MAAM,UAAU,OAAO,SAAS,OAAO;AAGvC,KAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,QAAO;EACL;EACA,aAAa,OAAO;EACpB,OAAO,QAAQ;EAChB;;AAGH,SAAgB,0BAA0B,SAQjC;AACP,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,MAAM,KAAK,GAAG,QAAQ,MAAM,WAAW,CAAC;AAC1D,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,gBAAgB,GAAG,MAAM,MAAM,QAAQ,WAAW,CAAC;AAC1E,KAAI,QAAQ,YACV,SAAQ,IAAI,MAAM,KAAK,gBAAgB,GAAG,MAAM,MAAM,QAAQ,YAAY,CAAC;AAE7E,SAAQ,IAAI,MAAM,KAAK,gBAAgB,GAAG,MAAM,MAAM,QAAQ,UAAU,CAAC;AACzE,SAAQ,IAAI,MAAM,KAAK,gBAAgB,GAAG,MAAM,MAAM,QAAQ,QAAQ,CAAC;AACvE,SAAQ,IAAI,MAAM,KAAK,gBAAgB,GAAG,MAAM,KAAK,QAAQ,OAAO,CAAC;AACrE,SAAQ,IACN,MAAM,KAAK,gBAAgB,GAAG,MAAM,MAAM,OAAO,QAAQ,cAAc,CAAC,CACzE;AACD,SAAQ,KAAK;;AAGf,SAAgB,yBAA+B;AAC7C,SAAQ,IACN,MAAM,OACJ,4FACD,CACF;AACD,SAAQ,KAAK;;AAGf,SAAgB,0BAA0B,gBAA+B;AACvE,SAAQ,IAAI,MAAM,KAAK,kCAAkC,CAAC;AAC1D,SAAQ,IAAI,KAAK,UAAU,gBAAgB,MAAM,EAAE,CAAC;AACpD,SAAQ,KAAK;;;;;;;;;;;;ACjNf,SAAgB,aAAa,MAAsB;CACjD,MAAM,SAAS,KACZ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,OAAO,EAAE,CAAC,aAAa,GAAG,EAAE,MAAM,EAAE,CAAC,CAClD,KAAK,GAAG;AACX,QAAO,OAAO,SAAS,SAAS,GAAG,SAAS,GAAG,OAAO;;;;;;;;AASxD,SAAgB,gBAAgB,YAA4B;AAC1D,KAAI,eAAe,SAAU,QAAO;AAEpC,QADiB,WAAW,QAAQ,WAAW,GAAG,IAC/B;;;;;;AAOrB,SAAgB,cAAc,MAAsB;AAClD,QAAO,KACJ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,OAAO,EAAE,CAAC,aAAa,GAAG,EAAE,MAAM,EAAE,CAAC,CAClD,KAAK,IAAI;;;;;;AAOd,SAAgB,YAAY,MAAsB;AAChD,QAAO,KAAK,QAAQ,QAAQ,MAAM,EAAE,OAAO,EAAE,CAAC,aAAa,CAAC;;;;;;AAO9D,SAAgB,mBAAmB,MAA6B;AAC9D,KAAI,CAAC,gCAAgC,KAAK,KAAK,CAC7C,QAAO;AAET,KAAI,SAAS,SACX,QAAO;AAET,QAAO;;;;;;;AAQT,SAAgB,aACd,QACA,YACe;AAEf,KAAI,OAAO,SAAS,WAAW,CAAE,QAAO;CAGxC,MAAM,gBAAgB;CACtB,IAAI,kBAAkB;CACtB,IAAI;AACJ,SAAQ,QAAQ,cAAc,KAAK,OAAO,MAAM,KAC9C,mBAAkB,MAAM;AAE1B,KAAI,oBAAoB,GAAI,QAAO;CAEnC,MAAM,UAAU,OAAO,QAAQ,MAAM,gBAAgB;AACrD,KAAI,YAAY,GAEd,QAAO,SAAS,OAAO,aAAa;AAGtC,QACE,OAAO,MAAM,GAAG,UAAU,EAAE,GAAG,aAAa,OAAO,OAAO,MAAM,UAAU,EAAE;;;AAKhF,SAAgB,wBACd,QACA,YACe;CACf,MAAM,eAAe,OAAO,YAAY,uBAAuB;AAC/D,KAAI,iBAAiB,GAAI,QAAO;CAEhC,MAAM,gBAAgB,OAAO,MAAM,aAAa;CAChD,MAAM,UAAU;CAChB,IAAI,YAAoC;CACxC,IAAI;AACJ,SAAQ,IAAI,QAAQ,KAAK,cAAc,MAAM,KAC3C,aAAY;AAGd,KAAI,CAAC,UAAW,QAAO;CAEvB,MAAM,YAAY,UAAU;CAC5B,MAAM,cAAc,UAAU;CAC9B,MAAM,QAAQ,UAAU;CACxB,MAAM,aAAa,eAAe,UAAU;CAE5C,MAAM,QAAQ,MAAM,MAAM,KAAK;CAG/B,MAAM,kBAA4B,EAAE;AACpC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,MAAM;AAC3B,MAAI,WAAW,CAAC,QAAQ,WAAW,KAAK,CACtC,iBAAgB,KAAK,QAAQ,QAAQ,MAAM,GAAG,CAAC;;AAKnD,KAAI,gBAAgB,SAAS,WAAW,CACtC,QAAO;CAIT,MAAM,eAAe,MAClB,QAAQ,SAAS,KAAK,MAAM,CAAC,WAAW,KAAK,CAAC,CAC9C,KAAK,SAAS,KAAK,SAAS,CAAC;CAShC,MAAM,cAAc,GAAG,YAAY,GANjC,aAAa,SAAS,IAAI,OAAO,aAAa,KAAK,KAAK,GAAG,GAMV,IAHhC,CAAC,GAAG,iBAAiB,WAAW,CACrB,KAAK,MAAM,KAAK,EAAE,GAAG,CAAC,KAAK,KAAK,CAEI;AAElE,QACE,OAAO,MAAM,GAAG,WAAW,GAC3B,cACA,OAAO,MAAM,aAAa,UAAU,OAAO;;;;ACjI/C,MAAM,qBAAqB,KAAK,KAAK,OAAO,oBAAoB;AAEhE,MAAM,mBAAmB,IAAI,QAAQ,SAAS,CAC3C,YAAY,mCAAmC,CAC/C,SAAS,UAAU,iDAAiD,CACpE,OACC,6BACA,wCACA,aACD,CACA,OAAO,OAAO,MAAc,YAAiC;CAC5D,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,kBAAkB,KAAK,KAAK,KAAK,eAAe;CACtD,MAAM,aAAa,KAAK,KAAK,KAAK,mBAAmB;CACrD,MAAM,YAAY,KAAK,KAAK,KAAK,OAAO,WAAW,KAAK;CAExD,IAAI,iBAAiB;CACrB,IAAI,qBAAoC;CAExC,MAAM,kBAAkB,mBAAmB,KAAK;AAChD,KAAI,iBAAiB;AACnB,UAAQ,MAAM,MAAM,IAAI,UAAU,kBAAkB,CAAC;AACrD,UAAQ,KAAK,EAAE;;AAGjB,KAAI,CAAE,MAAM,GAAG,WAAW,gBAAgB,EAAG;AAC3C,UAAQ,MAAM,MAAM,IAAI,gCAAgC,CAAC;AACzD,UAAQ,MACN,MAAM,OAAO,qDAAqD,CACnE;AACD,UAAQ,KAAK,EAAE;;AAGjB,KAAI,MAAM,GAAG,WAAW,UAAU,EAAE;AAClC,UAAQ,MACN,MAAM,IACJ,uDAAuD,OACxD,CACF;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,aAAa,aAAa,KAAK;CACrC,MAAM,gBAAgB,gBAAgB,WAAW;CACjD,MAAM,YAAY,YAAY,KAAK;CACnC,MAAM,aAAa,UAAU,SAAS,SAAS,GAC3C,YACA,GAAG,UAAU;CACjB,MAAM,cAAc,cAAc,KAAK;CACvC,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,aAAa,YAAY,WAAW,qBAAqB,KAAK;AAEpE,KAAI;AACF,QAAM,GAAG,UAAU,UAAU;AAC7B,QAAM,GAAG,UACP,KAAK,KAAK,WAAW,YAAY,EACjC,mBAAmB;GACjB;GACA;GACA;GACA;GACA;GACA;GACD,CAAC,EACF,QACD;AAmBD,QAAM,mBAAmB,KAAK,KAAK;AACnC,mBAAiB;AAEjB,MAAI,MAAM,GAAG,WAAW,WAAW,EAAE;GACnC,MAAM,eAAe,MAAM,GAAG,SAAS,YAAY,QAAQ;AAC3D,OAAI,oBAAoB,aAAa,EAAE;AACrC,yBAAqB,MAAM,0BAA0B;AACrD,UAAM,GAAG,UACP,YACA,yBACE,YACA,YACA,sBAAsB,KAAA,EACvB,EACD,QACD;UACI;IACL,MAAM,aAAa,aAAa,cAAc,WAAW;IACzD,MAAM,UAAU,aACZ,wBAAwB,YAAY,WAAW,GAC/C;AACJ,QAAI,CAAC,QACH,OAAM,IAAI,MACR,GAAG,mBAAmB,uHACvB;AAEH,UAAM,GAAG,UAAU,YAAY,SAAS,QAAQ;;SAE7C;AACL,wBAAqB,MAAM,0BAA0B;AACrD,SAAM,GAAG,UACP,YACA,yBACE,YACA,YACA,sBAAsB,KAAA,EACvB,EACD,QACD;;UAEI,OAAO;AACd,QAAM,GAAG,OAAO,UAAU,CAAC,YAAY,GAAG;AAO1C,MAAI,eACF,OAAM,mBAAmB,KAAK,KAAK,CAAC,YAAY,GAAG;AAErD,UAAQ,MAAM,MAAM,IAAI,+CAA+C,CAAC;AACxE,UAAQ,MAAM,MAAM;AACpB,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,KAAK;AACb,SAAQ,IACN,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,KAAK,YACvD;AACD,SAAQ,IACN,MAAM,MAAM,aAAa,GAAG,OAAO,MAAM,KAAK,mBAAmB,GAClE;AACD,KAAI,mBACF,SAAQ,IACN,MAAM,MAAM,eAAe,GACzB,IAAI,MAAM,KAAK,mBAAmB,CAAC,2CACtC;AAEH,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,OAAO,cAAc,CAAC;AACxC,SAAQ,IAAI,aAAa,MAAM,KAAK,eAAe,KAAK,YAAY,GAAG;AACvE,SAAQ,IACN,uBAAuB,MAAM,KAAK,aAAa,CAAC,qDACjD;AACD,SAAQ,IACN,YAAY,MAAM,KAAK,gCAAgC,CAAC,wBACzD;EACD;AAEJ,SAAgB,mBAAmB,SAOxB;AACT,QAAO;;YAEG,QAAQ,WAAW;;;;;;;;;;;;;;WAcpB,QAAQ,cAAc,aAAa,KAAK,UAAU,QAAQ,YAAY,CAAC,MAAM,QAAQ,WAAW;;0BAEjF,QAAQ,KAAK;uBAChB,QAAQ,KAAK;sBACd,QAAQ,KAAK;0CACO,QAAQ,KAAK;;;;;;eAMxC,QAAQ,WAAW;UACxB,KAAK,UAAU,QAAQ,cAAc,CAAC;eACjC,QAAQ,cAAc;iBACpB,KAAK,UAAU,QAAQ,YAAY,CAAC;iBACpC,KAAK,UAAU,YAAY,QAAQ,YAAY,aAAa,CAAC,SAAS,CAAC;;cAE1E,KAAK,UAAU,QAAQ,SAAS,CAAC;;;;;aAKlC,KAAK,UAAU,QAAQ,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBjD,eAAsB,mBACpB,YACA,MACe;CACf,MAAM,aAAa,KAAK,KAAK,YAAY,aAAa;AACtD,KAAI,CAAE,MAAM,GAAG,WAAW,WAAW,CAAG;CACxC,MAAM,WAAW,MAAM,GAAG,SAAS,YAAY,QAAQ;CACvD,MAAM,QAAQ,yBAAyB,KAAK;AAC5C,KAAI,CAAC,SAAS,SAAS,MAAM,CAAE;CAC/B,MAAM,eAAe,SAAS,QAAQ,OAAO,GAAG;AAChD,OAAM,GAAG,UACP,YACA,aAAa,QAAQ,YAAY,OAAO,EACxC,QACD;;AAGH,eAAsB,mBACpB,YACA,MACe;CACf,MAAM,aAAa,KAAK,KAAK,YAAY,aAAa;CACtD,MAAM,QAAQ,yBAAyB,KAAK;AAE5C,KAAI,CAAE,MAAM,GAAG,WAAW,WAAW,EAAG;AACtC,QAAM,GAAG,UAAU,YAAY,GAAG,mBAAmB,IAAI,SAAS,QAAQ;AAC1E;;CAGF,MAAM,WAAW,MAAM,GAAG,SAAS,YAAY,QAAQ;AACvD,KAAI,SAAS,SAAS,IAAI,KAAK,IAAI,CAAE;CACrC,MAAM,YAAY,SAAS,SAAS,KAAK,GAAG,OAAO;AACnD,OAAM,GAAG,WAAW,YAAY,GAAG,YAAY,SAAS,QAAQ;;AAGlE,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;AAoB3B,SAAS,yBAAyB,MAAsB;AACtD,QAAO,IAAI,KAAK;;;;;;;;;GASf,KAAK;;;;;;;;GAQL,KAAK;;;;;;AAOR,SAAS,oBAAoB,QAAyB;AACpD,QAAO,+DAA+D,KACpE,OACD;;;;;;;;;AAUH,MAAM,0BAA0B;AAEhC,SAAgB,yBACd,YACA,YACA,aAAqB,yBACb;AACR,QAAO;EACP,WAAW;;;;qBAIQ,KAAK,UAAU,WAAW,CAAC;;cAElC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;AAyBzB,eAAe,2BAAmD;AAChE,KAAI;EACF,MAAM,SAAS,wCAAwC;AAOvD,UANiB,MAAM,QAAQ,KAAK,CAClC,0BAA0B,OAAO,EACjC,IAAI,SAAiC,YAAY;AAC/C,oBAAiB,QAAQ,EAAE,CAAC,EAAE,8BAA8B;IAC5D,CACH,CAAC,EACc,IAAI,OAAO;SACrB;AAGN,SAAO;;;AAIX,MAAM,gCAAgC;AAEtC,MAAa,gBAAyB,IAAI,QAAQ,SAAS,CACxD,YAAY,mCAAmC,CAC/C,WAAW,iBAAiB;;;ACtY/B,MAAMC,oBAAkB;AACxB,MAAM,sBAAsB;AAE5B,MAAa,gBAAyB,IAAI,QAAQ,SAAS,CACxD,YAAY,wDAAwD,CACpE,OACC,4BACA,iDACA,oBACD,CACA,OACC,uBACA,oCACAA,kBACD,CACA,OAAO,aAAa,uDAAuD,CAC3E,OAAO,OAAO,YAAiC;CAC9C,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,SAAS,QAAQ,UAAUA;CACjC,MAAM,SAAS,QAAQ,WAAW;AAElC,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,KAAK,sBAAsB,CAAC;AACnD,SAAQ,KAAK;AACb,yBAAwB;AACxB,SAAQ,IAAI,MAAM,KAAK,gBAAgB,GAAG,MAAM,MAAM,YAAY,CAAC;AACnE,SAAQ,IAAI,MAAM,KAAK,gBAAgB,GAAG,MAAM,KAAK,OAAO,CAAC;AAC7D,KAAI,OACF,SAAQ,IAAI,MAAM,OAAO,sCAAsC,CAAC;AAClE,SAAQ,KAAK;CAEb,MAAM,UAAU,IAAI,2CAA2C,CAAC,OAAO;CACvE,MAAM,SAAS,MAAM,gBAAgB;EACnC,YAAY,QAAQ,KAAK;EACzB;EACA;EACA;EACA,aAAa,UAAU;AACrB,WAAQ,OAAO;;EAElB,CAAC;AAEF,KAAI,CAAC,OAAO,IAAI;AACd,UAAQ,KAAK,uBAAuB;AACpC,UAAQ,KAAK;AACb,MAAI,OAAO,SAAS,uBAClB,SAAQ,IAAI,MAAM,IAAI,2CAA2C,CAAC;MAElE,SAAQ,MAAM,MAAM,IAAI,SAAS,GAAG,MAAM,OAAO,QAAQ;AAE3D,MAAI,OAAO,MAAM;AACf,WAAQ,KAAK;AACb,WAAQ,IAAI,OAAO,KAAK;;AAE1B,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,QAAQ,iCAAiC;AAEjD,KAAI,QAAQ;AACV,UAAQ,IACN,MAAM,OAAO,uDAAuD,CACrE;AACD,UAAQ,KAAK;AACb,MAAI,OAAO,mBAAmB,KAAA,EAC5B,2BAA0B,OAAO,eAAe;OAGlD,SAAQ,IAAI,MAAM,MAAM,oCAAoC,CAAC;AAG/D,KAAI,OAAO,kBAAkB;AAC3B,UAAQ,IACN,MAAM,OACJ,WAAW,OAAO,iBAAiB,8DAA8D,OAAO,QAAQ,GACjH,CACF;AACD,UAAQ,IACN,MAAM,KACJ,+FACD,CACF;AACD,UAAQ,KAAK;;AAGf,2BAA0B;EACxB,OAAO,SAAS,0BAA0B;EAC1C,YAAY;EACZ;EACA;EACA,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,eAAe,OAAO;EACvB,CAAC;EACF;;;;;;;;AAoCJ,eAAsB,gBACpB,SACgC;CAChC,IAAI;AACJ,KAAI,CAAC,QAAQ,OACX,KAAI;AACF,WAAS,wCAAwC;UAC1C,OAAO;AACd,SAAO;GACL,IAAI;GACJ,MAAM;GACN,SAAS;GACT,GAAI,iBAAiB,QAAQ,EAAE,MAAM,MAAM,SAAS,GAAG,EAAE;GAC1D;;CAWL,IAAI,YAAqC,EAAE;AAC3C,KAAI,QAAQ;AACV,UAAQ,aAAa,4CAA4C;AACjE,cAAY,MAAM,wBAAwB;GACxC,YAAY,QAAQ;GACpB;GACA,uBAAuB,OAAO,QAAQ;IACpC,MAAM,EAAE,cAAc,MAAM,6BAA6B;KACvD,YAAY;KACZ,OAAO;KACR,CAAC;AACF,WAAO;KAAE,WAAW,UAAU;KAAW,SAAS,UAAU;KAAS;;GAExE,CAAC;AACF,MAAI,UAAU,SACZ,QAAO;GACL,IAAI;GACJ,MAAM;GACN,SAAS;GACT,MAAM,UAAU;GACjB;;AAIL,SAAQ,aAAa,2CAA2C;AAChE,KAAI;EACF,MAAM,SAAS,MAAM,8BAA8B;GACjD,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,YAAY;GACZ,aAAa,EAAE,MAAM,WAAW;GAChC,QAAQ,QAAQ;GAChB,GAAI,UAAU,kBACV,EAAE,SAAS,UAAU,iBAAiB,GACtC,EAAE;GACN,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;GAC7B,CAAC;AAEF,SAAO;GACL,IAAI;GACJ,WAAW,OAAO,MAAM;GACxB,SAAS,OAAO,MAAM;GACtB,eAAe,OAAO,QAAQ,UAAU;GAIxC,GAAI,UAAU,mBAAmB,UAAU,oBACvC,EAAE,kBAAkB,UAAU,mBAAmB,GACjD,EAAE;GACN,gBAAgB,OAAO,QAAQ;GAChC;UACM,OAAO;AACd,SAAO;GACL,IAAI;GACJ,MACE,iBAAiB,SAAS,UAAU,QAChC,OAAQ,MAA4B,KAAK,GACzC;GACN,SACE,iBAAiB,QACb,MAAM,UACN;GACN,GAAI,iBAAiB,SACrB,aAAa,SACb,OAAO,MAAM,YAAY,YACzB,MAAM,QAAQ,SAAS,IACnB,EAAE,MAAM,MAAM,SAAS,GACvB,EAAE;GACP;;;;;;AC5NL,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACD;;AAGD,MAAM,eAAe,CACnB;CACE,MAAM;CACN,iBAAiB;CACjB,MAAM;CACP,EACD;CACE,MAAM;CACN,iBAAiB;CACjB,MAAM;CACP,CACF;;AAGD,MAAM,gBAAgB,CACpB;CACE,MAAM;CACN,MAAM;CACP,EACD;CACE,MAAM;CACN,MAAM;CACP,CACF;AAED,SAAS,eAAe,UAAiC;AACvD,KAAI;AACF,SAAO,aAAa,UAAU,QAAQ;SAChC;AACN,SAAO;;;AAKX,MAAM,cAAc,QADC,cAAc,OAAO,KAAK,IAAI,CACV;AAEzC,SAAS,kBAAiC;CAExC,IAAI,MAAM;AACV,QAAO,CAAC,WAAW,KAAK,KAAK,eAAe,CAAC,EAAE;EAC7C,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;CAGR,MAAM,cAAc,KAAK,KAAK,YAAY;AAC1C,KAAI,WAAW,KAAK,aAAa,OAAO,CAAC,CAAE,QAAO;AAClD,QAAO;;AAGT,SAAgB,yBACd,KACA,aACc;CACd,MAAM,cAA4B,EAAE;AAEpC,MAAK,MAAM,QAAQ,sBAAsB;EACvC,MAAM,aAAa,KAAK,KAAK,KAAK;EAClC,MAAM,eAAe,KAAK,aAAa,QAAQ,KAAK;AAEpD,MAAI,CAAC,WAAW,WAAW,EAAE;AAC3B,eAAY,KAAK;IACf;IACA,UAAU;IACV,SAAS,iBAAiB;IAC3B,CAAC;AACF;;AAGF,MAAI,CAAC,WAAW,aAAa,CAE3B;EAGF,MAAM,gBAAgB,eAAe,WAAW;EAChD,MAAM,kBAAkB,eAAe,aAAa;AAEpD,MAAI,kBAAkB,QAAQ,oBAAoB;OAC5C,cAAc,MAAM,KAAK,gBAAgB,MAAM,CACjD,aAAY,KAAK;IACf;IACA,UAAU;IACV,SAAS;IACV,CAAC;;;AAKR,QAAO;;AAGT,SAAgB,mBAAmB,KAA2B;CAC5D,MAAM,cAA4B,EAAE;AAEpC,MAAK,MAAM,SAAS,cAAc;EAEhC,MAAM,UAAU,eADC,KAAK,KAAK,MAAM,KAAK,CACE;AAExC,MAAI,YAAY,MAAM;AACpB,eAAY,KAAK;IACf,MAAM,MAAM;IACZ,UAAU;IACV,SAAS,iBAAiB,MAAM;IACjC,CAAC;AACF;;AAGF,MAAI,CAAC,QAAQ,SAAS,MAAM,gBAAgB,CAC1C,aAAY,KAAK;GACf,MAAM,MAAM;GACZ,UAAU;GACV,SAAS,sCAAsC,MAAM,gBAAgB,KAAK,MAAM;GACjF,CAAC;;AAIN,QAAO;;AAGT,SAAgB,kBAAkB,KAA2B;CAC3D,MAAM,cAA4B,EAAE;AAEpC,MAAK,MAAM,SAAS,cAClB,KAAI,WAAW,KAAK,KAAK,MAAM,KAAK,CAAC,CACnC,aAAY,KAAK;EACf,MAAM,MAAM;EACZ,UAAU;EACV,SAAS,wBAAwB,MAAM;EACxC,CAAC;AAIN,QAAO;;AAGT,SAAS,iBAAiB,GAAuB;AAQ/C,QAAO,KANL,EAAE,aAAa,UACX,MAAM,IAAI,QAAQ,GAClB,EAAE,aAAa,SACb,MAAM,OAAO,QAAQ,GACrB,MAAM,KAAK,QAAQ,CAEV,IAAI,MAAM,KAAK,EAAE,KAAK,CAAC,aAAa,MAAM,IAAI,EAAE,QAAQ;;AAG3E,MAAa,gBAAyB,IAAI,QAAQ,SAAS,CACxD,YACC,wEACD,CACA,OAAO,YAAY;CAClB,MAAM,MAAM,QAAQ,KAAK;CAGzB,MAAM,kBAAkB,KAAK,KAAK,eAAe;AACjD,KAAI,CAAC,WAAW,gBAAgB,EAAE;AAChC,UAAQ,MACN,MAAM,IAAI,oDAAoD,CAC/D;AACD,UAAQ,MACN,MAAM,OAAO,uDAAuD,CACrE;AACD,UAAQ,KAAK,EAAE;;CAGjB,IAAI;AAIJ,KAAI;AACF,gBAAc,KAAK,MAAM,aAAa,iBAAiB,QAAQ,CAAC;SAC1D;AACN,UAAQ,MAAM,MAAM,IAAI,sCAAsC,CAAC;AAC/D,UAAQ,MAAM,MAAM,OAAO,0CAA0C,CAAC;AACtE,UAAQ,KAAK,EAAE;;AAOjB,KAAI,CALS;EACX,GAAG,YAAY;EACf,GAAG,YAAY;EAChB,CAES,0BAA0B;AAClC,UAAQ,MACN,MAAM,IAAI,yDAAyD,CACpE;AACD,UAAQ,MACN,MAAM,OACJ,iEACD,CACF;AACD,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,sBAAsB,CAAC;AAC9C,SAAQ,IAAI,MAAM,IAAI,mCAAmC,CAAC;CAE1D,MAAM,cAA4B,EAAE;AAGpC,aAAY,KAAK,GAAG,mBAAmB,IAAI,CAAC;AAG5C,aAAY,KAAK,GAAG,kBAAkB,IAAI,CAAC;CAG3C,MAAM,cAAc,iBAAiB;AACrC,KAAI,YACF,aAAY,KAAK,GAAG,yBAAyB,KAAK,YAAY,CAAC;KAE/D,SAAQ,IACN,MAAM,IACJ,oEACD,CACF;AAIH,KAAI,YAAY,WAAW,GAAG;AAC5B,UAAQ,IAAI,MAAM,MAAM,qCAAqC,CAAC;AAC9D;;CAGF,MAAM,SAAS,YAAY,QAAQ,MAAM,EAAE,aAAa,QAAQ;CAChE,MAAM,QAAQ,YAAY,QAAQ,MAAM,EAAE,aAAa,OAAO;CAC9D,MAAM,QAAQ,YAAY,QAAQ,MAAM,EAAE,aAAa,OAAO;AAE9D,MAAK,MAAM,KAAK;EAAC,GAAG;EAAQ,GAAG;EAAO,GAAG;EAAM,EAAE;AAC/C,UAAQ,IAAI,iBAAiB,EAAE,CAAC;AAChC,UAAQ,KAAK;;CAIf,MAAM,QAAkB,EAAE;AAC1B,KAAI,OAAO,SAAS,EAAG,OAAM,KAAK,MAAM,IAAI,GAAG,OAAO,OAAO,WAAW,CAAC;AACzE,KAAI,MAAM,SAAS,EACjB,OAAM,KAAK,MAAM,OAAO,GAAG,MAAM,OAAO,aAAa,CAAC;AACxD,KAAI,MAAM,SAAS,EAAG,OAAM,KAAK,MAAM,KAAK,GAAG,MAAM,OAAO,OAAO,CAAC;AACpE,SAAQ,IAAI,KAAK,MAAM,KAAK,KAAK,CAAC,IAAI;AAEtC,KAAI,OAAO,SAAS,EAClB,SAAQ,KAAK,EAAE;EAEjB;;;ACpQJ,MAAM,kBAAkB;AAExB,SAAS,aAAqB;AAC5B,QAAO,QAAQ,IAAI,qBAAqB;;AAG1C,SAAS,eAAuB;CAC9B,MAAM,QAAQ,cAAc;AAC5B,KAAI,CAAC,OAAO;AACV,UAAQ,MACN,MAAM,IAAI,SAAS,GACjB,yBACA,MAAM,KAAK,gBAAgB,GAC3B,UACH;AACD,UAAQ,KAAK,EAAE;;AAEjB,QAAO;;AAGT,SAAS,aAAa,OAAe;AACnC,QAAO,oBAAoB;EACzB,SAAS,YAAY;EACrB;EACD,CAAC;;AAGJ,eAAe,sBAAuC;CACpD,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,WAAW,MAAM,aAAa,KAAK,KAAK,KAAK,gBAAgB,CAAC;AACpE,KAAI,CAAC,UAAU;AACb,UAAQ,MACN,MAAM,IAAI,SAAS,GACjB,gCACA,MAAM,KAAK,sBAAsB,GACjC,UACH;AACD,UAAQ,KAAK,EAAE;;AAEjB,QAAO,SAAS,WAAW;;AAO7B,MAAM,uBAAuB,IAAI,QAAQ,SAAS,CAC/C,YAAY,2DAA2D,CACvE,OAAO,cAAc,kDAAkD,CACvE,OAAO,OAAO,YAAoC;CAEjD,MAAM,SAAS,aADD,cAAc,CACM;CAClC,MAAM,eAAe,MAAM,qBAAqB;AAEhD,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,sBAAsB,CAAC;CAE9C,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,WAAW,cAAc,QAAQ,aAAa;UACtD,KAAK;AACZ,UAAQ,MACN,MAAM,IAAI,SAAS,GACjB,kCACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,UAAU,OAAO;AAEvB,KAAI,CAAC,SAAS,IAAI;AAChB,UAAQ,MACN,MAAM,IAAI,SAAS,GACjB,mDACH;AACD,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,MAAM,gCAAgC,CAAC;AACzD,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,eAAe,GAAG,MAAM,MAAM,QAAQ,GAAG,CAAC;CAEjE,IAAI,SAAS,QAAQ,UAAU;AAE/B,KAAI,QAAQ,UAAU;AACpB,UAAQ,IAAI,wBAAwB;AACpC,MAAI;AACF,SAAM,WAAW,cAAc,QAAQ,cAAc,QAAQ,IAAI,EAC/D,SAAS,EAAE,QAAQ,MAAM,EAC1B,CAAC;WACK,KAAK;AACZ,WAAQ,MACN,MAAM,IAAI,SAAS,GACjB,oCACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,WAAQ,KAAK,EAAE;;AAEjB,WAAS;;AAGX,SAAQ,IACN,MAAM,KAAK,eAAe,IACvB,SAAS,MAAM,MAAM,MAAM,GAAG,MAAM,KAAK,KAAK,EAClD;AACD,SAAQ,KAAK;EACb;AAEJ,MAAM,qBAAqB,IAAI,QAAQ,OAAO,CAC3C,YAAY,6CAA6C,CACzD,OAAO,YAAY;CAElB,MAAM,SAAS,aADD,cAAc,CACM;CAClC,MAAM,eAAe,MAAM,qBAAqB;CAEhD,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,WAAW,aAAa,QAAQ,aAAa;UACrD,KAAK;AACZ,UAAQ,MACN,MAAM,IAAI,SAAS,GACjB,iCACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,WAAW,OAAO;AAExB,KAAI,CAAC,MAAM,QAAQ,SAAS,IAAI,SAAS,WAAW,GAAG;AACrD,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,OAAO,qBAAqB,CAAC;AAC/C,UAAQ,IACN,SACE,MAAM,KAAK,gCAAgC,GAC3C,yBACH;AACD,UAAQ,KAAK;AACb;;AAGF,SAAQ,KAAK;CAGb,MAAM,SAAS,aAAa,OAAO,GAAG;CACtC,MAAM,UAAU,SAAS,OAAO,EAAE;AAElC,SAAQ,IAAI,MAAM,KAAK,KAAK,OAAO,IAAI,QAAQ,aAAc,CAAC;AAE9D,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,KAAK,OAAO,EAAE,GAAG,CAAC,OAAO,GAAG;EAClC,MAAM,SAAS,EAAE,SAAS,MAAM,MAAM,IAAS,GAAG,UAAU;EAC5D,MAAM,YAAY,EAAE,eAChB,IAAI,KAAK,EAAE,aAAa,CAAC,gBAAgB,GACzC;AACJ,UAAQ,IAAI,KAAK,GAAG,IAAI,OAAO,IAAI,YAAY;;AAGjD,SAAQ,KAAK;EACb;AAEJ,MAAM,yBAAyB,IAAI,QAAQ,WAAW,CACnD,YAAY,0DAA0D,CACtE,SAAS,gBAAgB,6BAA6B,CACtD,OAAO,aAAa,2BAA2B,CAC/C,OAAO,OAAO,WAAmB,YAA+B;CAC/D,MAAM,mBAAmB,OAAO,UAAU;AAC1C,KAAI,CAAC,OAAO,cAAc,iBAAiB,IAAI,mBAAmB,GAAG;AACnE,UAAQ,MACN,MAAM,IAAI,SAAS,GAAG,0CACvB;AACD,UAAQ,KAAK,EAAE;;CAIjB,MAAM,SAAS,aADD,cAAc,CACM;CAClC,MAAM,eAAe,MAAM,qBAAqB;AAEhD,KAAI,CAAC,QAAQ,KAAK;EAChB,MAAM,EAAE,YAAY,MAAM,QAAQ;GAChC,MAAM;GACN,MAAM;GACN,SAAS,oBAAoB,UAAU;GACvC,SAAS;GACV,CAAC;AAEF,MAAI,CAAC,SAAS;AACZ,WAAQ,IAAI,MAAM,OAAO,WAAW,CAAC;AACrC;;;AAIJ,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,wBAAwB,CAAC;AAEhD,KAAI;AACF,QAAM,WAAW,cAAc,QAAQ,cAAc,kBAAkB,EACrE,SAAS,EAAE,QAAQ,MAAM,EAC1B,CAAC;UACK,KAAK;AACZ,UAAQ,MACN,MAAM,IAAI,SAAS,GACjB,oCACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,MAAM,aAAa,YAAY,kBAAkB,CAAC;AACpE,SAAQ,KAAK;EACb;AAMJ,MAAa,iBAA0B,IAAI,QAAQ,UAAU,CAC1D,YAAY,oCAAoC,CAChD,WAAW,qBAAqB,CAChC,WAAW,mBAAmB,CAC9B,WAAW,uBAAuB;;;AC9NrC,MAAM,kBAAkB;AAExB,MAAM,oBAAoB,IAAI,QAAQ,UAAU,CAC7C,YAAY,iDAAiD,CAC7D,eACC,oBACA,4CACD,CACA,OACC,uBACA,oCACA,gBACD,CACA,OAAO,aAAa,uDAAuD,CAC3E,OAAO,OAAO,YAAkC;CAC/C,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,SAAS,QAAQ,WAAW;AAElC,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,KAAK,uBAAuB,CAAC;AACpD,SAAQ,KAAK;AACb,yBAAwB;AACxB,SAAQ,IAAI,MAAM,KAAK,YAAY,GAAG,MAAM,MAAM,QAAQ,QAAQ,CAAC;AACnE,SAAQ,IAAI,MAAM,KAAK,YAAY,GAAG,MAAM,KAAK,OAAO,CAAC;AACzD,KAAI,OACF,SAAQ,IAAI,MAAM,OAAO,sCAAsC,CAAC;AAClE,SAAQ,KAAK;CAEb,MAAM,UAAU,IAAI,2CAA2C,CAAC,OAAO;AAEvE,KAAI;EACF,MAAM,SAAS,MAAM,8BAA8B;GACjD,YAAY,QAAQ,KAAK;GACzB;GACA,YAAY;GACZ,aAAa;IAAE,MAAM;IAAW,MAAM,QAAQ;IAAS;GACvD;GACA,GAAI,SAAS,EAAE,GAAG,EAAE,QAAQ,wCAAwC,EAAE;GACvE,CAAC;AAEF,UAAQ,QAAQ,iCAAiC;AAEjD,MAAI,QAAQ;AACV,WAAQ,IACN,MAAM,OAAO,uDAAuD,CACrE;AACD,WAAQ,KAAK;AACb,6BAA0B,OAAO,QAAQ,eAAe;QAExD,SAAQ,IAAI,MAAM,MAAM,oCAAoC,CAAC;AAG/D,4BAA0B;GACxB,OAAO,SAAS,2BAA2B;GAC3C,YAAY,WAAW,QAAQ;GAC/B;GACA,WAAW,OAAO,MAAM;GACxB,SAAS,OAAO,MAAM;GACtB,eAAe,OAAO,QAAQ,UAAU;GACzC,CAAC;UACK,KAAK;AACZ,UAAQ,KAAK,wBAAwB;AACrC,0BAAwB,IAAI;AAC5B,UAAQ,KAAK,EAAE;;EAEjB;AAEJ,MAAa,wBAAiC,IAAI,QAAQ,SAAS,CAChE,YAAY,gCAAgC,CAC5C,WAAW,kBAAkB;;;;;;;;;AC/DhC,MAAM,SAAsB;CAC1B,MAAM;CACN,SAAS;CACT,MAAM,SAAS,KAAoB;EACjC,MAAM,SAAS,IAAI,QAAQ,SAAS,CAAC,YACnC,wCACD;AAED,SAAO,WAAW,cAAc;AAChC,SAAO,WAAW,WAAW;AAC7B,SAAO,WAAW,aAAa;AAC/B,SAAO,WAAW,YAAY;AAC9B,SAAO,WAAW,YAAY;AAC9B,SAAO,WAAW,aAAa;AAC/B,SAAO,WAAW,cAAc;AAChC,SAAO,WAAW,cAAc;AAChC,SAAO,WAAW,cAAc;AAChC,SAAO,WAAW,eAAe;AAEjC,MAAI,QAAQ,WAAW,OAAO;;CAEjC"}