@fragno-dev/cli 0.1.2 → 0.1.3

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/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":["fragnoDatabases: FragnoDatabase<AnySchema>[]","targetModule: Record<string, unknown>","result: { schema: string; path: string }","finalOutputPath: string","targetModule: Record<string, unknown>","didMigrate: boolean","targetModule: Record<string, unknown>","fragnoDatabases: FragnoDatabase<AnySchema>[]","generateCommand: Command","migrateCommand: Command","infoCommand: Command","dbCommand: Command","mainCommand: Command"],"sources":["../src/utils/find-fragno-databases.ts","../src/commands/db/generate.ts","../src/commands/db/migrate.ts","../src/commands/db/info.ts","../src/cli.ts"],"sourcesContent":["import { isFragnoDatabase, type DatabaseAdapter, FragnoDatabase } from \"@fragno-dev/db\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport {\n instantiatedFragmentFakeSymbol,\n type FragnoInstantiatedFragment,\n} from \"@fragno-dev/core/api/fragment-instantiation\";\n\nfunction isFragnoInstantiatedFragment(\n value: unknown,\n): value is FragnoInstantiatedFragment<[], {}, {}, {}> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n instantiatedFragmentFakeSymbol in value &&\n value[instantiatedFragmentFakeSymbol] === instantiatedFragmentFakeSymbol\n );\n}\n\nfunction additionalContextIsDatabaseContext(additionalContext: unknown): additionalContext is {\n databaseSchema: AnySchema;\n databaseNamespace: string;\n databaseAdapter: DatabaseAdapter;\n} {\n return (\n typeof additionalContext === \"object\" &&\n additionalContext !== null &&\n \"databaseSchema\" in additionalContext &&\n \"databaseNamespace\" in additionalContext &&\n \"databaseAdapter\" in additionalContext\n );\n}\n\n/**\n * Finds all FragnoDatabase instances in a module, including those embedded\n * in instantiated fragments.\n */\nexport function findFragnoDatabases(\n targetModule: Record<string, unknown>,\n): FragnoDatabase<AnySchema>[] {\n const fragnoDatabases: FragnoDatabase<AnySchema>[] = [];\n\n for (const [key, value] of Object.entries(targetModule)) {\n if (isFragnoDatabase(value)) {\n fragnoDatabases.push(value);\n console.log(`Found FragnoDatabase instance: ${key}`);\n } else if (isFragnoInstantiatedFragment(value)) {\n const additionalContext = value.additionalContext;\n\n if (!additionalContext || !additionalContextIsDatabaseContext(additionalContext)) {\n console.warn(`Instantiated fragment ${key} has no database context`);\n continue;\n }\n\n // Extract database schema, namespace, and adapter from instantiated fragment's additionalContext\n const { databaseSchema, databaseNamespace, databaseAdapter } = additionalContext;\n\n fragnoDatabases.push(\n new FragnoDatabase({\n namespace: databaseNamespace,\n schema: databaseSchema,\n adapter: databaseAdapter,\n }),\n );\n console.log(`Found database context in instantiated fragment: ${key}`);\n }\n }\n\n return fragnoDatabases;\n}\n","import { writeFile, stat, mkdir } from \"node:fs/promises\";\nimport { resolve, join, dirname, basename } from \"node:path\";\nimport type { CommandContext } from \"gunshi\";\nimport { findFragnoDatabases } from \"../../utils/find-fragno-databases.js\";\n\nexport async function generate(ctx: CommandContext) {\n const target = ctx.values[\"target\"];\n const output = ctx.values[\"output\"];\n const toVersion = ctx.values[\"to\"];\n const fromVersion = ctx.values[\"from\"];\n const prefix = ctx.values[\"prefix\"];\n\n if (!target || typeof target !== \"string\") {\n throw new Error(\"Target file path is required and must be a string\");\n }\n\n if (typeof output === \"number\" || typeof output === \"boolean\") {\n throw new Error(\"Output file path is required and must be a string\");\n }\n\n if (toVersion !== undefined && typeof toVersion !== \"string\" && typeof toVersion !== \"number\") {\n throw new Error(\"Version must be a number or string\");\n }\n\n if (\n fromVersion !== undefined &&\n typeof fromVersion !== \"string\" &&\n typeof fromVersion !== \"number\"\n ) {\n throw new Error(\"Version must be a number or string\");\n }\n\n if (prefix !== undefined && typeof prefix !== \"string\") {\n throw new Error(\"Prefix must be a string\");\n }\n\n // Resolve the target file path relative to current working directory\n const targetPath = resolve(process.cwd(), target);\n\n console.log(`Loading target file: ${targetPath}`);\n\n // Dynamically import the target file\n let targetModule: Record<string, unknown>;\n try {\n targetModule = await import(targetPath);\n } catch (error) {\n throw new Error(\n `Failed to import target file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Find all FragnoDatabase instances or instantiated fragments with databases\n const fragnoDatabases = findFragnoDatabases(targetModule);\n\n if (fragnoDatabases.length === 0) {\n throw new Error(\n `No FragnoDatabase instances found in ${target}.\\n` +\n `Make sure you export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n }\n\n if (fragnoDatabases.length > 1) {\n console.warn(\n `Warning: Multiple FragnoDatabase instances found (${fragnoDatabases.length}). Using the first one.`,\n );\n }\n\n // Use the first FragnoDatabase instance\n const fragnoDb = fragnoDatabases[0];\n\n // Parse versions if provided\n const targetVersion = toVersion !== undefined ? parseInt(String(toVersion), 10) : undefined;\n const sourceVersion = fromVersion !== undefined ? parseInt(String(fromVersion), 10) : undefined;\n\n if (targetVersion !== undefined && isNaN(targetVersion)) {\n throw new Error(`Invalid version number: ${toVersion}`);\n }\n\n if (sourceVersion !== undefined && isNaN(sourceVersion)) {\n throw new Error(`Invalid version number: ${fromVersion}`);\n }\n\n if (sourceVersion !== undefined && targetVersion !== undefined) {\n console.log(\n `Generating schema for namespace: ${fragnoDb.namespace} (from version ${sourceVersion} to version ${targetVersion})`,\n );\n } else if (targetVersion !== undefined) {\n console.log(\n `Generating schema for namespace: ${fragnoDb.namespace} (to version ${targetVersion})`,\n );\n } else if (sourceVersion !== undefined) {\n console.log(\n `Generating schema for namespace: ${fragnoDb.namespace} (from version ${sourceVersion})`,\n );\n } else {\n console.log(`Generating schema for namespace: ${fragnoDb.namespace}`);\n }\n\n // Determine if output is a directory or file\n let isDirectory = false;\n\n if (output) {\n const resolvedOutput = resolve(process.cwd(), output);\n try {\n const stats = await stat(resolvedOutput);\n isDirectory = stats.isDirectory();\n } catch {\n // Path doesn't exist - check if it looks like a directory (ends with /)\n isDirectory = output.endsWith(\"/\");\n }\n }\n\n // Generate schema\n let result: { schema: string; path: string };\n try {\n // If output is a directory, pass undefined to get the default file name\n // Otherwise pass the output path as-is\n result = await fragnoDb.generateSchema({\n path: isDirectory ? undefined : output,\n toVersion: targetVersion,\n fromVersion: sourceVersion,\n });\n } catch (error) {\n throw new Error(\n `Failed to generate schema: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Resolve final output path\n let finalOutputPath: string;\n if (isDirectory && output) {\n // Combine directory with generated file name\n const resolvedDir = resolve(process.cwd(), output);\n finalOutputPath = join(resolvedDir, basename(result.path));\n } else if (output) {\n // Use the provided path as-is\n finalOutputPath = resolve(process.cwd(), output);\n } else {\n // Use the generated file name in current directory\n finalOutputPath = resolve(process.cwd(), result.path);\n }\n\n // Ensure parent directory exists\n const parentDir = dirname(finalOutputPath);\n try {\n await mkdir(parentDir, { recursive: true });\n } catch (error) {\n throw new Error(\n `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Write schema to file\n try {\n const content = prefix ? `${prefix}\\n${result.schema}` : result.schema;\n await writeFile(finalOutputPath, content, { encoding: \"utf-8\" });\n } catch (error) {\n throw new Error(\n `Failed to write schema file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n console.log(`✓ Schema generated successfully: ${finalOutputPath}`);\n console.log(` Namespace: ${fragnoDb.namespace}`);\n console.log(` Schema version: ${fragnoDb.schema.version}`);\n}\n","import { resolve } from \"node:path\";\nimport type { CommandContext } from \"gunshi\";\nimport { findFragnoDatabases } from \"../../utils/find-fragno-databases\";\n\nexport async function migrate(ctx: CommandContext) {\n const target = ctx.values[\"target\"];\n const version = ctx.values[\"to\"];\n const fromVersion = ctx.values[\"from\"];\n\n if (!target || typeof target !== \"string\") {\n throw new Error(\"Target file path is required and must be a string\");\n }\n\n if (version !== undefined && typeof version !== \"string\" && typeof version !== \"number\") {\n throw new Error(\"Version must be a number or string\");\n }\n\n if (\n fromVersion !== undefined &&\n typeof fromVersion !== \"string\" &&\n typeof fromVersion !== \"number\"\n ) {\n throw new Error(\"Version must be a number or string\");\n }\n\n // Resolve the target file path relative to current working directory\n const targetPath = resolve(process.cwd(), target);\n\n console.log(`Loading target file: ${targetPath}`);\n\n // Dynamically import the target file\n let targetModule: Record<string, unknown>;\n try {\n targetModule = await import(targetPath);\n } catch (error) {\n throw new Error(\n `Failed to import target file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Find all FragnoDatabase instances or instantiated fragments with databases\n const fragnoDatabases = findFragnoDatabases(targetModule);\n\n if (fragnoDatabases.length === 0) {\n throw new Error(\n `No FragnoDatabase instances found in ${target}.\\n` +\n `Make sure you export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n }\n\n if (fragnoDatabases.length > 1) {\n console.warn(\n `Warning: Multiple FragnoDatabase instances found (${fragnoDatabases.length}). Using the first one.`,\n );\n }\n\n // Use the first FragnoDatabase instance\n const fragnoDb = fragnoDatabases[0];\n\n console.log(`Migrating database for namespace: ${fragnoDb.namespace}`);\n\n // Check if the adapter supports migrations\n if (!fragnoDb.adapter.createMigrationEngine) {\n throw new Error(\n `Adapter does not support running migrations. The adapter only supports schema generation.\\n` +\n `Try using 'fragno db generate' instead to generate schema files.`,\n );\n }\n\n // Parse versions if provided\n const targetVersion = version !== undefined ? parseInt(String(version), 10) : undefined;\n const expectedFromVersion =\n fromVersion !== undefined ? parseInt(String(fromVersion), 10) : undefined;\n\n if (targetVersion !== undefined && isNaN(targetVersion)) {\n throw new Error(`Invalid version number: ${version}`);\n }\n\n if (expectedFromVersion !== undefined && isNaN(expectedFromVersion)) {\n throw new Error(`Invalid version number: ${fromVersion}`);\n }\n\n // Run migrations\n let didMigrate: boolean;\n try {\n if (targetVersion !== undefined) {\n console.log(`Migrating to version ${targetVersion}...`);\n const migrator = fragnoDb.adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace);\n const currentVersion = await migrator.getVersion();\n console.log(`Current version: ${currentVersion}`);\n\n // Validate from version if provided\n if (expectedFromVersion !== undefined && currentVersion !== expectedFromVersion) {\n throw new Error(\n `Current database version (${currentVersion}) does not match expected --from version (${expectedFromVersion})`,\n );\n }\n\n const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {\n updateSettings: true,\n });\n\n if (preparedMigration.operations.length === 0) {\n console.log(\"✓ Database is already at the target version. No migrations needed.\");\n didMigrate = false;\n } else {\n await preparedMigration.execute();\n didMigrate = true;\n }\n } else {\n console.log(`Migrating to latest version (${fragnoDb.schema.version})...`);\n didMigrate = await fragnoDb.runMigrations();\n }\n } catch (error) {\n throw new Error(\n `Failed to run migrations: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n if (didMigrate) {\n console.log(`✓ Migration completed successfully`);\n console.log(` Namespace: ${fragnoDb.namespace}`);\n if (targetVersion !== undefined) {\n console.log(` New version: ${targetVersion}`);\n } else {\n console.log(` New version: ${fragnoDb.schema.version}`);\n }\n }\n}\n","import { isFragnoDatabase, type FragnoDatabase } from \"@fragno-dev/db\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport { resolve } from \"node:path\";\nimport type { CommandContext } from \"gunshi\";\n\nexport async function info(ctx: CommandContext) {\n const target = ctx.values[\"target\"];\n\n if (!target || typeof target !== \"string\") {\n throw new Error(\"Target file path is required and must be a string\");\n }\n\n // Resolve the target file path relative to current working directory\n const targetPath = resolve(process.cwd(), target);\n\n console.log(`Loading target file: ${targetPath}`);\n\n // Dynamically import the target file\n let targetModule: Record<string, unknown>;\n try {\n targetModule = await import(targetPath);\n } catch (error) {\n throw new Error(\n `Failed to import target file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Find all FragnoDatabase instances in the exported values\n const fragnoDatabases: FragnoDatabase<AnySchema>[] = [];\n\n for (const [key, value] of Object.entries(targetModule)) {\n if (isFragnoDatabase(value)) {\n fragnoDatabases.push(value);\n console.log(`Found FragnoDatabase instance: ${key}`);\n }\n }\n\n if (fragnoDatabases.length === 0) {\n throw new Error(`No FragnoDatabase instances found in ${target}.\\n`);\n }\n\n if (fragnoDatabases.length > 1) {\n console.warn(\n `Warning: Multiple FragnoDatabase instances found (${fragnoDatabases.length}). Using the first one.`,\n );\n }\n\n // Use the first FragnoDatabase instance\n const fragnoDb = fragnoDatabases[0];\n\n console.log(\"\");\n console.log(\"Database Information\");\n console.log(\"=\".repeat(50));\n console.log(`Namespace: ${fragnoDb.namespace}`);\n console.log(`Latest Schema Version: ${fragnoDb.schema.version}`);\n\n // Check if the adapter supports migrations\n if (!fragnoDb.adapter.createMigrationEngine) {\n console.log(`Migration Support: No`);\n console.log(\"\");\n console.log(\"Note: This adapter does not support running migrations.\");\n console.log(\"Use 'fragno db generate' to generate schema files.\");\n return;\n }\n\n console.log(`Migration Support: Yes`);\n\n // Get current database version\n try {\n const migrator = fragnoDb.adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace);\n const currentVersion = await migrator.getVersion();\n\n console.log(`Current Database Version: ${currentVersion}`);\n\n // Check if migrations are pending\n const pendingVersions = fragnoDb.schema.version - currentVersion;\n if (pendingVersions > 0) {\n console.log(\"\");\n console.log(`⚠ Pending Migrations: ${pendingVersions} version(s) behind`);\n console.log(` Run '@fragno-dev/cli db migrate --target ${target}' to update`);\n } else if (pendingVersions === 0) {\n console.log(\"\");\n console.log(`✓ Database is up to date`);\n }\n } catch (error) {\n console.log(\"\");\n console.log(\n `Warning: Could not retrieve current version: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n console.log(\"=\".repeat(50));\n}\n","#!/usr/bin/env node\n\nimport { cli, type Command, parseArgs, resolveArgs } from \"gunshi\";\nimport { generate } from \"./commands/db/generate.js\";\nimport { migrate } from \"./commands/db/migrate.js\";\nimport { info } from \"./commands/db/info.js\";\n\n// Define the db generate command\nexport const generateCommand: Command = {\n name: \"generate\",\n description: \"Generate schema files from FragnoDatabase definitions\",\n args: {\n target: {\n type: \"positional\" as const,\n description: \"Path to the file that exports a FragnoDatabase instance\",\n },\n output: {\n type: \"string\" as const,\n short: \"o\",\n description:\n \"Output path for the generated schema file (default: schema.sql for Kysely, schema.ts for Drizzle)\",\n },\n from: {\n type: \"string\" as const,\n short: \"f\",\n description: \"Source version to generate migration from (default: current database version)\",\n },\n to: {\n type: \"string\" as const,\n short: \"t\",\n description: \"Target version to generate migration to (default: latest schema version)\",\n },\n prefix: {\n type: \"string\" as const,\n short: \"p\",\n description: \"String to prepend to the generated file (e.g., '/* eslint-disable */')\",\n },\n },\n run: generate,\n};\n\n// Define the db migrate command\nexport const migrateCommand: Command = {\n name: \"migrate\",\n description: \"Run database migrations\",\n args: {\n target: {\n type: \"positional\" as const,\n description: \"Path to the file that exports a FragnoDatabase instance\",\n },\n from: {\n type: \"string\" as const,\n short: \"f\",\n description: \"Expected current database version (validates before migrating)\",\n },\n to: {\n type: \"string\" as const,\n short: \"t\",\n description: \"Target version to migrate to (default: latest schema version)\",\n },\n },\n run: migrate,\n};\n\n// Define the db info command\nexport const infoCommand: Command = {\n name: \"info\",\n description: \"Display database information and migration status\",\n args: {\n target: {\n type: \"positional\" as const,\n description: \"Path to the file that exports a FragnoDatabase instance\",\n },\n },\n run: info,\n};\n\n// Create a Map of db sub-commands\nconst dbSubCommands = new Map();\ndbSubCommands.set(\"generate\", generateCommand);\ndbSubCommands.set(\"migrate\", migrateCommand);\ndbSubCommands.set(\"info\", infoCommand);\n\n// Helper function to print db command help\nfunction printDbHelp() {\n console.log(\"Database management commands for Fragno\");\n console.log(\"\");\n console.log(\"Usage: @fragno-dev/cli db <command> [options]\");\n console.log(\"\");\n console.log(\"Commands:\");\n console.log(\" generate Generate schema files from FragnoDatabase definitions\");\n console.log(\" migrate Run database migrations\");\n console.log(\" info Display database information and migration status\");\n console.log(\"\");\n console.log(\"Run '@fragno-dev/cli db <command> --help' for more information.\");\n}\n\n// Define the db command\nexport const dbCommand: Command = {\n name: \"db\",\n description: \"Database management commands\",\n run: printDbHelp,\n};\n\n// Create a Map of root sub-commands\nconst rootSubCommands = new Map();\nrootSubCommands.set(\"db\", dbCommand);\n\n// Define the main command\nexport const mainCommand: Command = {\n name: \"@fragno-dev/cli\",\n description: \"Fragno CLI - Tools for working with Fragno fragments\",\n run: () => {\n console.log(\"Fragno CLI - Tools for working with Fragno fragments\");\n console.log(\"\");\n console.log(\"Usage: @fragno-dev/cli <command> [options]\");\n console.log(\"\");\n console.log(\"Commands:\");\n console.log(\" db Database management commands\");\n console.log(\"\");\n console.log(\"Run '@fragno-dev/cli <command> --help' for more information.\");\n },\n};\n\nif (import.meta.main) {\n try {\n // Parse arguments to handle nested subcommands\n const args = process.argv.slice(2);\n\n // Check if we're calling a db subcommand directly\n if (args[0] === \"db\" && args.length > 1) {\n const subCommandName = args[1];\n\n // Check if it's a help request\n if (subCommandName === \"--help\" || subCommandName === \"-h\") {\n printDbHelp();\n process.exit(0);\n }\n\n const subCommand = dbSubCommands.get(subCommandName);\n\n if (!subCommand) {\n console.error(`Unknown command: ${subCommandName}`);\n console.log(\"\");\n printDbHelp();\n process.exit(1);\n }\n\n // Run the specific subcommand with its args\n const subArgs = args.slice(2);\n const isSubCommandHelp = subArgs.includes(\"--help\") || subArgs.includes(\"-h\");\n\n // Check for validation errors before running\n let hasValidationError = false;\n if (!isSubCommandHelp && subCommand.args) {\n const tokens = parseArgs(subArgs);\n const resolved = resolveArgs(subCommand.args, tokens);\n hasValidationError = !!resolved.error;\n }\n\n // Run the command (let gunshi handle printing errors/help)\n await cli(subArgs, subCommand);\n\n // Exit with error code if there was a validation error\n if (hasValidationError) {\n process.exit(1);\n }\n } else if (args[0] === \"db\") {\n // \"db\" command with no subcommand - show db help\n printDbHelp();\n } else {\n // Run the main CLI\n await cli(args, mainCommand, {\n subCommands: rootSubCommands,\n });\n }\n } catch (error) {\n console.error(\"Error:\", error instanceof Error ? error.message : error);\n process.exit(1);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":["fragnoDatabases: FragnoDatabase<AnySchema>[]","allFragnoDatabases: FragnoDatabase<AnySchema>[]","targetModule: Record<string, unknown>","results: { schema: string; path: string; namespace: string }[]","allFragnoDatabases: FragnoDatabase<AnySchema>[]","targetModule: Record<string, unknown>","results: ExecuteMigrationResult[]","allFragnoDatabases: FragnoDatabase<AnySchema>[]","targetModule: Record<string, unknown>","info: {\n namespace: string;\n schemaVersion: number;\n migrationSupport: boolean;\n currentVersion?: number;\n pendingVersions?: number;\n status?: string;\n error?: string;\n }"],"sources":["../src/utils/find-fragno-databases.ts","../src/commands/db/generate.ts","../src/commands/db/migrate.ts","../src/commands/db/info.ts","../src/cli.ts"],"sourcesContent":["import { isFragnoDatabase, type DatabaseAdapter, FragnoDatabase } from \"@fragno-dev/db\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport {\n instantiatedFragmentFakeSymbol,\n type FragnoInstantiatedFragment,\n} from \"@fragno-dev/core/api/fragment-instantiation\";\n\nfunction isFragnoInstantiatedFragment(\n value: unknown,\n): value is FragnoInstantiatedFragment<[], {}, {}, {}> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n instantiatedFragmentFakeSymbol in value &&\n value[instantiatedFragmentFakeSymbol] === instantiatedFragmentFakeSymbol\n );\n}\n\nfunction additionalContextIsDatabaseContext(additionalContext: unknown): additionalContext is {\n databaseSchema: AnySchema;\n databaseNamespace: string;\n databaseAdapter: DatabaseAdapter;\n} {\n return (\n typeof additionalContext === \"object\" &&\n additionalContext !== null &&\n \"databaseSchema\" in additionalContext &&\n \"databaseNamespace\" in additionalContext &&\n \"databaseAdapter\" in additionalContext\n );\n}\n\n/**\n * Finds all FragnoDatabase instances in a module, including those embedded\n * in instantiated fragments.\n */\nexport function findFragnoDatabases(\n targetModule: Record<string, unknown>,\n): FragnoDatabase<AnySchema>[] {\n const fragnoDatabases: FragnoDatabase<AnySchema>[] = [];\n\n for (const [key, value] of Object.entries(targetModule)) {\n if (isFragnoDatabase(value)) {\n fragnoDatabases.push(value);\n console.log(`Found FragnoDatabase instance: ${key}`);\n } else if (isFragnoInstantiatedFragment(value)) {\n const additionalContext = value.additionalContext;\n\n if (!additionalContext || !additionalContextIsDatabaseContext(additionalContext)) {\n console.warn(`Instantiated fragment ${key} has no database context`);\n continue;\n }\n\n // Extract database schema, namespace, and adapter from instantiated fragment's additionalContext\n const { databaseSchema, databaseNamespace, databaseAdapter } = additionalContext;\n\n fragnoDatabases.push(\n new FragnoDatabase({\n namespace: databaseNamespace,\n schema: databaseSchema,\n adapter: databaseAdapter,\n }),\n );\n console.log(`Found database context in instantiated fragment: ${key}`);\n }\n }\n\n return fragnoDatabases;\n}\n","import { writeFile, mkdir } from \"node:fs/promises\";\nimport { resolve, dirname } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { findFragnoDatabases } from \"../../utils/find-fragno-databases\";\nimport type { FragnoDatabase } from \"@fragno-dev/db\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport { generateMigrationsOrSchema } from \"@fragno-dev/db/generation-engine\";\n\n// Define the db generate command with type safety\nexport const generateCommand = define({\n name: \"generate\",\n description: \"Generate schema files from FragnoDatabase definitions\",\n args: {\n output: {\n type: \"string\",\n short: \"o\",\n description:\n \"Output path: for single file, exact file path; for multiple files, output directory (default: current directory)\",\n },\n from: {\n type: \"number\",\n short: \"f\",\n description: \"Source version to generate migration from (default: current database version)\",\n },\n to: {\n type: \"number\",\n short: \"t\",\n description: \"Target version to generate migration to (default: latest schema version)\",\n },\n prefix: {\n type: \"string\",\n short: \"p\",\n description: \"String to prepend to the generated file (e.g., '/* eslint-disable */')\",\n },\n },\n run: async (ctx) => {\n // With `define()` and `multiple: true`, targets is properly typed as string[]\n const targets = ctx.positionals;\n const output = ctx.values.output;\n const toVersion = ctx.values.to;\n const fromVersion = ctx.values.from;\n const prefix = ctx.values.prefix;\n\n // De-duplicate targets (in case same file was specified multiple times)\n const uniqueTargets = Array.from(new Set(targets));\n\n // Load all target files and collect FragnoDatabase instances\n const allFragnoDatabases: FragnoDatabase<AnySchema>[] = [];\n\n for (const target of uniqueTargets) {\n const targetPath = resolve(process.cwd(), target);\n console.log(`Loading target file: ${targetPath}`);\n\n // Dynamically import the target file\n let targetModule: Record<string, unknown>;\n try {\n targetModule = await import(targetPath);\n } catch (error) {\n throw new Error(\n `Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Find all FragnoDatabase instances or instantiated fragments with databases\n const fragnoDatabases = findFragnoDatabases(targetModule);\n\n if (fragnoDatabases.length === 0) {\n console.warn(\n `Warning: No FragnoDatabase instances found in ${target}.\\n` +\n `Make sure you export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n continue;\n }\n\n if (fragnoDatabases.length > 1) {\n console.warn(\n `Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Using all of them.`,\n );\n }\n\n allFragnoDatabases.push(...fragnoDatabases);\n }\n\n if (allFragnoDatabases.length === 0) {\n throw new Error(\n `No FragnoDatabase instances found in any of the target files.\\n` +\n `Make sure your files export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n }\n\n console.log(\n `Found ${allFragnoDatabases.length} FragnoDatabase instance(s) across ${uniqueTargets.length} file(s)`,\n );\n\n // Validate all databases use the same adapter object (identity)\n const firstDb = allFragnoDatabases[0];\n const firstAdapter = firstDb.adapter;\n const allSameAdapter = allFragnoDatabases.every((db) => db.adapter === firstAdapter);\n\n if (!allSameAdapter) {\n throw new Error(\n \"All fragments must use the same database adapter instance. Mixed adapters are not supported.\",\n );\n }\n\n // Check if adapter supports any form of schema generation\n if (!firstDb.adapter.createSchemaGenerator && !firstDb.adapter.createMigrationEngine) {\n throw new Error(\n `The adapter does not support schema generation. ` +\n `Please use an adapter that implements either createSchemaGenerator or createMigrationEngine.`,\n );\n }\n\n // Generate schema for all fragments\n console.log(\"Generating schema...\");\n\n let results: { schema: string; path: string; namespace: string }[];\n try {\n results = await generateMigrationsOrSchema(allFragnoDatabases, {\n path: output,\n toVersion,\n fromVersion,\n });\n } catch (error) {\n throw new Error(\n `Failed to generate schema: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Write all generated files\n for (const result of results) {\n // For single file: use output as exact file path\n // For multiple files: use output as base directory\n const finalOutputPath =\n output && results.length === 1\n ? resolve(process.cwd(), output)\n : output\n ? resolve(process.cwd(), output, result.path)\n : resolve(process.cwd(), result.path);\n\n // Ensure parent directory exists\n const parentDir = dirname(finalOutputPath);\n try {\n await mkdir(parentDir, { recursive: true });\n } catch (error) {\n throw new Error(\n `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Write schema to file\n try {\n const content = prefix ? `${prefix}\\n${result.schema}` : result.schema;\n await writeFile(finalOutputPath, content, { encoding: \"utf-8\" });\n } catch (error) {\n throw new Error(\n `Failed to write schema file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n console.log(`✓ Generated: ${finalOutputPath}`);\n }\n\n console.log(`\\n✓ Schema generated successfully!`);\n console.log(` Files generated: ${results.length}`);\n console.log(` Fragments:`);\n for (const db of allFragnoDatabases) {\n console.log(` - ${db.namespace} (version ${db.schema.version})`);\n }\n },\n});\n","import { resolve } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { findFragnoDatabases } from \"../../utils/find-fragno-databases\";\nimport { type FragnoDatabase } from \"@fragno-dev/db\";\nimport { executeMigrations, type ExecuteMigrationResult } from \"@fragno-dev/db/generation-engine\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\n\nexport const migrateCommand = define({\n name: \"migrate\",\n description: \"Run database migrations for all fragments to their latest versions\",\n args: {},\n run: async (ctx) => {\n const targets = ctx.positionals;\n\n if (targets.length === 0) {\n throw new Error(\"At least one target file path is required\");\n }\n\n // De-duplicate targets\n const uniqueTargets = Array.from(new Set(targets));\n\n // Load all target files and collect FragnoDatabase instances\n const allFragnoDatabases: FragnoDatabase<AnySchema>[] = [];\n\n for (const target of uniqueTargets) {\n const targetPath = resolve(process.cwd(), target);\n console.log(`Loading target file: ${targetPath}`);\n\n // Dynamically import the target file\n let targetModule: Record<string, unknown>;\n try {\n targetModule = await import(targetPath);\n } catch (error) {\n throw new Error(\n `Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Find all FragnoDatabase instances or instantiated fragments with databases\n const fragnoDatabases = findFragnoDatabases(targetModule);\n\n if (fragnoDatabases.length === 0) {\n console.warn(\n `Warning: No FragnoDatabase instances found in ${target}.\\n` +\n `Make sure you export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n continue;\n }\n\n if (fragnoDatabases.length > 1) {\n console.warn(\n `Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Using all of them.`,\n );\n }\n\n allFragnoDatabases.push(...fragnoDatabases);\n }\n\n if (allFragnoDatabases.length === 0) {\n throw new Error(\n `No FragnoDatabase instances found in any of the target files.\\n` +\n `Make sure your files export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n }\n\n console.log(\n `Found ${allFragnoDatabases.length} FragnoDatabase instance(s) across ${uniqueTargets.length} file(s)`,\n );\n\n // Validate all databases use the same adapter object (identity)\n const firstDb = allFragnoDatabases[0];\n const firstAdapter = firstDb.adapter;\n const allSameAdapter = allFragnoDatabases.every((db) => db.adapter === firstAdapter);\n\n if (!allSameAdapter) {\n throw new Error(\n \"All fragments must use the same database adapter instance. Mixed adapters are not supported.\",\n );\n }\n\n console.log(\"\\nMigrating all fragments to their latest versions...\\n\");\n\n let results: ExecuteMigrationResult[];\n try {\n results = await executeMigrations(allFragnoDatabases);\n } catch (error) {\n throw new Error(\n `Migration failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Display progress for each result\n for (const result of results) {\n console.log(`Fragment: ${result.namespace}`);\n console.log(` Current version: ${result.fromVersion}`);\n console.log(` Target version: ${result.toVersion}`);\n\n if (result.didMigrate) {\n console.log(` ✓ Migration completed: v${result.fromVersion} → v${result.toVersion}\\n`);\n } else {\n console.log(` ✓ Already at latest version. No migration needed.\\n`);\n }\n }\n\n // Summary\n console.log(\"═══════════════════════════════════════\");\n console.log(\"Migration Summary\");\n console.log(\"═══════════════════════════════════════\");\n\n const migrated = results.filter((r) => r.didMigrate);\n const skipped = results.filter((r) => !r.didMigrate);\n\n if (migrated.length > 0) {\n console.log(`\\n✓ Migrated ${migrated.length} fragment(s):`);\n for (const r of migrated) {\n console.log(` - ${r.namespace}: v${r.fromVersion} → v${r.toVersion}`);\n }\n }\n\n if (skipped.length > 0) {\n console.log(`\\n○ Skipped ${skipped.length} fragment(s) (already up-to-date):`);\n for (const r of skipped) {\n console.log(` - ${r.namespace}: v${r.toVersion}`);\n }\n }\n\n console.log(\"\\n✓ All migrations completed successfully\");\n },\n});\n","import { resolve } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { findFragnoDatabases } from \"../../utils/find-fragno-databases\";\nimport type { FragnoDatabase } from \"@fragno-dev/db\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\n\nexport const infoCommand = define({\n name: \"info\",\n description: \"Display database information and migration status\",\n args: {},\n run: async (ctx) => {\n const targets = ctx.positionals;\n\n if (targets.length === 0) {\n throw new Error(\"At least one target file path is required\");\n }\n\n // De-duplicate targets (in case same file was specified multiple times)\n const uniqueTargets = Array.from(new Set(targets));\n\n // Load all target files and collect FragnoDatabase instances\n const allFragnoDatabases: FragnoDatabase<AnySchema>[] = [];\n\n for (const target of uniqueTargets) {\n const targetPath = resolve(process.cwd(), target);\n console.log(`Loading target file: ${targetPath}`);\n\n // Dynamically import the target file\n let targetModule: Record<string, unknown>;\n try {\n targetModule = await import(targetPath);\n } catch (error) {\n throw new Error(\n `Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Find all FragnoDatabase instances or instantiated fragments with databases\n const fragnoDatabases = findFragnoDatabases(targetModule);\n\n if (fragnoDatabases.length === 0) {\n console.warn(\n `Warning: No FragnoDatabase instances found in ${target}.\\n` +\n `Make sure you export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n continue;\n }\n\n if (fragnoDatabases.length > 1) {\n console.warn(\n `Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Showing info for all of them.`,\n );\n }\n\n allFragnoDatabases.push(...fragnoDatabases);\n }\n\n if (allFragnoDatabases.length === 0) {\n throw new Error(\n `No FragnoDatabase instances found in any of the target files.\\n` +\n `Make sure your files export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n }\n\n // Collect database information\n const dbInfos = await Promise.all(\n allFragnoDatabases.map(async (fragnoDb) => {\n const info: {\n namespace: string;\n schemaVersion: number;\n migrationSupport: boolean;\n currentVersion?: number;\n pendingVersions?: number;\n status?: string;\n error?: string;\n } = {\n namespace: fragnoDb.namespace,\n schemaVersion: fragnoDb.schema.version,\n migrationSupport: !!fragnoDb.adapter.createMigrationEngine,\n };\n\n // Get current database version if migrations are supported\n if (fragnoDb.adapter.createMigrationEngine) {\n try {\n const migrator = fragnoDb.adapter.createMigrationEngine(\n fragnoDb.schema,\n fragnoDb.namespace,\n );\n const currentVersion = await migrator.getVersion();\n info.currentVersion = currentVersion;\n info.pendingVersions = fragnoDb.schema.version - currentVersion;\n\n if (info.pendingVersions > 0) {\n info.status = `Pending (${info.pendingVersions} migration(s))`;\n } else if (info.pendingVersions === 0) {\n info.status = \"Up to date\";\n }\n } catch (error) {\n info.error = error instanceof Error ? error.message : String(error);\n info.status = \"Error\";\n }\n } else {\n info.status = \"Schema only\";\n }\n\n return info;\n }),\n );\n\n // Determine if any database supports migrations\n const hasMigrationSupport = dbInfos.some((info) => info.migrationSupport);\n\n // Print compact table\n console.log(\"\");\n console.log(\n `Found ${allFragnoDatabases.length} database(s) across ${uniqueTargets.length} file(s):`,\n );\n console.log(\"\");\n\n // Table header\n const namespaceHeader = \"Namespace\";\n const versionHeader = \"Schema\";\n const currentHeader = \"Current\";\n const statusHeader = \"Status\";\n\n const maxNamespaceLen = Math.max(\n namespaceHeader.length,\n ...dbInfos.map((info) => info.namespace.length),\n );\n const namespaceWidth = Math.max(maxNamespaceLen + 2, 20);\n const versionWidth = 8;\n const currentWidth = 9;\n const statusWidth = 25;\n\n // Print table\n console.log(\n namespaceHeader.padEnd(namespaceWidth) +\n versionHeader.padEnd(versionWidth) +\n (hasMigrationSupport ? currentHeader.padEnd(currentWidth) : \"\") +\n statusHeader,\n );\n console.log(\n \"-\".repeat(namespaceWidth) +\n \"-\".repeat(versionWidth) +\n (hasMigrationSupport ? \"-\".repeat(currentWidth) : \"\") +\n \"-\".repeat(statusWidth),\n );\n\n for (const info of dbInfos) {\n const currentVersionStr =\n info.currentVersion !== undefined ? String(info.currentVersion) : \"-\";\n console.log(\n info.namespace.padEnd(namespaceWidth) +\n String(info.schemaVersion).padEnd(versionWidth) +\n (hasMigrationSupport ? currentVersionStr.padEnd(currentWidth) : \"\") +\n (info.status || \"-\"),\n );\n }\n\n // Print help text\n console.log(\"\");\n if (!hasMigrationSupport) {\n console.log(\"Note: These adapters do not support migrations.\");\n console.log(\"Use '@fragno-dev/cli db generate' to generate schema files.\");\n } else {\n const hasPendingMigrations = dbInfos.some(\n (info) => info.pendingVersions && info.pendingVersions > 0,\n );\n if (hasPendingMigrations) {\n console.log(\"Run '@fragno-dev/cli db migrate <target>' to apply pending migrations.\");\n }\n }\n },\n});\n","#!/usr/bin/env node\n\nimport { cli, define, parseArgs, resolveArgs } from \"gunshi\";\nimport { generateCommand } from \"./commands/db/generate.js\";\nimport { migrateCommand } from \"./commands/db/migrate.js\";\nimport { infoCommand } from \"./commands/db/info.js\";\n\n// Create a Map of db sub-commands\nconst dbSubCommands = new Map();\ndbSubCommands.set(\"generate\", generateCommand);\ndbSubCommands.set(\"migrate\", migrateCommand);\ndbSubCommands.set(\"info\", infoCommand);\n\n// Helper function to print db command help\nfunction printDbHelp() {\n console.log(\"Database management commands for Fragno\");\n console.log(\"\");\n console.log(\"Usage: @fragno-dev/cli db <command> [options]\");\n console.log(\"\");\n console.log(\"Commands:\");\n console.log(\" generate Generate schema files from FragnoDatabase definitions\");\n console.log(\" migrate Run database migrations\");\n console.log(\" info Display database information and migration status\");\n console.log(\"\");\n console.log(\"Run '@fragno-dev/cli db <command> --help' for more information.\");\n}\n\n// Define the db command with type safety\nexport const dbCommand = define({\n name: \"db\",\n description: \"Database management commands\",\n run: printDbHelp,\n});\n\n// Create a Map of root sub-commands\nconst rootSubCommands = new Map();\nrootSubCommands.set(\"db\", dbCommand);\n\n// Define the main command with type safety\nexport const mainCommand = define({\n name: \"@fragno-dev/cli\",\n description: \"Fragno CLI - Tools for working with Fragno fragments\",\n run: () => {\n console.log(\"Fragno CLI - Tools for working with Fragno fragments\");\n console.log(\"\");\n console.log(\"Usage: @fragno-dev/cli <command> [options]\");\n console.log(\"\");\n console.log(\"Commands:\");\n console.log(\" db Database management commands\");\n console.log(\"\");\n console.log(\"Run '@fragno-dev/cli <command> --help' for more information.\");\n },\n});\n\nif (import.meta.main) {\n try {\n // Parse arguments to handle nested subcommands\n const args = process.argv.slice(2);\n\n // Check if we're calling a db subcommand directly\n if (args[0] === \"db\" && args.length > 1) {\n const subCommandName = args[1];\n\n // Check if it's a help request\n if (subCommandName === \"--help\" || subCommandName === \"-h\") {\n printDbHelp();\n process.exit(0);\n }\n\n const subCommand = dbSubCommands.get(subCommandName);\n\n if (!subCommand) {\n console.error(`Unknown command: ${subCommandName}`);\n console.log(\"\");\n printDbHelp();\n process.exit(1);\n }\n\n // Run the specific subcommand with its args\n const subArgs = args.slice(2);\n const isSubCommandHelp = subArgs.includes(\"--help\") || subArgs.includes(\"-h\");\n\n // Check for validation errors before running\n let hasValidationError = false;\n if (!isSubCommandHelp && subCommand.args) {\n const tokens = parseArgs(subArgs);\n const resolved = resolveArgs(subCommand.args, tokens);\n hasValidationError = !!resolved.error;\n }\n\n // Run the command (let gunshi handle printing errors/help)\n await cli(subArgs, subCommand);\n\n // Exit with error code if there was a validation error\n if (hasValidationError) {\n process.exit(1);\n }\n } else if (args[0] === \"db\") {\n // \"db\" command with no subcommand - show db help\n printDbHelp();\n } else {\n // Run the main CLI\n await cli(args, mainCommand, {\n subCommands: rootSubCommands,\n });\n }\n } catch (error) {\n console.error(\"Error:\", error instanceof Error ? error.message : error);\n process.exit(1);\n }\n}\n\nexport { generateCommand, migrateCommand, infoCommand };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragno-dev/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "exports": {
5
5
  ".": {
6
6
  "bun": "./src/cli.ts",
@@ -29,8 +29,8 @@
29
29
  "dependencies": {
30
30
  "@clack/prompts": "^0.11.0",
31
31
  "gunshi": "^0.26.3",
32
- "@fragno-dev/core": "0.1.1",
33
- "@fragno-dev/db": "0.1.1"
32
+ "@fragno-dev/core": "0.1.2",
33
+ "@fragno-dev/db": "0.1.2"
34
34
  },
35
35
  "main": "./dist/cli.js",
36
36
  "module": "./dist/cli.js",
package/src/cli.ts CHANGED
@@ -1,79 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { cli, type Command, parseArgs, resolveArgs } from "gunshi";
4
- import { generate } from "./commands/db/generate.js";
5
- import { migrate } from "./commands/db/migrate.js";
6
- import { info } from "./commands/db/info.js";
7
-
8
- // Define the db generate command
9
- export const generateCommand: Command = {
10
- name: "generate",
11
- description: "Generate schema files from FragnoDatabase definitions",
12
- args: {
13
- target: {
14
- type: "positional" as const,
15
- description: "Path to the file that exports a FragnoDatabase instance",
16
- },
17
- output: {
18
- type: "string" as const,
19
- short: "o",
20
- description:
21
- "Output path for the generated schema file (default: schema.sql for Kysely, schema.ts for Drizzle)",
22
- },
23
- from: {
24
- type: "string" as const,
25
- short: "f",
26
- description: "Source version to generate migration from (default: current database version)",
27
- },
28
- to: {
29
- type: "string" as const,
30
- short: "t",
31
- description: "Target version to generate migration to (default: latest schema version)",
32
- },
33
- prefix: {
34
- type: "string" as const,
35
- short: "p",
36
- description: "String to prepend to the generated file (e.g., '/* eslint-disable */')",
37
- },
38
- },
39
- run: generate,
40
- };
41
-
42
- // Define the db migrate command
43
- export const migrateCommand: Command = {
44
- name: "migrate",
45
- description: "Run database migrations",
46
- args: {
47
- target: {
48
- type: "positional" as const,
49
- description: "Path to the file that exports a FragnoDatabase instance",
50
- },
51
- from: {
52
- type: "string" as const,
53
- short: "f",
54
- description: "Expected current database version (validates before migrating)",
55
- },
56
- to: {
57
- type: "string" as const,
58
- short: "t",
59
- description: "Target version to migrate to (default: latest schema version)",
60
- },
61
- },
62
- run: migrate,
63
- };
64
-
65
- // Define the db info command
66
- export const infoCommand: Command = {
67
- name: "info",
68
- description: "Display database information and migration status",
69
- args: {
70
- target: {
71
- type: "positional" as const,
72
- description: "Path to the file that exports a FragnoDatabase instance",
73
- },
74
- },
75
- run: info,
76
- };
3
+ import { cli, define, parseArgs, resolveArgs } from "gunshi";
4
+ import { generateCommand } from "./commands/db/generate.js";
5
+ import { migrateCommand } from "./commands/db/migrate.js";
6
+ import { infoCommand } from "./commands/db/info.js";
77
7
 
78
8
  // Create a Map of db sub-commands
79
9
  const dbSubCommands = new Map();
@@ -95,19 +25,19 @@ function printDbHelp() {
95
25
  console.log("Run '@fragno-dev/cli db <command> --help' for more information.");
96
26
  }
97
27
 
98
- // Define the db command
99
- export const dbCommand: Command = {
28
+ // Define the db command with type safety
29
+ export const dbCommand = define({
100
30
  name: "db",
101
31
  description: "Database management commands",
102
32
  run: printDbHelp,
103
- };
33
+ });
104
34
 
105
35
  // Create a Map of root sub-commands
106
36
  const rootSubCommands = new Map();
107
37
  rootSubCommands.set("db", dbCommand);
108
38
 
109
- // Define the main command
110
- export const mainCommand: Command = {
39
+ // Define the main command with type safety
40
+ export const mainCommand = define({
111
41
  name: "@fragno-dev/cli",
112
42
  description: "Fragno CLI - Tools for working with Fragno fragments",
113
43
  run: () => {
@@ -120,7 +50,7 @@ export const mainCommand: Command = {
120
50
  console.log("");
121
51
  console.log("Run '@fragno-dev/cli <command> --help' for more information.");
122
52
  },
123
- };
53
+ });
124
54
 
125
55
  if (import.meta.main) {
126
56
  try {
@@ -179,3 +109,5 @@ if (import.meta.main) {
179
109
  process.exit(1);
180
110
  }
181
111
  }
112
+
113
+ export { generateCommand, migrateCommand, infoCommand };
@@ -1,168 +1,175 @@
1
- import { writeFile, stat, mkdir } from "node:fs/promises";
2
- import { resolve, join, dirname, basename } from "node:path";
3
- import type { CommandContext } from "gunshi";
4
- import { findFragnoDatabases } from "../../utils/find-fragno-databases.js";
5
-
6
- export async function generate(ctx: CommandContext) {
7
- const target = ctx.values["target"];
8
- const output = ctx.values["output"];
9
- const toVersion = ctx.values["to"];
10
- const fromVersion = ctx.values["from"];
11
- const prefix = ctx.values["prefix"];
12
-
13
- if (!target || typeof target !== "string") {
14
- throw new Error("Target file path is required and must be a string");
15
- }
16
-
17
- if (typeof output === "number" || typeof output === "boolean") {
18
- throw new Error("Output file path is required and must be a string");
19
- }
20
-
21
- if (toVersion !== undefined && typeof toVersion !== "string" && typeof toVersion !== "number") {
22
- throw new Error("Version must be a number or string");
23
- }
24
-
25
- if (
26
- fromVersion !== undefined &&
27
- typeof fromVersion !== "string" &&
28
- typeof fromVersion !== "number"
29
- ) {
30
- throw new Error("Version must be a number or string");
31
- }
32
-
33
- if (prefix !== undefined && typeof prefix !== "string") {
34
- throw new Error("Prefix must be a string");
35
- }
36
-
37
- // Resolve the target file path relative to current working directory
38
- const targetPath = resolve(process.cwd(), target);
39
-
40
- console.log(`Loading target file: ${targetPath}`);
41
-
42
- // Dynamically import the target file
43
- let targetModule: Record<string, unknown>;
44
- try {
45
- targetModule = await import(targetPath);
46
- } catch (error) {
47
- throw new Error(
48
- `Failed to import target file: ${error instanceof Error ? error.message : String(error)}`,
49
- );
50
- }
51
-
52
- // Find all FragnoDatabase instances or instantiated fragments with databases
53
- const fragnoDatabases = findFragnoDatabases(targetModule);
1
+ import { writeFile, mkdir } from "node:fs/promises";
2
+ import { resolve, dirname } from "node:path";
3
+ import { define } from "gunshi";
4
+ import { findFragnoDatabases } from "../../utils/find-fragno-databases";
5
+ import type { FragnoDatabase } from "@fragno-dev/db";
6
+ import type { AnySchema } from "@fragno-dev/db/schema";
7
+ import { generateMigrationsOrSchema } from "@fragno-dev/db/generation-engine";
8
+
9
+ // Define the db generate command with type safety
10
+ export const generateCommand = define({
11
+ name: "generate",
12
+ description: "Generate schema files from FragnoDatabase definitions",
13
+ args: {
14
+ output: {
15
+ type: "string",
16
+ short: "o",
17
+ description:
18
+ "Output path: for single file, exact file path; for multiple files, output directory (default: current directory)",
19
+ },
20
+ from: {
21
+ type: "number",
22
+ short: "f",
23
+ description: "Source version to generate migration from (default: current database version)",
24
+ },
25
+ to: {
26
+ type: "number",
27
+ short: "t",
28
+ description: "Target version to generate migration to (default: latest schema version)",
29
+ },
30
+ prefix: {
31
+ type: "string",
32
+ short: "p",
33
+ description: "String to prepend to the generated file (e.g., '/* eslint-disable */')",
34
+ },
35
+ },
36
+ run: async (ctx) => {
37
+ // With `define()` and `multiple: true`, targets is properly typed as string[]
38
+ const targets = ctx.positionals;
39
+ const output = ctx.values.output;
40
+ const toVersion = ctx.values.to;
41
+ const fromVersion = ctx.values.from;
42
+ const prefix = ctx.values.prefix;
43
+
44
+ // De-duplicate targets (in case same file was specified multiple times)
45
+ const uniqueTargets = Array.from(new Set(targets));
46
+
47
+ // Load all target files and collect FragnoDatabase instances
48
+ const allFragnoDatabases: FragnoDatabase<AnySchema>[] = [];
49
+
50
+ for (const target of uniqueTargets) {
51
+ const targetPath = resolve(process.cwd(), target);
52
+ console.log(`Loading target file: ${targetPath}`);
53
+
54
+ // Dynamically import the target file
55
+ let targetModule: Record<string, unknown>;
56
+ try {
57
+ targetModule = await import(targetPath);
58
+ } catch (error) {
59
+ throw new Error(
60
+ `Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`,
61
+ );
62
+ }
63
+
64
+ // Find all FragnoDatabase instances or instantiated fragments with databases
65
+ const fragnoDatabases = findFragnoDatabases(targetModule);
66
+
67
+ if (fragnoDatabases.length === 0) {
68
+ console.warn(
69
+ `Warning: No FragnoDatabase instances found in ${target}.\n` +
70
+ `Make sure you export either:\n` +
71
+ ` - A FragnoDatabase instance created with .create(adapter)\n` +
72
+ ` - An instantiated fragment with embedded database definition\n`,
73
+ );
74
+ continue;
75
+ }
76
+
77
+ if (fragnoDatabases.length > 1) {
78
+ console.warn(
79
+ `Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Using all of them.`,
80
+ );
81
+ }
82
+
83
+ allFragnoDatabases.push(...fragnoDatabases);
84
+ }
54
85
 
55
- if (fragnoDatabases.length === 0) {
56
- throw new Error(
57
- `No FragnoDatabase instances found in ${target}.\n` +
58
- `Make sure you export either:\n` +
59
- ` - A FragnoDatabase instance created with .create(adapter)\n` +
60
- ` - An instantiated fragment with embedded database definition\n`,
61
- );
62
- }
86
+ if (allFragnoDatabases.length === 0) {
87
+ throw new Error(
88
+ `No FragnoDatabase instances found in any of the target files.\n` +
89
+ `Make sure your files export either:\n` +
90
+ ` - A FragnoDatabase instance created with .create(adapter)\n` +
91
+ ` - An instantiated fragment with embedded database definition\n`,
92
+ );
93
+ }
63
94
 
64
- if (fragnoDatabases.length > 1) {
65
- console.warn(
66
- `Warning: Multiple FragnoDatabase instances found (${fragnoDatabases.length}). Using the first one.`,
95
+ console.log(
96
+ `Found ${allFragnoDatabases.length} FragnoDatabase instance(s) across ${uniqueTargets.length} file(s)`,
67
97
  );
68
- }
69
98
 
70
- // Use the first FragnoDatabase instance
71
- const fragnoDb = fragnoDatabases[0];
99
+ // Validate all databases use the same adapter object (identity)
100
+ const firstDb = allFragnoDatabases[0];
101
+ const firstAdapter = firstDb.adapter;
102
+ const allSameAdapter = allFragnoDatabases.every((db) => db.adapter === firstAdapter);
72
103
 
73
- // Parse versions if provided
74
- const targetVersion = toVersion !== undefined ? parseInt(String(toVersion), 10) : undefined;
75
- const sourceVersion = fromVersion !== undefined ? parseInt(String(fromVersion), 10) : undefined;
76
-
77
- if (targetVersion !== undefined && isNaN(targetVersion)) {
78
- throw new Error(`Invalid version number: ${toVersion}`);
79
- }
80
-
81
- if (sourceVersion !== undefined && isNaN(sourceVersion)) {
82
- throw new Error(`Invalid version number: ${fromVersion}`);
83
- }
104
+ if (!allSameAdapter) {
105
+ throw new Error(
106
+ "All fragments must use the same database adapter instance. Mixed adapters are not supported.",
107
+ );
108
+ }
84
109
 
85
- if (sourceVersion !== undefined && targetVersion !== undefined) {
86
- console.log(
87
- `Generating schema for namespace: ${fragnoDb.namespace} (from version ${sourceVersion} to version ${targetVersion})`,
88
- );
89
- } else if (targetVersion !== undefined) {
90
- console.log(
91
- `Generating schema for namespace: ${fragnoDb.namespace} (to version ${targetVersion})`,
92
- );
93
- } else if (sourceVersion !== undefined) {
94
- console.log(
95
- `Generating schema for namespace: ${fragnoDb.namespace} (from version ${sourceVersion})`,
96
- );
97
- } else {
98
- console.log(`Generating schema for namespace: ${fragnoDb.namespace}`);
99
- }
110
+ // Check if adapter supports any form of schema generation
111
+ if (!firstDb.adapter.createSchemaGenerator && !firstDb.adapter.createMigrationEngine) {
112
+ throw new Error(
113
+ `The adapter does not support schema generation. ` +
114
+ `Please use an adapter that implements either createSchemaGenerator or createMigrationEngine.`,
115
+ );
116
+ }
100
117
 
101
- // Determine if output is a directory or file
102
- let isDirectory = false;
118
+ // Generate schema for all fragments
119
+ console.log("Generating schema...");
103
120
 
104
- if (output) {
105
- const resolvedOutput = resolve(process.cwd(), output);
121
+ let results: { schema: string; path: string; namespace: string }[];
106
122
  try {
107
- const stats = await stat(resolvedOutput);
108
- isDirectory = stats.isDirectory();
109
- } catch {
110
- // Path doesn't exist - check if it looks like a directory (ends with /)
111
- isDirectory = output.endsWith("/");
123
+ results = await generateMigrationsOrSchema(allFragnoDatabases, {
124
+ path: output,
125
+ toVersion,
126
+ fromVersion,
127
+ });
128
+ } catch (error) {
129
+ throw new Error(
130
+ `Failed to generate schema: ${error instanceof Error ? error.message : String(error)}`,
131
+ );
112
132
  }
113
- }
114
-
115
- // Generate schema
116
- let result: { schema: string; path: string };
117
- try {
118
- // If output is a directory, pass undefined to get the default file name
119
- // Otherwise pass the output path as-is
120
- result = await fragnoDb.generateSchema({
121
- path: isDirectory ? undefined : output,
122
- toVersion: targetVersion,
123
- fromVersion: sourceVersion,
124
- });
125
- } catch (error) {
126
- throw new Error(
127
- `Failed to generate schema: ${error instanceof Error ? error.message : String(error)}`,
128
- );
129
- }
130
-
131
- // Resolve final output path
132
- let finalOutputPath: string;
133
- if (isDirectory && output) {
134
- // Combine directory with generated file name
135
- const resolvedDir = resolve(process.cwd(), output);
136
- finalOutputPath = join(resolvedDir, basename(result.path));
137
- } else if (output) {
138
- // Use the provided path as-is
139
- finalOutputPath = resolve(process.cwd(), output);
140
- } else {
141
- // Use the generated file name in current directory
142
- finalOutputPath = resolve(process.cwd(), result.path);
143
- }
144
-
145
- // Ensure parent directory exists
146
- const parentDir = dirname(finalOutputPath);
147
- try {
148
- await mkdir(parentDir, { recursive: true });
149
- } catch (error) {
150
- throw new Error(
151
- `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`,
152
- );
153
- }
154
-
155
- // Write schema to file
156
- try {
157
- const content = prefix ? `${prefix}\n${result.schema}` : result.schema;
158
- await writeFile(finalOutputPath, content, { encoding: "utf-8" });
159
- } catch (error) {
160
- throw new Error(
161
- `Failed to write schema file: ${error instanceof Error ? error.message : String(error)}`,
162
- );
163
- }
164
133
 
165
- console.log(`✓ Schema generated successfully: ${finalOutputPath}`);
166
- console.log(` Namespace: ${fragnoDb.namespace}`);
167
- console.log(` Schema version: ${fragnoDb.schema.version}`);
168
- }
134
+ // Write all generated files
135
+ for (const result of results) {
136
+ // For single file: use output as exact file path
137
+ // For multiple files: use output as base directory
138
+ const finalOutputPath =
139
+ output && results.length === 1
140
+ ? resolve(process.cwd(), output)
141
+ : output
142
+ ? resolve(process.cwd(), output, result.path)
143
+ : resolve(process.cwd(), result.path);
144
+
145
+ // Ensure parent directory exists
146
+ const parentDir = dirname(finalOutputPath);
147
+ try {
148
+ await mkdir(parentDir, { recursive: true });
149
+ } catch (error) {
150
+ throw new Error(
151
+ `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`,
152
+ );
153
+ }
154
+
155
+ // Write schema to file
156
+ try {
157
+ const content = prefix ? `${prefix}\n${result.schema}` : result.schema;
158
+ await writeFile(finalOutputPath, content, { encoding: "utf-8" });
159
+ } catch (error) {
160
+ throw new Error(
161
+ `Failed to write schema file: ${error instanceof Error ? error.message : String(error)}`,
162
+ );
163
+ }
164
+
165
+ console.log(`✓ Generated: ${finalOutputPath}`);
166
+ }
167
+
168
+ console.log(`\n✓ Schema generated successfully!`);
169
+ console.log(` Files generated: ${results.length}`);
170
+ console.log(` Fragments:`);
171
+ for (const db of allFragnoDatabases) {
172
+ console.log(` - ${db.namespace} (version ${db.schema.version})`);
173
+ }
174
+ },
175
+ });