@fluid-app/fluid-cli-portal 0.1.22 → 0.1.23
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.
- package/dist/index.mjs +92 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -7,10 +7,11 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import { failure, getActiveProfile, getAuthToken, listProfileNames, success } from "@fluid-app/fluid-cli";
|
|
9
9
|
import prompts from "prompts";
|
|
10
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
11
11
|
import Handlebars from "handlebars";
|
|
12
12
|
import { execa } from "execa";
|
|
13
13
|
import fs from "fs-extra";
|
|
14
|
+
import path$1 from "path";
|
|
14
15
|
//#region src/types.ts
|
|
15
16
|
/**
|
|
16
17
|
* Available project templates
|
|
@@ -564,6 +565,84 @@ const devCommand = new Command("dev").description("Start the development server
|
|
|
564
565
|
}
|
|
565
566
|
});
|
|
566
567
|
//#endregion
|
|
568
|
+
//#region src/utils/extract-manifests.ts
|
|
569
|
+
/**
|
|
570
|
+
* Manifest extraction utility
|
|
571
|
+
*
|
|
572
|
+
* Extracts serializable widget manifest metadata from portal.config.ts
|
|
573
|
+
* by writing a minimal wrapper script that imports customWidgets and
|
|
574
|
+
* serializes the result to stdout, then running it with tsx.
|
|
575
|
+
*
|
|
576
|
+
* Strips the `component` field (not serializable) from each manifest.
|
|
577
|
+
*
|
|
578
|
+
* Writes a temp script, runs it with tsx, and parses JSON output. This
|
|
579
|
+
* avoids needing Vite's module resolution — portal.config.ts must use
|
|
580
|
+
* relative imports only.
|
|
581
|
+
*
|
|
582
|
+
* Used by `fluid build`. The dev server uses Vite's ssrLoadModule
|
|
583
|
+
* instead (see manifest-plugin.ts in portal-sdk).
|
|
584
|
+
*/
|
|
585
|
+
const EXTRACT_FILENAME = "__fluid_extract_manifests.ts";
|
|
586
|
+
/**
|
|
587
|
+
* Extract serializable widget manifests from a project's portal.config.ts.
|
|
588
|
+
*
|
|
589
|
+
* Writes a temp wrapper script, runs it with tsx, and parses JSON output.
|
|
590
|
+
* The temp file is always cleaned up.
|
|
591
|
+
*
|
|
592
|
+
* Returns an empty array if no customWidgets export exists.
|
|
593
|
+
*
|
|
594
|
+
* @param projectDir - The project root directory containing src/portal.config.ts
|
|
595
|
+
*/
|
|
596
|
+
async function extractManifests(projectDir) {
|
|
597
|
+
const configPath = path$1.join(projectDir, "src", "portal.config.ts");
|
|
598
|
+
const extractFile = path$1.join(projectDir, EXTRACT_FILENAME);
|
|
599
|
+
try {
|
|
600
|
+
if (!await fs.pathExists(configPath)) return success([]);
|
|
601
|
+
const strippedSource = (await fs.readFile(configPath, "utf-8")).replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
602
|
+
if (!/export\s+(const|let)\s+customWidgets[\s:=]/.test(strippedSource)) return success([]);
|
|
603
|
+
await fs.writeFile(extractFile, `
|
|
604
|
+
import { customWidgets } from "./src/portal.config.ts";
|
|
605
|
+
|
|
606
|
+
const serializable = customWidgets.map(({ component, ...rest }) => rest);
|
|
607
|
+
console.log(JSON.stringify(serializable));
|
|
608
|
+
`, "utf-8");
|
|
609
|
+
const output = (await execa("npx", ["tsx", EXTRACT_FILENAME], {
|
|
610
|
+
cwd: projectDir,
|
|
611
|
+
stdio: "pipe",
|
|
612
|
+
env: {
|
|
613
|
+
...process.env,
|
|
614
|
+
NODE_ENV: "production"
|
|
615
|
+
}
|
|
616
|
+
})).stdout.trim();
|
|
617
|
+
if (!output || output === "null" || output === "[]") return success([]);
|
|
618
|
+
let parsed;
|
|
619
|
+
try {
|
|
620
|
+
parsed = JSON.parse(output);
|
|
621
|
+
} catch {
|
|
622
|
+
return failure({
|
|
623
|
+
code: "INVALID_FORMAT",
|
|
624
|
+
message: "Failed to parse manifest output as JSON",
|
|
625
|
+
details: `Output was: ${output.slice(0, 200)}`
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (!Array.isArray(parsed)) return failure({
|
|
629
|
+
code: "INVALID_FORMAT",
|
|
630
|
+
message: "customWidgets export is not an array",
|
|
631
|
+
details: `Expected an array, got: ${typeof parsed}`
|
|
632
|
+
});
|
|
633
|
+
return success(parsed.filter((m) => typeof m === "object" && m !== null && typeof m.type === "string" && typeof m.displayName === "string"));
|
|
634
|
+
} catch (err) {
|
|
635
|
+
const error = err;
|
|
636
|
+
return failure({
|
|
637
|
+
code: "EXTRACTION_FAILED",
|
|
638
|
+
message: "Failed to extract widget manifests from portal.config.ts",
|
|
639
|
+
details: error.stderr ?? error.message ?? String(err)
|
|
640
|
+
});
|
|
641
|
+
} finally {
|
|
642
|
+
await fs.remove(extractFile).catch(() => {});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
//#endregion
|
|
567
646
|
//#region src/commands/build.ts
|
|
568
647
|
const buildCommand = new Command("build").description("Build the application for production").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
|
|
569
648
|
const cwd = process.cwd();
|
|
@@ -587,8 +666,19 @@ const buildCommand = new Command("build").description("Build the application for
|
|
|
587
666
|
stdio: "pipe"
|
|
588
667
|
});
|
|
589
668
|
spinner.succeed("Build completed");
|
|
669
|
+
const manifestSpinner = ora("Extracting widget manifests...").start();
|
|
670
|
+
const outDir = options.outDir ?? "dist";
|
|
671
|
+
const manifestResult = await extractManifests(cwd);
|
|
672
|
+
const manifestPath = [join(cwd, outDir, "__manifests__.json"), join(cwd, outDir, "public", "__manifests__.json")].find((p) => existsSync(p));
|
|
673
|
+
if (manifestResult.success) if (manifestResult.value.length === 0) manifestSpinner.info("No custom widgets found");
|
|
674
|
+
else if (!manifestPath) manifestSpinner.warn(`__manifests__.json not found in build output — skipping manifest write`);
|
|
675
|
+
else {
|
|
676
|
+
writeFileSync(manifestPath, JSON.stringify(manifestResult.value));
|
|
677
|
+
manifestSpinner.succeed(`Extracted ${manifestResult.value.length} widget manifest(s)`);
|
|
678
|
+
}
|
|
679
|
+
else manifestSpinner.warn(`Manifest extraction failed: ${manifestResult.error.message}`);
|
|
590
680
|
console.log();
|
|
591
|
-
console.log(`Output written to ${chalk.cyan(
|
|
681
|
+
console.log(`Output written to ${chalk.cyan(outDir)}/`);
|
|
592
682
|
console.log();
|
|
593
683
|
console.log("To preview the build locally:");
|
|
594
684
|
console.log(chalk.cyan(" pnpm vite preview"));
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["_currentDir","PORTAL_DIR","readPortalFile","PORTAL_SYNC_DIR","createClient","fluidOs.createFluidOSScreen","fluidOs.updateFluidOSScreen","fluidOs.deleteFluidOSScreen","fluidOs.createFluidOSTheme","fluidOs.updateFluidOSTheme","fluidOs.deleteFluidOSTheme","fluidOs.createFluidOSNavigation","fluidOs.createFluidOSNavigationItem","fluidOs.updateFluidOSNavigation","fluidOs.listFluidOSNavigationItems","fluidOs.deleteFluidOSNavigationItem","fluidOs.updateFluidOSNavigationItem","fluidOs.deleteFluidOSNavigation","fluidOs.createFluidOSProfile","fluidOs.updateFluidOSProfile","fluidOs.deleteFluidOSProfile","fluidOs.createFluidOSVersion","fluidOs.updateFluidOSVersion","fluidOs.listFluidOSVersions"],"sources":["../src/types.ts","../src/utils/prompts.ts","../src/utils/file-system.ts","../src/utils/package-manager.ts","../src/commands/create.ts","../src/commands/dev.ts","../src/commands/build.ts","../src/utils/push-validation.ts","../src/commands/push.ts","../src/utils/widget-helpers.ts","../src/commands/widget-create.ts","../src/commands/doctor.ts","../src/commands/version.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 /** 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 file: links instead of npm versions */\n readonly local?: 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// 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 /** CLI package version (versioned independently from the SDK) */\n readonly cliVersion: string;\n /** portal-core dependency version */\n readonly coreVersion: 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 } 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\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 */\nfunction getOutputFilename(filename: string): string {\n if (filename.endsWith(\".template\")) {\n return filename.slice(0, -\".template\".length);\n }\n return filename;\n}\n\n/**\n * Copies a template directory to the target directory\n * Processes .template files with Handlebars\n */\nexport async function copyTemplate(\n templatePath: string,\n targetPath: string,\n variables: TemplateVariables,\n): Promise<void> {\n const files = await getFiles(templatePath);\n\n for (const file of files) {\n const sourcePath = join(templatePath, file);\n const isTemplate = file.endsWith(\".template\");\n const outputFile = getOutputFilename(file);\n const destPath = join(targetPath, outputFile);\n\n // Create directory if needed\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n\n // Read source file\n const content = await readFile(sourcePath, \"utf-8\");\n\n // Process and write\n const processed = processTemplate(\n content,\n variables,\n isTemplate,\n sourcePath,\n );\n await writeFile(destPath, processed, \"utf-8\");\n }\n}\n\n/**\n * Checks if a directory exists\n */\nexport async function directoryExists(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Checks if a file exists\n */\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Checks if a path exists (file or directory)\n */\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Creates a directory\n */\nexport async function createDirectory(path: string): Promise<void> {\n await mkdir(path, { recursive: true });\n}\n\n/**\n * Reads the SDK version from the workspace package.json\n * Falls back to ^0.1.0 if not found\n */\nexport async function getSdkVersion(): Promise<string> {\n try {\n // Try to read from workspace\n // CLI lives at packages/cli/portal/, SDK at packages/portal/sdk/\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPackagePath = join(packagesRoot, \"portal\", \"sdk\", \"package.json\");\n\n const content = await readFile(sdkPackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n return `^${pkg.version ?? \"0.1.0\"}`;\n } catch {\n // Fallback for when running outside the workspace\n return \"^0.1.0\";\n }\n}\n\n/**\n * Reads the portal-core version from the workspace package.json.\n * Falls back to ^0.1.0 if not found.\n */\nexport async function getCoreVersion(): Promise<string> {\n try {\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const corePackagePath = join(\n packagesRoot,\n \"portal\",\n \"core\",\n \"package.json\",\n );\n\n const content = await readFile(corePackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n return `^${pkg.version ?? \"0.1.0\"}`;\n } catch {\n return \"^0.1.0\";\n }\n}\n\n/**\n * Reads the CLI core version from the workspace package.json.\n * Falls back to ^0.1.0 if not found.\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 try {\n // CLI portal lives at packages/cli/portal/, CLI core at packages/cli/core/\n const packageRoot = findPackageRoot();\n const cliCorePath = join(packageRoot, \"..\", \"core\", \"package.json\");\n\n const content = await readFile(cliCorePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n return `^${pkg.version ?? \"0.1.0\"}`;\n } catch {\n return \"^0.1.0\";\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result-based variants for type-safe error handling\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Read a file's content with Result-based error handling\n */\nexport async function readFileSafe(\n path: string,\n): Promise<Result<string, FileSystemError>> {\n try {\n const content = await readFile(path, \"utf-8\");\n return success(content);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.readError,\n `Failed to read file: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Write content to a file with Result-based error handling\n */\nexport async function writeFileSafe(\n path: string,\n content: string,\n): Promise<Result<void, FileSystemError>> {\n try {\n await writeFile(path, content, \"utf-8\");\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.writeError,\n `Failed to write file: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Create a directory with Result-based error handling\n */\nexport async function createDirectorySafe(\n path: string,\n): Promise<Result<void, FileSystemError>> {\n try {\n await mkdir(path, { recursive: true });\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.writeError,\n `Failed to create directory: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Copy a template directory with Result-based error handling\n */\nexport async function copyTemplateSafe(\n templatePath: string,\n targetPath: string,\n variables: Readonly<TemplateVariables>,\n): Promise<Result<void, FileSystemError>> {\n try {\n const files = await getFiles(templatePath);\n\n for (const file of files) {\n const sourcePath = join(templatePath, file);\n const isTemplateFile = file.endsWith(\".template\");\n const outputFile = getOutputFilename(file);\n const destPath = join(targetPath, outputFile);\n\n // Create directory if needed\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n\n // Read source file\n const content = await readFile(sourcePath, \"utf-8\");\n\n // Process and write\n const processed = processTemplate(\n content,\n variables,\n isTemplateFile,\n sourcePath,\n );\n await writeFile(destPath, processed, \"utf-8\");\n }\n\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.templateError,\n `Failed to copy template from ${templatePath} to ${targetPath}`,\n templatePath,\n error,\n ),\n );\n }\n}\n\n/**\n * Get SDK version with Result-based error handling\n * Unlike getSdkVersion, this returns an error instead of a fallback\n */\nexport async function getSdkVersionSafe(): Promise<\n Result<string, FileSystemError>\n> {\n try {\n // CLI lives at packages/cli/portal/, SDK at packages/portal/sdk/\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPackagePath = join(packagesRoot, \"portal\", \"sdk\", \"package.json\");\n\n const content = await readFile(sdkPackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n const version = pkg.version;\n\n if (version === undefined) {\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.readError,\n \"SDK package.json does not contain a version field\",\n sdkPackagePath,\n ),\n );\n }\n\n return success(`^${version}`);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.fileNotFound,\n \"Could not find SDK package.json\",\n undefined,\n error,\n ),\n );\n }\n}\n","import { execa } from \"execa\";\n\n/**\n * Returns the install command for pnpm\n */\nexport function getInstallCommand(): string {\n return \"pnpm install\";\n}\n\n/**\n * Returns the run command for pnpm\n */\nexport function getRunCommand(script: string): string {\n return `pnpm run ${script}`;\n}\n\n/**\n * Runs a pnpm command in the specified directory\n */\nexport async function runPackageManager(\n args: string[],\n cwd: string,\n): Promise<void> {\n await execa(\"pnpm\", args, {\n cwd,\n stdio: \"inherit\",\n });\n}\n\n/**\n * Installs dependencies using pnpm\n */\nexport async function installDependencies(cwd: string): Promise<void> {\n await runPackageManager([\"install\"], cwd);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { join, dirname, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { copyFile } from \"node:fs/promises\";\nimport type { CreateOptions } from \"../types.js\";\nimport { promptProjectConfig } from \"../utils/prompts.js\";\nimport {\n getTemplatePaths,\n copyTemplate,\n directoryExists,\n createDirectory,\n getSdkVersion,\n getCoreVersion,\n getCliVersion,\n fileExists,\n} from \"../utils/file-system.js\";\nimport {\n installDependencies,\n getRunCommand,\n} from \"../utils/package-manager.js\";\n\nexport const createCommand: Command = new Command(\"create\")\n .description(\"Create a new Fluid portal application\")\n .argument(\"<app-name>\", \"Name of the application to create\")\n .option(\"--skip-install\", \"Skip dependency installation\")\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 file: links (for development testing)\",\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\n // Get template paths (base + overlay)\n const templatePaths = getTemplatePaths(\"starter\");\n if (!(await directoryExists(templatePaths.base))) {\n console.error(chalk.red(\"Error: Base template not found\"));\n process.exit(1);\n }\n if (!(await directoryExists(templatePaths.overlay))) {\n console.error(chalk.red(\"Error: Starter template not found\"));\n process.exit(1);\n }\n\n // Get package versions (SDK, core, and CLI are versioned independently)\n let sdkVersion: string;\n let coreVersion: string;\n const cliVersion = await getCliVersion();\n const isLocal = !!options.local;\n\n if (isLocal) {\n // Resolve relative file: paths so the generated project is portable\n // within the same monorepo clone (works regardless of absolute location)\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const packageRoot = join(currentDir, \"..\", \"..\");\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPath = join(packagesRoot, \"portal\", \"sdk\");\n const corePath = join(packagesRoot, \"portal\", \"core\");\n\n if (\n !(await directoryExists(sdkPath)) ||\n !(await directoryExists(corePath))\n ) {\n console.error(\n chalk.red(\n \"Error: --local requires running from within the fluid-mono monorepo\\n\" +\n \" Could not find packages/portal/sdk or packages/portal/core\",\n ),\n );\n process.exit(1);\n }\n\n sdkVersion = `file:${relative(targetPath, sdkPath)}`;\n coreVersion = `file:${relative(targetPath, corePath)}`;\n console.log(chalk.cyan(\" Using local packages (--local mode)\"));\n } else {\n sdkVersion = await getSdkVersion();\n coreVersion = await getCoreVersion();\n }\n\n // Create project directory\n const spinner = ora(\"Creating project directory...\").start();\n try {\n await createDirectory(targetPath);\n spinner.succeed(\"Created project directory\");\n } catch (error) {\n spinner.fail(\"Failed to create project directory\");\n throw error;\n }\n\n // Copy base template first, then overlay template-specific files on top\n const templateVariables = {\n projectName: config.name,\n sdkVersion,\n cliVersion,\n coreVersion,\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(\n templatePaths.overlay,\n targetPath,\n templateVariables,\n );\n\n // Copy .env.example → .env so dotenv works out of the box\n const envExamplePath = join(targetPath, \".env.example\");\n if (await fileExists(envExamplePath)) {\n await copyFile(envExamplePath, join(targetPath, \".env\"));\n }\n\n spinner.succeed(\"Copied template files\");\n } catch (error) {\n spinner.fail(\"Failed to copy template files\");\n throw error;\n }\n\n // Install dependencies\n if (config.installDeps) {\n spinner.start(\"Installing dependencies with pnpm...\");\n try {\n await installDependencies(targetPath);\n spinner.succeed(\"Installed dependencies\");\n } catch {\n spinner.fail(\"Failed to install dependencies\");\n console.log();\n console.log(\n chalk.yellow(\"You can try installing dependencies manually:\"),\n );\n console.log(chalk.cyan(` cd ${appName}`));\n console.log(chalk.cyan(\" pnpm install\"));\n }\n }\n\n // Print success message\n console.log();\n console.log(\n chalk.green.bold(\"Success!\") + ` Created ${chalk.cyan(appName)}`,\n );\n console.log();\n console.log(\"Next steps:\");\n console.log();\n const cdPath = options.outputDir ? targetPath : appName;\n console.log(chalk.cyan(` cd ${cdPath}`));\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 \" +\n chalk.cyan(\"http://localhost:5173\") +\n \" 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 \"Edit \" +\n chalk.cyan(\"src/portal.config.ts\") +\n \" to customize your navigation.\",\n );\n console.log();\n } catch (error) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (error instanceof Error ? error.message : String(error)),\n );\n console.log();\n process.exit(1);\n }\n });\n\nexport function registerCreateCommand(ctx: PluginContext): void {\n ctx.program.addCommand(createCommand);\n}\n","/**\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 { join } from \"node:path\";\nimport type { DevOptions } from \"../types.js\";\n\nconst PORTAL_DIR = \"portal\";\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 * 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 // ── 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/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 // Build vite args\n const viteArgs = [\"vite\"];\n if (options.port) {\n viteArgs.push(\"--port\", String(options.port));\n }\n if (options.host) {\n viteArgs.push(\"--host\");\n }\n\n console.log();\n console.log(chalk.bold(\"Starting development server...\"));\n console.log();\n\n try {\n await execa(\"pnpm\", viteArgs, {\n cwd,\n stdio: \"inherit\",\n });\n } catch (error) {\n // execa v8 sets `signal` (not `code`) when a process is killed by a signal\n const execaError = error as { signal?: string };\n if (execaError.signal === \"SIGINT\") {\n return;\n }\n console.error(chalk.red(\"Development server exited with an error\"));\n process.exit(1);\n }\n });\n\nexport function registerDevCommand(ctx: PluginContext): void {\n ctx.program.addCommand(devCommand);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { execa } from \"execa\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { BuildOptions } from \"../types.js\";\n\nexport const buildCommand: Command = new Command(\"build\")\n .description(\"Build the application for production\")\n .option(\"-o, --out-dir <dir>\", \"Output directory\", \"dist\")\n .action(async (options: BuildOptions) => {\n const cwd = process.cwd();\n\n // Check if we're in a Fluid project\n const packageJsonPath = join(cwd, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n console.error(\n chalk.red(\"Error: No package.json found in current directory\"),\n );\n console.error(\n chalk.yellow(\"Make sure you're in a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n // Check for vite.config\n const viteConfigPath = join(cwd, \"vite.config.ts\");\n if (!existsSync(viteConfigPath)) {\n console.error(chalk.red(\"Error: No vite.config.ts found\"));\n console.error(\n chalk.yellow(\"This command must be run from a Fluid project directory\"),\n );\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold(\"Building for production...\"));\n console.log();\n\n const spinner = ora(\"Building...\").start();\n\n try {\n // Run the project's build script\n await execa(\"pnpm\", [\"run\", \"build\"], {\n cwd,\n stdio: \"pipe\",\n });\n\n spinner.succeed(\"Build completed\");\n console.log();\n console.log(`Output written to ${chalk.cyan(options.outDir ?? \"dist\")}/`);\n console.log();\n console.log(\"To preview the build locally:\");\n console.log(chalk.cyan(\" pnpm vite preview\"));\n console.log();\n } catch (error) {\n spinner.fail(\"Build failed\");\n const execaError = error as { stderr?: string };\n if (execaError.stderr) {\n console.error(execaError.stderr);\n }\n process.exit(1);\n }\n });\n\nexport function registerBuildCommand(ctx: PluginContext): void {\n ctx.program.addCommand(buildCommand);\n}\n","/**\n * 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 } from \"node:path\";\nimport { existsSync, readdirSync } from \"node:fs\";\n\nimport { resolveSlugToId } from \"./mappings.js\";\nimport type { PortalMappings } from \"./mappings.js\";\nimport type { SnapshotDiff } from \"./snapshot.js\";\nimport type {\n LocalNavigation,\n LocalNavigationItem,\n LocalProfile,\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// 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\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// Cross-reference validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validate that all cross-references between local portal files are valid.\n *\n * Checks:\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 // 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 validateNavigationItems(\n nav.navigation_items,\n file,\n validScreenSlugs,\n errors,\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\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 } catch {\n errors.push({ file, message: \"Failed to read profile file\" });\n }\n }\n\n return errors;\n}\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 * `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 { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { getAuthToken, getActiveProfile } from \"@fluid-app/fluid-cli\";\nimport { createFetchClient } from \"@fluid-app/fluidos-api-client\";\nimport type { FetchClient, components } from \"@fluid-app/fluidos-api-client\";\nimport { fluidOs } from \"@fluid-app/fluidos-api-client\";\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\";\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} 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 categorizeChanges,\n validateCrossReferences,\n slugFromPath,\n} from \"../utils/push-validation.js\";\nimport type { CategorizedChanges } from \"../utils/push-validation.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 components[\"schemas\"][\"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\ninterface PushOptions {\n yes?: 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\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: Record<string, unknown>[],\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 FetchClient using the stored CLI profile.\n */\nfunction createClient(): FetchClient {\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 createFetchClient({\n baseUrl,\n getAuthToken: () => token,\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\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: FetchClient,\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 fluidOs.createFluidOSScreen(client, defId, {\n screen: {\n name: local.name,\n slug,\n component_tree: toApiComponentTree(\n local.component_tree,\n ) as unknown as Record<string, unknown>,\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 fluidOs.updateFluidOSScreen(client, defId, screenId, {\n screen: {\n name: local.name,\n slug,\n component_tree: toApiComponentTree(\n local.component_tree,\n ) as unknown as Record<string, unknown>,\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 fluidOs.deleteFluidOSScreen(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: FetchClient,\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 fluidOs.createFluidOSTheme(client, defId, {\n theme: {\n name: local.name,\n active: local.active,\n config: 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 fluidOs.updateFluidOSTheme(client, defId, themeId, {\n theme: {\n name: local.name,\n active: local.active,\n config: 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 fluidOs.deleteFluidOSTheme(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: NavigationSyncItem[],\n): NavigationSyncItem[] {\n const flat: NavigationSyncItem[] = [];\n for (const item of items) {\n const { children, ...rest } = item;\n flat.push(rest as NavigationSyncItem);\n if (children && children.length > 0) {\n flat.push(...flattenNavigationItems(children as NavigationSyncItem[]));\n }\n }\n return flat;\n}\n\n/**\n * Push navigation changes to the API.\n */\nasync function pushNavigations(\n client: FetchClient,\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\n // Create new navigations\n for (const file of changes.new) {\n const slug = slugFromPath(file);\n try {\n const local = await readPortalFile<LocalNavigation>(portalDir, file);\n const response = await fluidOs.createFluidOSNavigation(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 local→server ID mapping so child items reference the\n // correct server-assigned parent IDs.\n const resolvedItems = flattenNavigationItems(\n resolveNavigationItemScreenIds(\n local.navigation_items,\n currentMappings,\n ),\n );\n const localToServerId = new Map<number, number>();\n\n for (const item of resolvedItems) {\n const resolvedParentId =\n item.parent_id != null\n ? (localToServerId.get(item.parent_id) ?? item.parent_id)\n : undefined;\n\n const created = await fluidOs.createFluidOSNavigationItem(\n client,\n defId,\n newId,\n {\n navigation_item: {\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 },\n );\n\n if (item.id != null && created.navigation_item?.id != null) {\n localToServerId.set(item.id, created.navigation_item.id);\n }\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 navigations\n for (const file of changes.changed) {\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 try {\n const local = await readPortalFile<LocalNavigation>(portalDir, file);\n\n // Update navigation metadata\n await fluidOs.updateFluidOSNavigation(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 fluidOs.listFluidOSNavigationItems(\n client,\n defId,\n navId,\n );\n const serverItems = serverResponse.navigation_items ?? [];\n const serverById = new Map(serverItems.map((s) => [s.id, s]));\n const localIds = new Set(\n resolvedItems.filter((i) => i.id).map((i) => i.id),\n );\n\n // Delete server items not in local\n for (const serverItem of serverItems) {\n if (!localIds.has(serverItem.id)) {\n await fluidOs.deleteFluidOSNavigationItem(\n client,\n defId,\n navId,\n serverItem.id,\n );\n }\n }\n\n // Create or update local items.\n // Track local→server ID mapping so newly created child items\n // reference the correct server-assigned parent IDs.\n const localToServerId = new Map<number, number>();\n\n for (const item of resolvedItems) {\n const resolvedParentId =\n item.parent_id != null\n ? (localToServerId.get(item.parent_id) ?? item.parent_id)\n : undefined;\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 fluidOs.updateFluidOSNavigationItem(\n client,\n defId,\n navId,\n item.id,\n { navigation_item: body },\n );\n } else {\n // Create new\n const created = await fluidOs.createFluidOSNavigationItem(\n client,\n defId,\n navId,\n {\n navigation_item: {\n ...body,\n label: body.label ?? \"\",\n position: body.position ?? 0,\n },\n },\n );\n\n if (item.id != null && created.navigation_item?.id != null) {\n localToServerId.set(item.id, created.navigation_item.id);\n }\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 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 fluidOs.deleteFluidOSNavigation(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: FetchClient,\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 fluidOs.createFluidOSProfile(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 fluidOs.updateFluidOSProfile(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 fluidOs.deleteFluidOSProfile(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 .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 // ── 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 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 cross-references...\");\n const validationErrors = await validateCrossReferences(\n portalDir,\n mappings,\n changes,\n );\n\n if (validationErrors.length > 0) {\n spinner.fail(\"Cross-reference 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(\"Cross-references valid\");\n\n // ── Authenticate ───────────────────────────────────────────────────\n spinner.start(\"Authenticating...\");\n\n let client: FetchClient;\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 // ── 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 successfulFiles = new Set(\n allResults.filter((r) => r.success).map((r) => r.file),\n );\n\n if (successfulFiles.size > 0) {\n // Only advance snapshot entries for successfully pushed files;\n // leave skipped/failed entries at their original hashes so they\n // show up as changed on the next run.\n const updatedHashes = { ...snapshot.files };\n for (const file of successfulFiles) {\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 await writeSnapshot(portalSyncDir, {\n ...snapshot,\n files: updatedHashes,\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\nexport function registerPushCommand(ctx: PluginContext): void {\n ctx.program.addCommand(pushCommand);\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 const lastImportIndex = source.lastIndexOf(\"import \");\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/**\n * Insert a manifest reference into the customWidgets array.\n * Preserves developer comments. Skips if already present.\n * Returns null if the array pattern isn't found.\n */\nexport function insertIntoCustomWidgets(\n source: string,\n camelName: string,\n): string | null {\n let matched = false;\n const result = source.replace(\n /(export const customWidgets(?::\\s*WidgetManifest\\[\\])?)\\s*=\\s*\\[([^\\]]*)\\]/,\n (_match, declaration: string, inner: string) => {\n matched = true;\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(camelName)) {\n return _match;\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, camelName];\n const entryBlock = allEntries.map((e) => ` ${e},`).join(\"\\n\");\n\n return `${declaration} = [${commentBlock}\\n${entryBlock}\\n]`;\n },\n );\n\n return matched ? result : null;\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 type { WidgetCreateOptions } from \"../types.js\";\nimport {\n toWidgetType,\n toComponentName,\n toDisplayName,\n toCamelCase,\n validateWidgetName,\n insertImport,\n insertIntoCustomWidgets,\n} from \"../utils/widget-helpers.js\";\n\nconst createSubcommand = new Command(\"create\")\n .description(\"Scaffold a new custom 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 widgetDir = path.join(cwd, \"src\", \"widgets\", name);\n\n // Validate name format\n const validationError = validateWidgetName(name);\n if (validationError) {\n console.error(chalk.red(`Error: ${validationError}`));\n process.exit(1);\n }\n\n // Check we're in a Fluid portal project\n if (!fs.existsSync(path.join(cwd, \"src\", \"portal.config.ts\"))) {\n console.error(chalk.red(\"Error: No src/portal.config.ts found.\"));\n console.error(\n chalk.yellow(\"Run this command from a Fluid portal project root.\"),\n );\n process.exit(1);\n }\n\n // Check widget doesn't already exist\n if (fs.existsSync(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 displayName = toDisplayName(name);\n const category = options.category ?? \"components\";\n\n // Create widget directory and write files — clean up on failure\n try {\n await fs.ensureDir(widgetDir);\n\n await fs.writeFile(\n path.join(widgetDir, \"component.tsx\"),\n `interface ${widgetType}Props {\n title?: string;\n}\n\nexport function ${componentName}({ title = \"${displayName}\" }: ${widgetType}Props) {\n return (\n <div className=\"p-4\">\n <h3 className=\"text-lg font-semibold\">{title}</h3>\n <p className=\"text-sm text-gray-500\">\n Edit this widget in src/widgets/${name}/component.tsx\n </p>\n </div>\n );\n}\n`,\n );\n\n await fs.writeFile(\n path.join(widgetDir, \"manifest.ts\"),\n `import type { WidgetManifest } from \"@fluid-app/portal-core/registries\";\nimport { ${componentName} } from \"./component\";\n\nexport const manifest: WidgetManifest = {\n manifestVersion: 1,\n type: \"${widgetType}\",\n component: ${componentName},\n displayName: \"${displayName}\",\n description: \"A custom ${displayName.toLowerCase()} widget\",\n icon: \"puzzle-piece\",\n category: \"${category}\",\n propertySchema: {\n widgetType: \"${widgetType}\",\n displayName: \"${displayName}\",\n fields: [{ key: \"title\", label: \"Title\", type: \"text\" }],\n },\n defaultProps: {\n title: \"${displayName}\",\n },\n};\n`,\n );\n\n await fs.writeFile(\n path.join(widgetDir, \"index.ts\"),\n `export { ${componentName} } from \"./component\";\nexport { manifest } from \"./manifest\";\n`,\n );\n } catch (err) {\n await fs.remove(widgetDir).catch(() => {});\n console.error(chalk.red(\"Error: Failed to scaffold widget files.\"));\n console.error(err);\n process.exit(1);\n }\n\n // Auto-register in portal.config.ts\n const configPath = path.join(cwd, \"src\", \"portal.config.ts\");\n const camelName = toCamelCase(name);\n const importLine = `import { manifest as ${camelName} } from \"./widgets/${name}\";`;\n\n const configSource = await fs.readFile(configPath, \"utf-8\");\n\n const withImport = insertImport(configSource, importLine);\n if (withImport === null) {\n console.warn(\n chalk.yellow(\n \"Warning: Could not find import statements in portal.config.ts. \" +\n \"Add the import manually:\",\n ),\n );\n console.warn(chalk.cyan(` ${importLine}`));\n }\n\n let updated = insertIntoCustomWidgets(\n withImport ?? configSource,\n camelName,\n );\n if (updated === null) {\n if (withImport === null) {\n // Neither the import nor the array could be placed — warn and bail\n // to avoid writing a broken config with an undefined identifier\n console.warn(\n chalk.yellow(\n \"Warning: Could not register widget in portal.config.ts. Add manually:\",\n ),\n );\n console.warn(chalk.cyan(` ${importLine}`));\n console.warn(chalk.cyan(` customWidgets: [..., ${camelName}]`));\n } else {\n // Import succeeded but customWidgets array doesn't exist yet — create it\n updated =\n withImport.trimEnd() +\n `\\n\\nexport const customWidgets = [${camelName}];\\n`;\n }\n }\n\n if (updated !== null) {\n await fs.writeFile(configPath, updated, \"utf-8\");\n } else if (withImport !== null) {\n await fs.writeFile(configPath, withImport, \"utf-8\");\n }\n\n console.log();\n console.log(chalk.green(\"Created widget:\") + ` src/widgets/${name}/`);\n console.log(chalk.gray(\" component.tsx — React component\"));\n console.log(chalk.gray(\" manifest.ts — WidgetManifest\"));\n console.log(chalk.gray(\" index.ts — Re-exports\"));\n console.log();\n if (updated !== null) {\n console.log(\n chalk.green(\"Registered\") + ` in ${chalk.cyan(\"src/portal.config.ts\")}`,\n );\n console.log();\n }\n console.log(chalk.yellow(\"Next steps:\"));\n console.log(\n ` 1. Edit the component in ${chalk.cyan(`src/widgets/${name}/component.tsx`)}`,\n );\n console.log(\n ` 2. Customize the manifest fields in ${chalk.cyan(`src/widgets/${name}/manifest.ts`)}`,\n );\n console.log(\n ` 3. Run ${chalk.cyan(\"fluid portal dev\")} to preview in the builder`,\n );\n });\n\nexport const widgetCommand: Command = new Command(\"widget\")\n .description(\"Manage custom 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 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 { createFetchClient, fluidOs } from \"@fluid-app/fluidos-api-client\";\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 createFetchClient({\n baseUrl: getApiBase(),\n getAuthToken: () => 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 fluidOs.createFluidOSVersion(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 fluidOs.updateFluidOSVersion(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 fluidOs.listFluidOSVersions(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 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 fluidOs.updateFluidOSVersion(client, definitionId, versionId, {\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","/**\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 { widgetCommand } from \"./commands/widget-create.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(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} 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 getCoreVersion,\n readFileSafe,\n writeFileSafe,\n createDirectorySafe,\n copyTemplateSafe,\n getSdkVersionSafe,\n FILE_SYSTEM_ERRORS,\n type FileSystemErrorCode,\n type FileSystemError,\n type TemplatePaths,\n} from \"./utils/file-system.js\";\n\nexport { promptProjectConfig } from \"./utils/prompts.js\";\n\n// Expose the standalone Command for create-portal-app\nexport { createCommand } from \"./commands/create.js\";\nexport { pullCommand } from \"./commands/pull.js\";\nexport { pushCommand } from \"./commands/push.js\";\nexport { widgetCommand } from \"./commands/widget-create.js\";\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 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","path","readPortalFile","PORTAL_SYNC_DIR","createClient","fluidOs.createFluidOSScreen","fluidOs.updateFluidOSScreen","fluidOs.deleteFluidOSScreen","fluidOs.createFluidOSTheme","fluidOs.updateFluidOSTheme","fluidOs.deleteFluidOSTheme","fluidOs.createFluidOSNavigation","fluidOs.createFluidOSNavigationItem","fluidOs.updateFluidOSNavigation","fluidOs.listFluidOSNavigationItems","fluidOs.deleteFluidOSNavigationItem","fluidOs.updateFluidOSNavigationItem","fluidOs.deleteFluidOSNavigation","fluidOs.createFluidOSProfile","fluidOs.updateFluidOSProfile","fluidOs.deleteFluidOSProfile","fluidOs.createFluidOSVersion","fluidOs.updateFluidOSVersion","fluidOs.listFluidOSVersions"],"sources":["../src/types.ts","../src/utils/prompts.ts","../src/utils/file-system.ts","../src/utils/package-manager.ts","../src/commands/create.ts","../src/commands/dev.ts","../src/utils/extract-manifests.ts","../src/commands/build.ts","../src/utils/push-validation.ts","../src/commands/push.ts","../src/utils/widget-helpers.ts","../src/commands/widget-create.ts","../src/commands/doctor.ts","../src/commands/version.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 /** 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 file: links instead of npm versions */\n readonly local?: 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// 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 /** CLI package version (versioned independently from the SDK) */\n readonly cliVersion: string;\n /** portal-core dependency version */\n readonly coreVersion: 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 } 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\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 */\nfunction getOutputFilename(filename: string): string {\n if (filename.endsWith(\".template\")) {\n return filename.slice(0, -\".template\".length);\n }\n return filename;\n}\n\n/**\n * Copies a template directory to the target directory\n * Processes .template files with Handlebars\n */\nexport async function copyTemplate(\n templatePath: string,\n targetPath: string,\n variables: TemplateVariables,\n): Promise<void> {\n const files = await getFiles(templatePath);\n\n for (const file of files) {\n const sourcePath = join(templatePath, file);\n const isTemplate = file.endsWith(\".template\");\n const outputFile = getOutputFilename(file);\n const destPath = join(targetPath, outputFile);\n\n // Create directory if needed\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n\n // Read source file\n const content = await readFile(sourcePath, \"utf-8\");\n\n // Process and write\n const processed = processTemplate(\n content,\n variables,\n isTemplate,\n sourcePath,\n );\n await writeFile(destPath, processed, \"utf-8\");\n }\n}\n\n/**\n * Checks if a directory exists\n */\nexport async function directoryExists(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Checks if a file exists\n */\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Checks if a path exists (file or directory)\n */\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Creates a directory\n */\nexport async function createDirectory(path: string): Promise<void> {\n await mkdir(path, { recursive: true });\n}\n\n/**\n * Reads the SDK version from the workspace package.json\n * Falls back to ^0.1.0 if not found\n */\nexport async function getSdkVersion(): Promise<string> {\n try {\n // Try to read from workspace\n // CLI lives at packages/cli/portal/, SDK at packages/portal/sdk/\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPackagePath = join(packagesRoot, \"portal\", \"sdk\", \"package.json\");\n\n const content = await readFile(sdkPackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n return `^${pkg.version ?? \"0.1.0\"}`;\n } catch {\n // Fallback for when running outside the workspace\n return \"^0.1.0\";\n }\n}\n\n/**\n * Reads the portal-core version from the workspace package.json.\n * Falls back to ^0.1.0 if not found.\n */\nexport async function getCoreVersion(): Promise<string> {\n try {\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const corePackagePath = join(\n packagesRoot,\n \"portal\",\n \"core\",\n \"package.json\",\n );\n\n const content = await readFile(corePackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n return `^${pkg.version ?? \"0.1.0\"}`;\n } catch {\n return \"^0.1.0\";\n }\n}\n\n/**\n * Reads the CLI core version from the workspace package.json.\n * Falls back to ^0.1.0 if not found.\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 try {\n // CLI portal lives at packages/cli/portal/, CLI core at packages/cli/core/\n const packageRoot = findPackageRoot();\n const cliCorePath = join(packageRoot, \"..\", \"core\", \"package.json\");\n\n const content = await readFile(cliCorePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n return `^${pkg.version ?? \"0.1.0\"}`;\n } catch {\n return \"^0.1.0\";\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result-based variants for type-safe error handling\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Read a file's content with Result-based error handling\n */\nexport async function readFileSafe(\n path: string,\n): Promise<Result<string, FileSystemError>> {\n try {\n const content = await readFile(path, \"utf-8\");\n return success(content);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.readError,\n `Failed to read file: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Write content to a file with Result-based error handling\n */\nexport async function writeFileSafe(\n path: string,\n content: string,\n): Promise<Result<void, FileSystemError>> {\n try {\n await writeFile(path, content, \"utf-8\");\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.writeError,\n `Failed to write file: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Create a directory with Result-based error handling\n */\nexport async function createDirectorySafe(\n path: string,\n): Promise<Result<void, FileSystemError>> {\n try {\n await mkdir(path, { recursive: true });\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.writeError,\n `Failed to create directory: ${path}`,\n path,\n error,\n ),\n );\n }\n}\n\n/**\n * Copy a template directory with Result-based error handling\n */\nexport async function copyTemplateSafe(\n templatePath: string,\n targetPath: string,\n variables: Readonly<TemplateVariables>,\n): Promise<Result<void, FileSystemError>> {\n try {\n const files = await getFiles(templatePath);\n\n for (const file of files) {\n const sourcePath = join(templatePath, file);\n const isTemplateFile = file.endsWith(\".template\");\n const outputFile = getOutputFilename(file);\n const destPath = join(targetPath, outputFile);\n\n // Create directory if needed\n const destDir = dirname(destPath);\n await mkdir(destDir, { recursive: true });\n\n // Read source file\n const content = await readFile(sourcePath, \"utf-8\");\n\n // Process and write\n const processed = processTemplate(\n content,\n variables,\n isTemplateFile,\n sourcePath,\n );\n await writeFile(destPath, processed, \"utf-8\");\n }\n\n return success(undefined);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.templateError,\n `Failed to copy template from ${templatePath} to ${targetPath}`,\n templatePath,\n error,\n ),\n );\n }\n}\n\n/**\n * Get SDK version with Result-based error handling\n * Unlike getSdkVersion, this returns an error instead of a fallback\n */\nexport async function getSdkVersionSafe(): Promise<\n Result<string, FileSystemError>\n> {\n try {\n // CLI lives at packages/cli/portal/, SDK at packages/portal/sdk/\n const packageRoot = findPackageRoot();\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPackagePath = join(packagesRoot, \"portal\", \"sdk\", \"package.json\");\n\n const content = await readFile(sdkPackagePath, \"utf-8\");\n const pkg = JSON.parse(content) as { version?: string };\n const version = pkg.version;\n\n if (version === undefined) {\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.readError,\n \"SDK package.json does not contain a version field\",\n sdkPackagePath,\n ),\n );\n }\n\n return success(`^${version}`);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return failure(\n createFsError(\n FILE_SYSTEM_ERRORS.fileNotFound,\n \"Could not find SDK package.json\",\n undefined,\n error,\n ),\n );\n }\n}\n","import { execa } from \"execa\";\n\n/**\n * Returns the install command for pnpm\n */\nexport function getInstallCommand(): string {\n return \"pnpm install\";\n}\n\n/**\n * Returns the run command for pnpm\n */\nexport function getRunCommand(script: string): string {\n return `pnpm run ${script}`;\n}\n\n/**\n * Runs a pnpm command in the specified directory\n */\nexport async function runPackageManager(\n args: string[],\n cwd: string,\n): Promise<void> {\n await execa(\"pnpm\", args, {\n cwd,\n stdio: \"inherit\",\n });\n}\n\n/**\n * Installs dependencies using pnpm\n */\nexport async function installDependencies(cwd: string): Promise<void> {\n await runPackageManager([\"install\"], cwd);\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { join, dirname, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { copyFile } from \"node:fs/promises\";\nimport type { CreateOptions } from \"../types.js\";\nimport { promptProjectConfig } from \"../utils/prompts.js\";\nimport {\n getTemplatePaths,\n copyTemplate,\n directoryExists,\n createDirectory,\n getSdkVersion,\n getCoreVersion,\n getCliVersion,\n fileExists,\n} from \"../utils/file-system.js\";\nimport {\n installDependencies,\n getRunCommand,\n} from \"../utils/package-manager.js\";\n\nexport const createCommand: Command = new Command(\"create\")\n .description(\"Create a new Fluid portal application\")\n .argument(\"<app-name>\", \"Name of the application to create\")\n .option(\"--skip-install\", \"Skip dependency installation\")\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 file: links (for development testing)\",\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\n // Get template paths (base + overlay)\n const templatePaths = getTemplatePaths(\"starter\");\n if (!(await directoryExists(templatePaths.base))) {\n console.error(chalk.red(\"Error: Base template not found\"));\n process.exit(1);\n }\n if (!(await directoryExists(templatePaths.overlay))) {\n console.error(chalk.red(\"Error: Starter template not found\"));\n process.exit(1);\n }\n\n // Get package versions (SDK, core, and CLI are versioned independently)\n let sdkVersion: string;\n let coreVersion: string;\n const cliVersion = await getCliVersion();\n const isLocal = !!options.local;\n\n if (isLocal) {\n // Resolve relative file: paths so the generated project is portable\n // within the same monorepo clone (works regardless of absolute location)\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const packageRoot = join(currentDir, \"..\", \"..\");\n const packagesRoot = join(packageRoot, \"..\", \"..\");\n const sdkPath = join(packagesRoot, \"portal\", \"sdk\");\n const corePath = join(packagesRoot, \"portal\", \"core\");\n\n if (\n !(await directoryExists(sdkPath)) ||\n !(await directoryExists(corePath))\n ) {\n console.error(\n chalk.red(\n \"Error: --local requires running from within the fluid-mono monorepo\\n\" +\n \" Could not find packages/portal/sdk or packages/portal/core\",\n ),\n );\n process.exit(1);\n }\n\n sdkVersion = `file:${relative(targetPath, sdkPath)}`;\n coreVersion = `file:${relative(targetPath, corePath)}`;\n console.log(chalk.cyan(\" Using local packages (--local mode)\"));\n } else {\n sdkVersion = await getSdkVersion();\n coreVersion = await getCoreVersion();\n }\n\n // Create project directory\n const spinner = ora(\"Creating project directory...\").start();\n try {\n await createDirectory(targetPath);\n spinner.succeed(\"Created project directory\");\n } catch (error) {\n spinner.fail(\"Failed to create project directory\");\n throw error;\n }\n\n // Copy base template first, then overlay template-specific files on top\n const templateVariables = {\n projectName: config.name,\n sdkVersion,\n cliVersion,\n coreVersion,\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(\n templatePaths.overlay,\n targetPath,\n templateVariables,\n );\n\n // Copy .env.example → .env so dotenv works out of the box\n const envExamplePath = join(targetPath, \".env.example\");\n if (await fileExists(envExamplePath)) {\n await copyFile(envExamplePath, join(targetPath, \".env\"));\n }\n\n spinner.succeed(\"Copied template files\");\n } catch (error) {\n spinner.fail(\"Failed to copy template files\");\n throw error;\n }\n\n // Install dependencies\n if (config.installDeps) {\n spinner.start(\"Installing dependencies with pnpm...\");\n try {\n await installDependencies(targetPath);\n spinner.succeed(\"Installed dependencies\");\n } catch {\n spinner.fail(\"Failed to install dependencies\");\n console.log();\n console.log(\n chalk.yellow(\"You can try installing dependencies manually:\"),\n );\n console.log(chalk.cyan(` cd ${appName}`));\n console.log(chalk.cyan(\" pnpm install\"));\n }\n }\n\n // Print success message\n console.log();\n console.log(\n chalk.green.bold(\"Success!\") + ` Created ${chalk.cyan(appName)}`,\n );\n console.log();\n console.log(\"Next steps:\");\n console.log();\n const cdPath = options.outputDir ? targetPath : appName;\n console.log(chalk.cyan(` cd ${cdPath}`));\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 \" +\n chalk.cyan(\"http://localhost:5173\") +\n \" 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 \"Edit \" +\n chalk.cyan(\"src/portal.config.ts\") +\n \" to customize your navigation.\",\n );\n console.log();\n } catch (error) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (error instanceof Error ? error.message : String(error)),\n );\n console.log();\n process.exit(1);\n }\n });\n\nexport function registerCreateCommand(ctx: PluginContext): void {\n ctx.program.addCommand(createCommand);\n}\n","/**\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 { join } from \"node:path\";\nimport type { DevOptions } from \"../types.js\";\n\nconst PORTAL_DIR = \"portal\";\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 * 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 // ── 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/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 // Build vite args\n const viteArgs = [\"vite\"];\n if (options.port) {\n viteArgs.push(\"--port\", String(options.port));\n }\n if (options.host) {\n viteArgs.push(\"--host\");\n }\n\n console.log();\n console.log(chalk.bold(\"Starting development server...\"));\n console.log();\n\n try {\n await execa(\"pnpm\", viteArgs, {\n cwd,\n stdio: \"inherit\",\n });\n } catch (error) {\n // execa v8 sets `signal` (not `code`) when a process is killed by a signal\n const execaError = error as { signal?: string };\n if (execaError.signal === \"SIGINT\") {\n return;\n }\n console.error(chalk.red(\"Development server exited with an error\"));\n process.exit(1);\n }\n });\n\nexport function registerDevCommand(ctx: PluginContext): void {\n ctx.program.addCommand(devCommand);\n}\n","/**\n * Manifest extraction utility\n *\n * Extracts serializable widget manifest metadata from portal.config.ts\n * by writing a minimal wrapper script that imports customWidgets and\n * serializes the result to stdout, then running it with tsx.\n *\n * Strips the `component` field (not serializable) from each manifest.\n *\n * Writes a temp script, runs it with tsx, and parses JSON output. This\n * avoids needing Vite's module resolution — portal.config.ts must use\n * relative imports only.\n *\n * Used by `fluid build`. The dev server uses Vite's ssrLoadModule\n * instead (see manifest-plugin.ts in portal-sdk).\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { type Result, success, failure } from \"@fluid-app/fluid-cli\";\n\n/**\n * Serializable widget manifest — the JSON-safe subset of WidgetManifest.\n * Mirrors SerializableManifest from portal-core without the package dependency.\n */\nexport interface ExtractedManifest {\n readonly manifestVersion: number;\n readonly type: string;\n readonly displayName: string;\n readonly description: string;\n readonly icon: string;\n readonly category: string;\n readonly propertySchema: Record<string, unknown>;\n readonly defaultProps: Record<string, unknown>;\n readonly [key: string]: unknown;\n}\n\nexport interface ManifestExtractionError {\n readonly code: \"EXTRACTION_FAILED\" | \"INVALID_FORMAT\";\n readonly message: string;\n readonly details?: string;\n}\n\nconst EXTRACT_FILENAME = \"__fluid_extract_manifests.ts\";\n\n/**\n * Extract serializable widget manifests from a project's portal.config.ts.\n *\n * Writes a temp wrapper script, runs it with tsx, and parses JSON output.\n * The temp file is always cleaned up.\n *\n * Returns an empty array if no customWidgets export exists.\n *\n * @param projectDir - The project root directory containing src/portal.config.ts\n */\nexport async function extractManifests(\n projectDir: string,\n): Promise<Result<ExtractedManifest[], ManifestExtractionError>> {\n const configPath = path.join(projectDir, \"src\", \"portal.config.ts\");\n const extractFile = path.join(projectDir, EXTRACT_FILENAME);\n\n try {\n if (!(await fs.pathExists(configPath))) {\n return success([]);\n }\n\n const configSource = await fs.readFile(configPath, \"utf-8\");\n\n // Strip comments so the regex doesn't match commented-out exports\n const strippedSource = configSource\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n\n // Check if customWidgets export exists (anchored to avoid matching customWidgetsSomething)\n if (!/export\\s+(const|let)\\s+customWidgets[\\s:=]/.test(strippedSource)) {\n return success([]);\n }\n\n // Write wrapper script that imports manifests and strips component field\n const wrapperScript = `\nimport { customWidgets } from \"./src/portal.config.ts\";\n\nconst serializable = customWidgets.map(({ component, ...rest }) => rest);\nconsole.log(JSON.stringify(serializable));\n`;\n await fs.writeFile(extractFile, wrapperScript, \"utf-8\");\n\n const result = await execa(\"npx\", [\"tsx\", EXTRACT_FILENAME], {\n cwd: projectDir,\n stdio: \"pipe\",\n env: { ...process.env, NODE_ENV: \"production\" },\n });\n\n const output = result.stdout.trim();\n if (!output || output === \"null\" || output === \"[]\") {\n return success([]);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(output);\n } catch {\n return failure({\n code: \"INVALID_FORMAT\",\n message: \"Failed to parse manifest output as JSON\",\n details: `Output was: ${output.slice(0, 200)}`,\n });\n }\n\n if (!Array.isArray(parsed)) {\n return failure({\n code: \"INVALID_FORMAT\",\n message: \"customWidgets export is not an array\",\n details: `Expected an array, got: ${typeof parsed}`,\n });\n }\n\n const validated = (parsed as unknown[]).filter(\n (m): m is ExtractedManifest =>\n typeof m === \"object\" &&\n m !== null &&\n typeof (m as Record<string, unknown>).type === \"string\" &&\n typeof (m as Record<string, unknown>).displayName === \"string\",\n );\n return success(validated);\n } catch (err) {\n const error = err as { stderr?: string; message?: string };\n return failure({\n code: \"EXTRACTION_FAILED\",\n message: \"Failed to extract widget manifests from portal.config.ts\",\n details: error.stderr ?? error.message ?? String(err),\n });\n } finally {\n await fs.remove(extractFile).catch(() => {});\n }\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, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { BuildOptions } from \"../types.js\";\nimport { extractManifests } from \"../utils/extract-manifests.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 // Extract widget manifests and write to build output.\n // The manifest plugin emits an empty __manifests__.json to the Vite outDir.\n // We overwrite it with real data. Check both common outDir layouts:\n // - \"dist/public\" (monorepo portal)\n // - \"dist\" (starter template, Vite default)\n const manifestSpinner = ora(\"Extracting widget manifests...\").start();\n const outDir = options.outDir ?? \"dist\";\n const manifestResult = await extractManifests(cwd);\n\n // Find where the empty __manifests__.json was emitted by the manifest plugin\n const candidatePaths = [\n join(cwd, outDir, \"__manifests__.json\"),\n join(cwd, outDir, \"public\", \"__manifests__.json\"),\n ];\n const manifestPath = candidatePaths.find((p) => existsSync(p));\n\n if (manifestResult.success) {\n if (manifestResult.value.length === 0) {\n manifestSpinner.info(\"No custom widgets found\");\n } else if (!manifestPath) {\n manifestSpinner.warn(\n `__manifests__.json not found in build output — skipping manifest write`,\n );\n } else {\n writeFileSync(manifestPath, JSON.stringify(manifestResult.value));\n manifestSpinner.succeed(\n `Extracted ${manifestResult.value.length} widget manifest(s)`,\n );\n }\n } else {\n manifestSpinner.warn(\n `Manifest extraction failed: ${manifestResult.error.message}`,\n );\n }\n\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","/**\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 } from \"node:path\";\nimport { existsSync, readdirSync } from \"node:fs\";\n\nimport { resolveSlugToId } from \"./mappings.js\";\nimport type { PortalMappings } from \"./mappings.js\";\nimport type { SnapshotDiff } from \"./snapshot.js\";\nimport type {\n LocalNavigation,\n LocalNavigationItem,\n LocalProfile,\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// 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\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// Cross-reference validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Validate that all cross-references between local portal files are valid.\n *\n * Checks:\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 // 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 validateNavigationItems(\n nav.navigation_items,\n file,\n validScreenSlugs,\n errors,\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\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 } catch {\n errors.push({ file, message: \"Failed to read profile file\" });\n }\n }\n\n return errors;\n}\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 * `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 { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { getAuthToken, getActiveProfile } from \"@fluid-app/fluid-cli\";\nimport { createFetchClient } from \"@fluid-app/fluidos-api-client\";\nimport type { FetchClient, components } from \"@fluid-app/fluidos-api-client\";\nimport { fluidOs } from \"@fluid-app/fluidos-api-client\";\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\";\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} 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 categorizeChanges,\n validateCrossReferences,\n slugFromPath,\n} from \"../utils/push-validation.js\";\nimport type { CategorizedChanges } from \"../utils/push-validation.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 components[\"schemas\"][\"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\ninterface PushOptions {\n yes?: 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\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: Record<string, unknown>[],\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 FetchClient using the stored CLI profile.\n */\nfunction createClient(): FetchClient {\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 createFetchClient({\n baseUrl,\n getAuthToken: () => token,\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\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: FetchClient,\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 fluidOs.createFluidOSScreen(client, defId, {\n screen: {\n name: local.name,\n slug,\n component_tree: toApiComponentTree(\n local.component_tree,\n ) as unknown as Record<string, unknown>,\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 fluidOs.updateFluidOSScreen(client, defId, screenId, {\n screen: {\n name: local.name,\n slug,\n component_tree: toApiComponentTree(\n local.component_tree,\n ) as unknown as Record<string, unknown>,\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 fluidOs.deleteFluidOSScreen(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: FetchClient,\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 fluidOs.createFluidOSTheme(client, defId, {\n theme: {\n name: local.name,\n active: local.active,\n config: 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 fluidOs.updateFluidOSTheme(client, defId, themeId, {\n theme: {\n name: local.name,\n active: local.active,\n config: 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 fluidOs.deleteFluidOSTheme(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: NavigationSyncItem[],\n): NavigationSyncItem[] {\n const flat: NavigationSyncItem[] = [];\n for (const item of items) {\n const { children, ...rest } = item;\n flat.push(rest as NavigationSyncItem);\n if (children && children.length > 0) {\n flat.push(...flattenNavigationItems(children as NavigationSyncItem[]));\n }\n }\n return flat;\n}\n\n/**\n * Push navigation changes to the API.\n */\nasync function pushNavigations(\n client: FetchClient,\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\n // Create new navigations\n for (const file of changes.new) {\n const slug = slugFromPath(file);\n try {\n const local = await readPortalFile<LocalNavigation>(portalDir, file);\n const response = await fluidOs.createFluidOSNavigation(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 local→server ID mapping so child items reference the\n // correct server-assigned parent IDs.\n const resolvedItems = flattenNavigationItems(\n resolveNavigationItemScreenIds(\n local.navigation_items,\n currentMappings,\n ),\n );\n const localToServerId = new Map<number, number>();\n\n for (const item of resolvedItems) {\n const resolvedParentId =\n item.parent_id != null\n ? (localToServerId.get(item.parent_id) ?? item.parent_id)\n : undefined;\n\n const created = await fluidOs.createFluidOSNavigationItem(\n client,\n defId,\n newId,\n {\n navigation_item: {\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 },\n );\n\n if (item.id != null && created.navigation_item?.id != null) {\n localToServerId.set(item.id, created.navigation_item.id);\n }\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 navigations\n for (const file of changes.changed) {\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 try {\n const local = await readPortalFile<LocalNavigation>(portalDir, file);\n\n // Update navigation metadata\n await fluidOs.updateFluidOSNavigation(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 fluidOs.listFluidOSNavigationItems(\n client,\n defId,\n navId,\n );\n const serverItems = serverResponse.navigation_items ?? [];\n const serverById = new Map(serverItems.map((s) => [s.id, s]));\n const localIds = new Set(\n resolvedItems.filter((i) => i.id).map((i) => i.id),\n );\n\n // Delete server items not in local\n for (const serverItem of serverItems) {\n if (!localIds.has(serverItem.id)) {\n await fluidOs.deleteFluidOSNavigationItem(\n client,\n defId,\n navId,\n serverItem.id,\n );\n }\n }\n\n // Create or update local items.\n // Track local→server ID mapping so newly created child items\n // reference the correct server-assigned parent IDs.\n const localToServerId = new Map<number, number>();\n\n for (const item of resolvedItems) {\n const resolvedParentId =\n item.parent_id != null\n ? (localToServerId.get(item.parent_id) ?? item.parent_id)\n : undefined;\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 fluidOs.updateFluidOSNavigationItem(\n client,\n defId,\n navId,\n item.id,\n { navigation_item: body },\n );\n } else {\n // Create new\n const created = await fluidOs.createFluidOSNavigationItem(\n client,\n defId,\n navId,\n {\n navigation_item: {\n ...body,\n label: body.label ?? \"\",\n position: body.position ?? 0,\n },\n },\n );\n\n if (item.id != null && created.navigation_item?.id != null) {\n localToServerId.set(item.id, created.navigation_item.id);\n }\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 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 fluidOs.deleteFluidOSNavigation(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: FetchClient,\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 fluidOs.createFluidOSProfile(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 fluidOs.updateFluidOSProfile(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 fluidOs.deleteFluidOSProfile(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 .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 // ── 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 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 cross-references...\");\n const validationErrors = await validateCrossReferences(\n portalDir,\n mappings,\n changes,\n );\n\n if (validationErrors.length > 0) {\n spinner.fail(\"Cross-reference 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(\"Cross-references valid\");\n\n // ── Authenticate ───────────────────────────────────────────────────\n spinner.start(\"Authenticating...\");\n\n let client: FetchClient;\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 // ── 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 successfulFiles = new Set(\n allResults.filter((r) => r.success).map((r) => r.file),\n );\n\n if (successfulFiles.size > 0) {\n // Only advance snapshot entries for successfully pushed files;\n // leave skipped/failed entries at their original hashes so they\n // show up as changed on the next run.\n const updatedHashes = { ...snapshot.files };\n for (const file of successfulFiles) {\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 await writeSnapshot(portalSyncDir, {\n ...snapshot,\n files: updatedHashes,\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\nexport function registerPushCommand(ctx: PluginContext): void {\n ctx.program.addCommand(pushCommand);\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 const lastImportIndex = source.lastIndexOf(\"import \");\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/**\n * Insert a manifest reference into the customWidgets array.\n * Preserves developer comments. Skips if already present.\n * Returns null if the array pattern isn't found.\n */\nexport function insertIntoCustomWidgets(\n source: string,\n camelName: string,\n): string | null {\n let matched = false;\n const result = source.replace(\n /(export const customWidgets(?::\\s*WidgetManifest\\[\\])?)\\s*=\\s*\\[([^\\]]*)\\]/,\n (_match, declaration: string, inner: string) => {\n matched = true;\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(camelName)) {\n return _match;\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, camelName];\n const entryBlock = allEntries.map((e) => ` ${e},`).join(\"\\n\");\n\n return `${declaration} = [${commentBlock}\\n${entryBlock}\\n]`;\n },\n );\n\n return matched ? result : null;\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 type { WidgetCreateOptions } from \"../types.js\";\nimport {\n toWidgetType,\n toComponentName,\n toDisplayName,\n toCamelCase,\n validateWidgetName,\n insertImport,\n insertIntoCustomWidgets,\n} from \"../utils/widget-helpers.js\";\n\nconst createSubcommand = new Command(\"create\")\n .description(\"Scaffold a new custom 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 widgetDir = path.join(cwd, \"src\", \"widgets\", name);\n\n // Validate name format\n const validationError = validateWidgetName(name);\n if (validationError) {\n console.error(chalk.red(`Error: ${validationError}`));\n process.exit(1);\n }\n\n // Check we're in a Fluid portal project\n if (!fs.existsSync(path.join(cwd, \"src\", \"portal.config.ts\"))) {\n console.error(chalk.red(\"Error: No src/portal.config.ts found.\"));\n console.error(\n chalk.yellow(\"Run this command from a Fluid portal project root.\"),\n );\n process.exit(1);\n }\n\n // Check widget doesn't already exist\n if (fs.existsSync(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 displayName = toDisplayName(name);\n const category = options.category ?? \"components\";\n\n // Create widget directory and write files — clean up on failure\n try {\n await fs.ensureDir(widgetDir);\n\n await fs.writeFile(\n path.join(widgetDir, \"component.tsx\"),\n `interface ${widgetType}Props {\n title?: string;\n}\n\nexport function ${componentName}({ title = \"${displayName}\" }: ${widgetType}Props) {\n return (\n <div className=\"p-4\">\n <h3 className=\"text-lg font-semibold\">{title}</h3>\n <p className=\"text-sm text-gray-500\">\n Edit this widget in src/widgets/${name}/component.tsx\n </p>\n </div>\n );\n}\n`,\n );\n\n await fs.writeFile(\n path.join(widgetDir, \"manifest.ts\"),\n `import type { WidgetManifest } from \"@fluid-app/portal-core/registries\";\nimport { ${componentName} } from \"./component\";\n\nexport const manifest: WidgetManifest = {\n manifestVersion: 1,\n type: \"${widgetType}\",\n component: ${componentName},\n displayName: \"${displayName}\",\n description: \"A custom ${displayName.toLowerCase()} widget\",\n icon: \"puzzle-piece\",\n category: \"${category}\",\n propertySchema: {\n widgetType: \"${widgetType}\",\n displayName: \"${displayName}\",\n fields: [{ key: \"title\", label: \"Title\", type: \"text\" }],\n },\n defaultProps: {\n title: \"${displayName}\",\n },\n};\n`,\n );\n\n await fs.writeFile(\n path.join(widgetDir, \"index.ts\"),\n `export { ${componentName} } from \"./component\";\nexport { manifest } from \"./manifest\";\n`,\n );\n } catch (err) {\n await fs.remove(widgetDir).catch(() => {});\n console.error(chalk.red(\"Error: Failed to scaffold widget files.\"));\n console.error(err);\n process.exit(1);\n }\n\n // Auto-register in portal.config.ts\n const configPath = path.join(cwd, \"src\", \"portal.config.ts\");\n const camelName = toCamelCase(name);\n const importLine = `import { manifest as ${camelName} } from \"./widgets/${name}\";`;\n\n const configSource = await fs.readFile(configPath, \"utf-8\");\n\n const withImport = insertImport(configSource, importLine);\n if (withImport === null) {\n console.warn(\n chalk.yellow(\n \"Warning: Could not find import statements in portal.config.ts. \" +\n \"Add the import manually:\",\n ),\n );\n console.warn(chalk.cyan(` ${importLine}`));\n }\n\n let updated = insertIntoCustomWidgets(\n withImport ?? configSource,\n camelName,\n );\n if (updated === null) {\n if (withImport === null) {\n // Neither the import nor the array could be placed — warn and bail\n // to avoid writing a broken config with an undefined identifier\n console.warn(\n chalk.yellow(\n \"Warning: Could not register widget in portal.config.ts. Add manually:\",\n ),\n );\n console.warn(chalk.cyan(` ${importLine}`));\n console.warn(chalk.cyan(` customWidgets: [..., ${camelName}]`));\n } else {\n // Import succeeded but customWidgets array doesn't exist yet — create it\n updated =\n withImport.trimEnd() +\n `\\n\\nexport const customWidgets = [${camelName}];\\n`;\n }\n }\n\n if (updated !== null) {\n await fs.writeFile(configPath, updated, \"utf-8\");\n } else if (withImport !== null) {\n await fs.writeFile(configPath, withImport, \"utf-8\");\n }\n\n console.log();\n console.log(chalk.green(\"Created widget:\") + ` src/widgets/${name}/`);\n console.log(chalk.gray(\" component.tsx — React component\"));\n console.log(chalk.gray(\" manifest.ts — WidgetManifest\"));\n console.log(chalk.gray(\" index.ts — Re-exports\"));\n console.log();\n if (updated !== null) {\n console.log(\n chalk.green(\"Registered\") + ` in ${chalk.cyan(\"src/portal.config.ts\")}`,\n );\n console.log();\n }\n console.log(chalk.yellow(\"Next steps:\"));\n console.log(\n ` 1. Edit the component in ${chalk.cyan(`src/widgets/${name}/component.tsx`)}`,\n );\n console.log(\n ` 2. Customize the manifest fields in ${chalk.cyan(`src/widgets/${name}/manifest.ts`)}`,\n );\n console.log(\n ` 3. Run ${chalk.cyan(\"fluid portal dev\")} to preview in the builder`,\n );\n });\n\nexport const widgetCommand: Command = new Command(\"widget\")\n .description(\"Manage custom 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 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 { createFetchClient, fluidOs } from \"@fluid-app/fluidos-api-client\";\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 createFetchClient({\n baseUrl: getApiBase(),\n getAuthToken: () => 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 fluidOs.createFluidOSVersion(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 fluidOs.updateFluidOSVersion(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 fluidOs.listFluidOSVersions(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 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 fluidOs.updateFluidOSVersion(client, definitionId, versionId, {\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","/**\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 { widgetCommand } from \"./commands/widget-create.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(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} 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 getCoreVersion,\n readFileSafe,\n writeFileSafe,\n createDirectorySafe,\n copyTemplateSafe,\n getSdkVersionSafe,\n FILE_SYSTEM_ERRORS,\n type FileSystemErrorCode,\n type FileSystemError,\n type TemplatePaths,\n} from \"./utils/file-system.js\";\n\nexport { promptProjectConfig } from \"./utils/prompts.js\";\n\n// Expose the standalone Command for create-portal-app\nexport { createCommand } from \"./commands/create.js\";\nexport { pullCommand } from \"./commands/pull.js\";\nexport { pushCommand } from \"./commands/push.js\";\nexport { widgetCommand } from \"./commands/widget-create.js\";\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 slugFromPath,\n type CategorizedChanges,\n type ValidationError,\n} from \"./utils/push-validation.js\";\n"],"mappings":"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"}
|