@fragno-dev/cli 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
 
2
- > @fragno-dev/cli@0.1.7 build /home/runner/work/fragno/fragno/apps/fragno-cli
2
+ > @fragno-dev/cli@0.1.8 build /home/runner/work/fragno/fragno/apps/fragno-cli
3
3
  > tsdown
4
4
 
5
- ℹ tsdown v0.15.10 powered by rolldown v1.0.0-beta.44
5
+ ℹ tsdown v0.15.11 powered by rolldown v1.0.0-beta.45
6
6
  ℹ Using tsdown config: /home/runner/work/fragno/fragno/apps/fragno-cli/tsdown.config.ts
7
7
  ℹ entry: src/cli.ts
8
8
  ℹ target: node22.0.0
@@ -10,9 +10,9 @@
10
10
  ℹ Build start
11
11
  ℹ Granting execute permission to dist/cli.d.ts
12
12
  ℹ Granting execute permission to dist/cli.js
13
- ℹ dist/cli.js 16.55 kB │ gzip: 3.65 kB
14
- ℹ dist/cli.js.map 33.14 kB │ gzip: 7.25 kB
15
- ℹ dist/cli.d.ts.map  0.51 kB │ gzip: 0.30 kB
13
+ ℹ dist/cli.js 15.22 kB │ gzip: 3.96 kB
14
+ ℹ dist/cli.js.map 30.08 kB │ gzip: 7.78 kB
15
+ ℹ dist/cli.d.ts.map  0.51 kB │ gzip: 0.29 kB
16
16
  ℹ dist/cli.d.ts  0.94 kB │ gzip: 0.30 kB
17
- ℹ 4 files, total: 51.15 kB
18
- ✔ Build complete in 3345ms
17
+ ℹ 4 files, total: 46.75 kB
18
+ ✔ Build complete in 13305ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @fragno-dev/cli
2
2
 
3
+ ## 0.1.8
4
+
5
+ ### Patch Changes
6
+
7
+ - ab6c4bf: fix: make Fragment loading in the CLI more robust
8
+ - Updated dependencies [e36dbcd]
9
+ - Updated dependencies [ab6c4bf]
10
+ - Updated dependencies [d1feecd]
11
+ - @fragno-dev/db@0.1.7
12
+
3
13
  ## 0.1.7
4
14
 
5
15
  ### Patch Changes
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","names":[],"sources":["../src/commands/db/generate.ts","../src/commands/db/migrate.ts","../src/commands/db/info.ts","../src/cli.ts"],"sourcesContent":[],"mappings":";;;;cASa,iBAqKX,OAAA,CArK0B;;;;IAAf,WAAA,EAAA,MAqKX;;;;ICvKW,KAAA,EAAA,MA6HX;;;;IC9HW,IAAA,EAAA,QA2KX;;;;ECrJW,MAAA,EAAA;IAWA,IAAA,EAAA,QAaX;;;;;;;cF7CW,gBA6HX,OAAA,CA7HyB;;;cCDd,aA2KX,OAAA,CA3KsB;;;AFGX,cGmBA,SHkJX,EGlJoB,OAAA,CAAA,OHnBM,CGuB1B,OAAA,CAJoB,IAAA,CHnBM;cG8Bf,aAAW,OAAA,CAAA,QAatB,OAAA,CAbsB,IAAA"}
1
+ {"version":3,"file":"cli.d.ts","names":[],"sources":["../src/commands/db/generate.ts","../src/commands/db/migrate.ts","../src/commands/db/info.ts","../src/cli.ts"],"sourcesContent":[],"mappings":";;;;cAOa,iBAyGX,OAAA,CAzG0B;;;;IAAf,WAAA,EAAA,MAyGX;;;;IC3GW,KAAA,EAAA,MAqEX;;;;ICtEW,IAAA,EAAA,QA4HX;;;;ECpGW,MAAA,EAAA;IAWA,IAAA,EAAA,QAaX;;;;;;;cF/CW,gBAqEX,OAAA,CArEyB;;;cCDd,aA4HX,OAAA,CA5HsB;;;AFGX,cGqBA,SHoFX,EGpFoB,OAAA,CAAA,OHrBM,CGyB1B,OAAA,CAJoB,IAAA,CHrBM;cGgCf,aAAW,OAAA,CAAA,QAatB,OAAA,CAbsB,IAAA"}
package/dist/cli.js CHANGED
@@ -1,12 +1,71 @@
1
1
  #!/usr/bin/env node
2
2
  import { cli, define, parseArgs, resolveArgs } from "gunshi";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
- import { dirname, resolve } from "node:path";
4
+ import { dirname, relative, resolve } from "node:path";
5
+ import { executeMigrations, generateMigrationsOrSchema } from "@fragno-dev/db/generation-engine";
5
6
  import { FragnoDatabase, isFragnoDatabase } from "@fragno-dev/db";
7
+ import { fragnoDatabaseAdapterNameFakeSymbol, fragnoDatabaseAdapterVersionFakeSymbol } from "@fragno-dev/db/adapters";
6
8
  import { instantiatedFragmentFakeSymbol } from "@fragno-dev/core/api/fragment-instantiation";
7
- import { executeMigrations, generateMigrationsOrSchema } from "@fragno-dev/db/generation-engine";
9
+ import { loadConfig } from "c12";
8
10
 
9
11
  //#region src/utils/find-fragno-databases.ts
12
+ async function importFragmentFile(path) {
13
+ const { config } = await loadConfig({ configFile: path });
14
+ const databases = findFragnoDatabases(config);
15
+ const adapterNames = databases.map((db) => `${db.adapter[fragnoDatabaseAdapterNameFakeSymbol]}@${db.adapter[fragnoDatabaseAdapterVersionFakeSymbol]}`);
16
+ if ([...new Set(adapterNames)].length > 1) throw new Error(`All Fragno databases must use the same adapter name and version. Found mismatch: (${adapterNames.join(", ")})`);
17
+ return {
18
+ adapter: databases[0].adapter,
19
+ databases
20
+ };
21
+ }
22
+ /**
23
+ * Imports multiple fragment files and validates they all use the same adapter.
24
+ * Returns the combined databases from all files.
25
+ */
26
+ async function importFragmentFiles(paths) {
27
+ const uniquePaths = Array.from(new Set(paths));
28
+ if (uniquePaths.length === 0) throw new Error("No fragment files provided");
29
+ const allDatabases = [];
30
+ let adapter;
31
+ let firstAdapterFile;
32
+ const cwd = process.cwd();
33
+ for (const path of uniquePaths) {
34
+ const relativePath = relative(cwd, path);
35
+ try {
36
+ const result = await importFragmentFile(path);
37
+ const databases = result["databases"];
38
+ const fileAdapter = result["adapter"];
39
+ if (databases.length === 0) {
40
+ console.warn(`Warning: No FragnoDatabase instances found in ${relativePath}.\nMake sure you export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n`);
41
+ continue;
42
+ }
43
+ if (!adapter) {
44
+ adapter = fileAdapter;
45
+ firstAdapterFile = relativePath;
46
+ }
47
+ const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];
48
+ const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];
49
+ const fileAdapterName = fileAdapter[fragnoDatabaseAdapterNameFakeSymbol];
50
+ const fileAdapterVersion = fileAdapter[fragnoDatabaseAdapterVersionFakeSymbol];
51
+ if (firstAdapterName !== fileAdapterName || firstAdapterVersion !== fileAdapterVersion) {
52
+ const firstAdapterInfo = `${firstAdapterName}@${firstAdapterVersion}`;
53
+ const fileAdapterInfo = `${fileAdapterName}@${fileAdapterVersion}`;
54
+ throw new Error(`All fragments must use the same database adapter. Mixed adapters found:\n - ${firstAdapterFile}: ${firstAdapterInfo}\n - ${relativePath}: ${fileAdapterInfo}\n\nMake sure all fragments use the same adapter name and version.`);
55
+ }
56
+ allDatabases.push(...databases);
57
+ console.log(` Found ${databases.length} database(s) in ${relativePath}`);
58
+ } catch (error) {
59
+ throw new Error(`Failed to import fragment file ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
60
+ }
61
+ }
62
+ if (allDatabases.length === 0) throw new Error("No FragnoDatabase instances found in any of the target files.\nMake sure your files export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n");
63
+ if (!adapter) throw new Error("No adapter found in any of the fragment files");
64
+ return {
65
+ adapter,
66
+ databases: allDatabases
67
+ };
68
+ }
10
69
  function isFragnoInstantiatedFragment(value) {
11
70
  return typeof value === "object" && value !== null && instantiatedFragmentFakeSymbol in value && value[instantiatedFragmentFakeSymbol] === instantiatedFragmentFakeSymbol;
12
71
  }
@@ -19,22 +78,16 @@ function additionalContextIsDatabaseContext(additionalContext) {
19
78
  */
20
79
  function findFragnoDatabases(targetModule) {
21
80
  const fragnoDatabases = [];
22
- for (const [key, value] of Object.entries(targetModule)) if (isFragnoDatabase(value)) {
23
- fragnoDatabases.push(value);
24
- console.log(`Found FragnoDatabase instance: ${key}`);
25
- } else if (isFragnoInstantiatedFragment(value)) {
81
+ for (const [_key, value] of Object.entries(targetModule)) if (isFragnoDatabase(value)) fragnoDatabases.push(value);
82
+ else if (isFragnoInstantiatedFragment(value)) {
26
83
  const additionalContext = value.additionalContext;
27
- if (!additionalContext || !additionalContextIsDatabaseContext(additionalContext)) {
28
- console.warn(`Instantiated fragment ${key} has no database context`);
29
- continue;
30
- }
84
+ if (!additionalContext || !additionalContextIsDatabaseContext(additionalContext)) continue;
31
85
  const { databaseSchema, databaseNamespace, databaseAdapter } = additionalContext;
32
86
  fragnoDatabases.push(new FragnoDatabase({
33
87
  namespace: databaseNamespace,
34
88
  schema: databaseSchema,
35
89
  adapter: databaseAdapter
36
90
  }));
37
- console.log(`Found database context in instantiated fragment: ${key}`);
38
91
  }
39
92
  return fragnoDatabases;
40
93
  }
@@ -72,31 +125,8 @@ const generateCommand = define({
72
125
  const toVersion = ctx.values.to;
73
126
  const fromVersion = ctx.values.from;
74
127
  const prefix = ctx.values.prefix;
75
- const uniqueTargets = Array.from(new Set(targets));
76
- const allFragnoDatabases = [];
77
- for (const target of uniqueTargets) {
78
- const targetPath = resolve(process.cwd(), target);
79
- console.log(`Loading target file: ${targetPath}`);
80
- let targetModule;
81
- try {
82
- targetModule = await import(targetPath);
83
- } catch (error) {
84
- throw new Error(`Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`);
85
- }
86
- const fragnoDatabases = findFragnoDatabases(targetModule);
87
- if (fragnoDatabases.length === 0) {
88
- console.warn(`Warning: No FragnoDatabase instances found in ${target}.\nMake sure you export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n`);
89
- continue;
90
- }
91
- if (fragnoDatabases.length > 1) console.warn(`Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Using all of them.`);
92
- allFragnoDatabases.push(...fragnoDatabases);
93
- }
94
- if (allFragnoDatabases.length === 0) throw new Error("No FragnoDatabase instances found in any of the target files.\nMake sure your files export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n");
95
- console.log(`Found ${allFragnoDatabases.length} FragnoDatabase instance(s) across ${uniqueTargets.length} file(s)`);
96
- const firstDb = allFragnoDatabases[0];
97
- const firstAdapter = firstDb.adapter;
98
- if (!allFragnoDatabases.every((db) => db.adapter === firstAdapter)) throw new Error("All fragments must use the same database adapter instance. Mixed adapters are not supported.");
99
- if (!firstDb.adapter.createSchemaGenerator && !firstDb.adapter.createMigrationEngine) throw new Error("The adapter does not support schema generation. Please use an adapter that implements either createSchemaGenerator or createMigrationEngine.");
128
+ const { databases: allFragnoDatabases, adapter } = await importFragmentFiles(targets.map((target) => resolve(process.cwd(), target)));
129
+ if (!adapter.createSchemaGenerator && !adapter.createMigrationEngine) throw new Error("The adapter does not support schema generation. Please use an adapter that implements either createSchemaGenerator or createMigrationEngine.");
100
130
  console.log("Generating schema...");
101
131
  let results;
102
132
  try {
@@ -139,29 +169,7 @@ const migrateCommand = define({
139
169
  run: async (ctx) => {
140
170
  const targets = ctx.positionals;
141
171
  if (targets.length === 0) throw new Error("At least one target file path is required");
142
- const uniqueTargets = Array.from(new Set(targets));
143
- const allFragnoDatabases = [];
144
- for (const target of uniqueTargets) {
145
- const targetPath = resolve(process.cwd(), target);
146
- console.log(`Loading target file: ${targetPath}`);
147
- let targetModule;
148
- try {
149
- targetModule = await import(targetPath);
150
- } catch (error) {
151
- throw new Error(`Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`);
152
- }
153
- const fragnoDatabases = findFragnoDatabases(targetModule);
154
- if (fragnoDatabases.length === 0) {
155
- console.warn(`Warning: No FragnoDatabase instances found in ${target}.\nMake sure you export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n`);
156
- continue;
157
- }
158
- if (fragnoDatabases.length > 1) console.warn(`Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Using all of them.`);
159
- allFragnoDatabases.push(...fragnoDatabases);
160
- }
161
- if (allFragnoDatabases.length === 0) throw new Error("No FragnoDatabase instances found in any of the target files.\nMake sure your files export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n");
162
- console.log(`Found ${allFragnoDatabases.length} FragnoDatabase instance(s) across ${uniqueTargets.length} file(s)`);
163
- const firstAdapter = allFragnoDatabases[0].adapter;
164
- if (!allFragnoDatabases.every((db) => db.adapter === firstAdapter)) throw new Error("All fragments must use the same database adapter instance. Mixed adapters are not supported.");
172
+ const { databases: allFragnoDatabases } = await importFragmentFiles(targets.map((target) => resolve(process.cwd(), target)));
165
173
  console.log("\nMigrating all fragments to their latest versions...\n");
166
174
  let results;
167
175
  try {
@@ -189,6 +197,7 @@ const migrateCommand = define({
189
197
  console.log(`\n○ Skipped ${skipped.length} fragment(s) (already up-to-date):`);
190
198
  for (const r of skipped) console.log(` - ${r.namespace}: v${r.toVersion}`);
191
199
  }
200
+ for (const db of allFragnoDatabases) await db.adapter.close();
192
201
  console.log("\n✓ All migrations completed successfully");
193
202
  }
194
203
  });
@@ -202,26 +211,7 @@ const infoCommand = define({
202
211
  run: async (ctx) => {
203
212
  const targets = ctx.positionals;
204
213
  if (targets.length === 0) throw new Error("At least one target file path is required");
205
- const uniqueTargets = Array.from(new Set(targets));
206
- const allFragnoDatabases = [];
207
- for (const target of uniqueTargets) {
208
- const targetPath = resolve(process.cwd(), target);
209
- console.log(`Loading target file: ${targetPath}`);
210
- let targetModule;
211
- try {
212
- targetModule = await import(targetPath);
213
- } catch (error) {
214
- throw new Error(`Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`);
215
- }
216
- const fragnoDatabases = findFragnoDatabases(targetModule);
217
- if (fragnoDatabases.length === 0) {
218
- console.warn(`Warning: No FragnoDatabase instances found in ${target}.\nMake sure you export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n`);
219
- continue;
220
- }
221
- if (fragnoDatabases.length > 1) console.warn(`Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Showing info for all of them.`);
222
- allFragnoDatabases.push(...fragnoDatabases);
223
- }
224
- if (allFragnoDatabases.length === 0) throw new Error("No FragnoDatabase instances found in any of the target files.\nMake sure your files export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n");
214
+ const { databases: allFragnoDatabases } = await importFragmentFiles(targets.map((target) => resolve(process.cwd(), target)));
225
215
  const dbInfos = await Promise.all(allFragnoDatabases.map(async (fragnoDb) => {
226
216
  const info = {
227
217
  namespace: fragnoDb.namespace,
@@ -243,7 +233,7 @@ const infoCommand = define({
243
233
  }));
244
234
  const hasMigrationSupport = dbInfos.some((info) => info.migrationSupport);
245
235
  console.log("");
246
- console.log(`Found ${allFragnoDatabases.length} database(s) across ${uniqueTargets.length} file(s):`);
236
+ console.log(`Database Information:`);
247
237
  console.log("");
248
238
  const namespaceHeader = "Namespace";
249
239
  const versionHeader = "Schema";
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
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-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-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-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-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-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-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-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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"}
1
+ {"version":3,"file":"cli.js","names":["allDatabases: FragnoDatabase<AnySchema>[]","adapter: DatabaseAdapter | undefined","firstAdapterFile: string | undefined","fragnoDatabases: FragnoDatabase<AnySchema>[]","results: { schema: string; path: string; namespace: string }[]","results: ExecuteMigrationResult[]","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 {\n fragnoDatabaseAdapterNameFakeSymbol,\n fragnoDatabaseAdapterVersionFakeSymbol,\n} from \"@fragno-dev/db/adapters\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport {\n instantiatedFragmentFakeSymbol,\n type FragnoInstantiatedFragment,\n} from \"@fragno-dev/core/api/fragment-instantiation\";\nimport { loadConfig } from \"c12\";\nimport { relative } from \"node:path\";\n\nexport async function importFragmentFile(path: string): Promise<Record<string, unknown>> {\n const { config } = await loadConfig({\n configFile: path,\n });\n\n const databases = findFragnoDatabases(config);\n const adapterNames = databases.map(\n (db) =>\n `${db.adapter[fragnoDatabaseAdapterNameFakeSymbol]}@${db.adapter[fragnoDatabaseAdapterVersionFakeSymbol]}`,\n );\n const uniqueAdapterNames = [...new Set(adapterNames)];\n\n if (uniqueAdapterNames.length > 1) {\n throw new Error(\n `All Fragno databases must use the same adapter name and version. ` +\n `Found mismatch: (${adapterNames.join(\", \")})`,\n );\n }\n\n return {\n adapter: databases[0].adapter,\n databases,\n };\n}\n\n/**\n * Imports multiple fragment files and validates they all use the same adapter.\n * Returns the combined databases from all files.\n */\nexport async function importFragmentFiles(paths: string[]): Promise<{\n adapter: DatabaseAdapter;\n databases: FragnoDatabase<AnySchema>[];\n}> {\n // De-duplicate paths (in case same file was specified multiple times)\n const uniquePaths = Array.from(new Set(paths));\n\n if (uniquePaths.length === 0) {\n throw new Error(\"No fragment files provided\");\n }\n\n const allDatabases: FragnoDatabase<AnySchema>[] = [];\n let adapter: DatabaseAdapter | undefined;\n let firstAdapterFile: string | undefined;\n const cwd = process.cwd();\n\n for (const path of uniquePaths) {\n const relativePath = relative(cwd, path);\n\n try {\n const result = await importFragmentFile(path);\n const databases = result[\"databases\"] as FragnoDatabase<AnySchema>[];\n const fileAdapter = result[\"adapter\"] as DatabaseAdapter;\n\n if (databases.length === 0) {\n console.warn(\n `Warning: No FragnoDatabase instances found in ${relativePath}.\\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 // Set the adapter from the first file with databases\n if (!adapter) {\n adapter = fileAdapter;\n firstAdapterFile = relativePath;\n }\n\n // Validate all files use the same adapter name and version\n const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n const fileAdapterName = fileAdapter[fragnoDatabaseAdapterNameFakeSymbol];\n const fileAdapterVersion = fileAdapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n if (firstAdapterName !== fileAdapterName || firstAdapterVersion !== fileAdapterVersion) {\n const firstAdapterInfo = `${firstAdapterName}@${firstAdapterVersion}`;\n const fileAdapterInfo = `${fileAdapterName}@${fileAdapterVersion}`;\n\n throw new Error(\n `All fragments must use the same database adapter. Mixed adapters found:\\n` +\n ` - ${firstAdapterFile}: ${firstAdapterInfo}\\n` +\n ` - ${relativePath}: ${fileAdapterInfo}\\n\\n` +\n `Make sure all fragments use the same adapter name and version.`,\n );\n }\n\n allDatabases.push(...databases);\n console.log(` Found ${databases.length} database(s) in ${relativePath}`);\n } catch (error) {\n throw new Error(\n `Failed to import fragment file ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n if (allDatabases.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 if (!adapter) {\n throw new Error(\"No adapter found in any of the fragment files\");\n }\n\n return {\n adapter,\n databases: allDatabases,\n };\n}\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 } else if (isFragnoInstantiatedFragment(value)) {\n const additionalContext = value.additionalContext;\n\n if (!additionalContext || !additionalContextIsDatabaseContext(additionalContext)) {\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 }\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 { generateMigrationsOrSchema } from \"@fragno-dev/db/generation-engine\";\nimport { importFragmentFiles } from \"../../utils/find-fragno-databases\";\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 // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files and validate they use the same adapter\n const { databases: allFragnoDatabases, adapter } = await importFragmentFiles(targetPaths);\n\n // Check if adapter supports any form of schema generation\n if (!adapter.createSchemaGenerator && !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 { importFragmentFiles } from \"../../utils/find-fragno-databases\";\nimport { executeMigrations, type ExecuteMigrationResult } from \"@fragno-dev/db/generation-engine\";\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 // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files and validate they use the same adapter\n const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);\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 for (const db of allFragnoDatabases) {\n await db.adapter.close();\n }\n\n console.log(\"\\n✓ All migrations completed successfully\");\n },\n});\n","import { resolve } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { importFragmentFiles } from \"../../utils/find-fragno-databases\";\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 // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files\n const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);\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(`Database Information:`);\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-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-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-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-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-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-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-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.7",
3
+ "version": "0.1.8",
4
4
  "exports": {
5
5
  ".": {
6
6
  "development": "./src/cli.ts",
@@ -22,8 +22,9 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@clack/prompts": "^0.11.0",
25
+ "c12": "^3.3.1",
25
26
  "gunshi": "^0.26.3",
26
- "@fragno-dev/db": "0.1.6",
27
+ "@fragno-dev/db": "0.1.7",
27
28
  "@fragno-dev/core": "0.1.3"
28
29
  },
29
30
  "main": "./dist/cli.js",
@@ -1,10 +1,8 @@
1
1
  import { writeFile, mkdir } from "node:fs/promises";
2
2
  import { resolve, dirname } from "node:path";
3
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
4
  import { generateMigrationsOrSchema } from "@fragno-dev/db/generation-engine";
5
+ import { importFragmentFiles } from "../../utils/find-fragno-databases";
8
6
 
9
7
  // Define the db generate command with type safety
10
8
  export const generateCommand = define({
@@ -41,74 +39,14 @@ export const generateCommand = define({
41
39
  const fromVersion = ctx.values.from;
42
40
  const prefix = ctx.values.prefix;
43
41
 
44
- // De-duplicate targets (in case same file was specified multiple times)
45
- const uniqueTargets = Array.from(new Set(targets));
42
+ // Resolve all target paths
43
+ const targetPaths = targets.map((target) => resolve(process.cwd(), target));
46
44
 
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
- }
85
-
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
- }
94
-
95
- console.log(
96
- `Found ${allFragnoDatabases.length} FragnoDatabase instance(s) across ${uniqueTargets.length} file(s)`,
97
- );
98
-
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);
103
-
104
- if (!allSameAdapter) {
105
- throw new Error(
106
- "All fragments must use the same database adapter instance. Mixed adapters are not supported.",
107
- );
108
- }
45
+ // Import all fragment files and validate they use the same adapter
46
+ const { databases: allFragnoDatabases, adapter } = await importFragmentFiles(targetPaths);
109
47
 
110
48
  // Check if adapter supports any form of schema generation
111
- if (!firstDb.adapter.createSchemaGenerator && !firstDb.adapter.createMigrationEngine) {
49
+ if (!adapter.createSchemaGenerator && !adapter.createMigrationEngine) {
112
50
  throw new Error(
113
51
  `The adapter does not support schema generation. ` +
114
52
  `Please use an adapter that implements either createSchemaGenerator or createMigrationEngine.`,
@@ -1,8 +1,6 @@
1
1
  import { resolve } from "node:path";
2
2
  import { define } from "gunshi";
3
- import { findFragnoDatabases } from "../../utils/find-fragno-databases";
4
- import type { FragnoDatabase } from "@fragno-dev/db";
5
- import type { AnySchema } from "@fragno-dev/db/schema";
3
+ import { importFragmentFiles } from "../../utils/find-fragno-databases";
6
4
 
7
5
  export const infoCommand = define({
8
6
  name: "info",
@@ -15,56 +13,11 @@ export const infoCommand = define({
15
13
  throw new Error("At least one target file path is required");
16
14
  }
17
15
 
18
- // De-duplicate targets (in case same file was specified multiple times)
19
- const uniqueTargets = Array.from(new Set(targets));
16
+ // Resolve all target paths
17
+ const targetPaths = targets.map((target) => resolve(process.cwd(), target));
20
18
 
21
- // Load all target files and collect FragnoDatabase instances
22
- const allFragnoDatabases: FragnoDatabase<AnySchema>[] = [];
23
-
24
- for (const target of uniqueTargets) {
25
- const targetPath = resolve(process.cwd(), target);
26
- console.log(`Loading target file: ${targetPath}`);
27
-
28
- // Dynamically import the target file
29
- let targetModule: Record<string, unknown>;
30
- try {
31
- targetModule = await import(targetPath);
32
- } catch (error) {
33
- throw new Error(
34
- `Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`,
35
- );
36
- }
37
-
38
- // Find all FragnoDatabase instances or instantiated fragments with databases
39
- const fragnoDatabases = findFragnoDatabases(targetModule);
40
-
41
- if (fragnoDatabases.length === 0) {
42
- console.warn(
43
- `Warning: No FragnoDatabase instances found in ${target}.\n` +
44
- `Make sure you export either:\n` +
45
- ` - A FragnoDatabase instance created with .create(adapter)\n` +
46
- ` - An instantiated fragment with embedded database definition\n`,
47
- );
48
- continue;
49
- }
50
-
51
- if (fragnoDatabases.length > 1) {
52
- console.warn(
53
- `Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Showing info for all of them.`,
54
- );
55
- }
56
-
57
- allFragnoDatabases.push(...fragnoDatabases);
58
- }
59
-
60
- if (allFragnoDatabases.length === 0) {
61
- throw new Error(
62
- `No FragnoDatabase instances found in any of the target files.\n` +
63
- `Make sure your files export either:\n` +
64
- ` - A FragnoDatabase instance created with .create(adapter)\n` +
65
- ` - An instantiated fragment with embedded database definition\n`,
66
- );
67
- }
19
+ // Import all fragment files
20
+ const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);
68
21
 
69
22
  // Collect database information
70
23
  const dbInfos = await Promise.all(
@@ -116,9 +69,7 @@ export const infoCommand = define({
116
69
 
117
70
  // Print compact table
118
71
  console.log("");
119
- console.log(
120
- `Found ${allFragnoDatabases.length} database(s) across ${uniqueTargets.length} file(s):`,
121
- );
72
+ console.log(`Database Information:`);
122
73
  console.log("");
123
74
 
124
75
  // Table header
@@ -1,9 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { define } from "gunshi";
3
- import { findFragnoDatabases } from "../../utils/find-fragno-databases";
4
- import { type FragnoDatabase } from "@fragno-dev/db";
3
+ import { importFragmentFiles } from "../../utils/find-fragno-databases";
5
4
  import { executeMigrations, type ExecuteMigrationResult } from "@fragno-dev/db/generation-engine";
6
- import type { AnySchema } from "@fragno-dev/db/schema";
7
5
 
8
6
  export const migrateCommand = define({
9
7
  name: "migrate",
@@ -16,71 +14,11 @@ export const migrateCommand = define({
16
14
  throw new Error("At least one target file path is required");
17
15
  }
18
16
 
19
- // De-duplicate targets
20
- const uniqueTargets = Array.from(new Set(targets));
17
+ // Resolve all target paths
18
+ const targetPaths = targets.map((target) => resolve(process.cwd(), target));
21
19
 
22
- // Load all target files and collect FragnoDatabase instances
23
- const allFragnoDatabases: FragnoDatabase<AnySchema>[] = [];
24
-
25
- for (const target of uniqueTargets) {
26
- const targetPath = resolve(process.cwd(), target);
27
- console.log(`Loading target file: ${targetPath}`);
28
-
29
- // Dynamically import the target file
30
- let targetModule: Record<string, unknown>;
31
- try {
32
- targetModule = await import(targetPath);
33
- } catch (error) {
34
- throw new Error(
35
- `Failed to import target file ${target}: ${error instanceof Error ? error.message : String(error)}`,
36
- );
37
- }
38
-
39
- // Find all FragnoDatabase instances or instantiated fragments with databases
40
- const fragnoDatabases = findFragnoDatabases(targetModule);
41
-
42
- if (fragnoDatabases.length === 0) {
43
- console.warn(
44
- `Warning: No FragnoDatabase instances found in ${target}.\n` +
45
- `Make sure you export either:\n` +
46
- ` - A FragnoDatabase instance created with .create(adapter)\n` +
47
- ` - An instantiated fragment with embedded database definition\n`,
48
- );
49
- continue;
50
- }
51
-
52
- if (fragnoDatabases.length > 1) {
53
- console.warn(
54
- `Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Using all of them.`,
55
- );
56
- }
57
-
58
- allFragnoDatabases.push(...fragnoDatabases);
59
- }
60
-
61
- if (allFragnoDatabases.length === 0) {
62
- throw new Error(
63
- `No FragnoDatabase instances found in any of the target files.\n` +
64
- `Make sure your files export either:\n` +
65
- ` - A FragnoDatabase instance created with .create(adapter)\n` +
66
- ` - An instantiated fragment with embedded database definition\n`,
67
- );
68
- }
69
-
70
- console.log(
71
- `Found ${allFragnoDatabases.length} FragnoDatabase instance(s) across ${uniqueTargets.length} file(s)`,
72
- );
73
-
74
- // Validate all databases use the same adapter object (identity)
75
- const firstDb = allFragnoDatabases[0];
76
- const firstAdapter = firstDb.adapter;
77
- const allSameAdapter = allFragnoDatabases.every((db) => db.adapter === firstAdapter);
78
-
79
- if (!allSameAdapter) {
80
- throw new Error(
81
- "All fragments must use the same database adapter instance. Mixed adapters are not supported.",
82
- );
83
- }
20
+ // Import all fragment files and validate they use the same adapter
21
+ const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);
84
22
 
85
23
  console.log("\nMigrating all fragments to their latest versions...\n");
86
24
 
@@ -128,6 +66,10 @@ export const migrateCommand = define({
128
66
  }
129
67
  }
130
68
 
69
+ for (const db of allFragnoDatabases) {
70
+ await db.adapter.close();
71
+ }
72
+
131
73
  console.log("\n✓ All migrations completed successfully");
132
74
  },
133
75
  });
@@ -1,9 +1,130 @@
1
1
  import { isFragnoDatabase, type DatabaseAdapter, FragnoDatabase } from "@fragno-dev/db";
2
+ import {
3
+ fragnoDatabaseAdapterNameFakeSymbol,
4
+ fragnoDatabaseAdapterVersionFakeSymbol,
5
+ } from "@fragno-dev/db/adapters";
2
6
  import type { AnySchema } from "@fragno-dev/db/schema";
3
7
  import {
4
8
  instantiatedFragmentFakeSymbol,
5
9
  type FragnoInstantiatedFragment,
6
10
  } from "@fragno-dev/core/api/fragment-instantiation";
11
+ import { loadConfig } from "c12";
12
+ import { relative } from "node:path";
13
+
14
+ export async function importFragmentFile(path: string): Promise<Record<string, unknown>> {
15
+ const { config } = await loadConfig({
16
+ configFile: path,
17
+ });
18
+
19
+ const databases = findFragnoDatabases(config);
20
+ const adapterNames = databases.map(
21
+ (db) =>
22
+ `${db.adapter[fragnoDatabaseAdapterNameFakeSymbol]}@${db.adapter[fragnoDatabaseAdapterVersionFakeSymbol]}`,
23
+ );
24
+ const uniqueAdapterNames = [...new Set(adapterNames)];
25
+
26
+ if (uniqueAdapterNames.length > 1) {
27
+ throw new Error(
28
+ `All Fragno databases must use the same adapter name and version. ` +
29
+ `Found mismatch: (${adapterNames.join(", ")})`,
30
+ );
31
+ }
32
+
33
+ return {
34
+ adapter: databases[0].adapter,
35
+ databases,
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Imports multiple fragment files and validates they all use the same adapter.
41
+ * Returns the combined databases from all files.
42
+ */
43
+ export async function importFragmentFiles(paths: string[]): Promise<{
44
+ adapter: DatabaseAdapter;
45
+ databases: FragnoDatabase<AnySchema>[];
46
+ }> {
47
+ // De-duplicate paths (in case same file was specified multiple times)
48
+ const uniquePaths = Array.from(new Set(paths));
49
+
50
+ if (uniquePaths.length === 0) {
51
+ throw new Error("No fragment files provided");
52
+ }
53
+
54
+ const allDatabases: FragnoDatabase<AnySchema>[] = [];
55
+ let adapter: DatabaseAdapter | undefined;
56
+ let firstAdapterFile: string | undefined;
57
+ const cwd = process.cwd();
58
+
59
+ for (const path of uniquePaths) {
60
+ const relativePath = relative(cwd, path);
61
+
62
+ try {
63
+ const result = await importFragmentFile(path);
64
+ const databases = result["databases"] as FragnoDatabase<AnySchema>[];
65
+ const fileAdapter = result["adapter"] as DatabaseAdapter;
66
+
67
+ if (databases.length === 0) {
68
+ console.warn(
69
+ `Warning: No FragnoDatabase instances found in ${relativePath}.\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
+ // Set the adapter from the first file with databases
78
+ if (!adapter) {
79
+ adapter = fileAdapter;
80
+ firstAdapterFile = relativePath;
81
+ }
82
+
83
+ // Validate all files use the same adapter name and version
84
+ const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];
85
+ const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];
86
+ const fileAdapterName = fileAdapter[fragnoDatabaseAdapterNameFakeSymbol];
87
+ const fileAdapterVersion = fileAdapter[fragnoDatabaseAdapterVersionFakeSymbol];
88
+
89
+ if (firstAdapterName !== fileAdapterName || firstAdapterVersion !== fileAdapterVersion) {
90
+ const firstAdapterInfo = `${firstAdapterName}@${firstAdapterVersion}`;
91
+ const fileAdapterInfo = `${fileAdapterName}@${fileAdapterVersion}`;
92
+
93
+ throw new Error(
94
+ `All fragments must use the same database adapter. Mixed adapters found:\n` +
95
+ ` - ${firstAdapterFile}: ${firstAdapterInfo}\n` +
96
+ ` - ${relativePath}: ${fileAdapterInfo}\n\n` +
97
+ `Make sure all fragments use the same adapter name and version.`,
98
+ );
99
+ }
100
+
101
+ allDatabases.push(...databases);
102
+ console.log(` Found ${databases.length} database(s) in ${relativePath}`);
103
+ } catch (error) {
104
+ throw new Error(
105
+ `Failed to import fragment file ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
106
+ );
107
+ }
108
+ }
109
+
110
+ if (allDatabases.length === 0) {
111
+ throw new Error(
112
+ `No FragnoDatabase instances found in any of the target files.\n` +
113
+ `Make sure your files export either:\n` +
114
+ ` - A FragnoDatabase instance created with .create(adapter)\n` +
115
+ ` - An instantiated fragment with embedded database definition\n`,
116
+ );
117
+ }
118
+
119
+ if (!adapter) {
120
+ throw new Error("No adapter found in any of the fragment files");
121
+ }
122
+
123
+ return {
124
+ adapter,
125
+ databases: allDatabases,
126
+ };
127
+ }
7
128
 
8
129
  function isFragnoInstantiatedFragment(
9
130
  value: unknown,
@@ -39,15 +160,13 @@ export function findFragnoDatabases(
39
160
  ): FragnoDatabase<AnySchema>[] {
40
161
  const fragnoDatabases: FragnoDatabase<AnySchema>[] = [];
41
162
 
42
- for (const [key, value] of Object.entries(targetModule)) {
163
+ for (const [_key, value] of Object.entries(targetModule)) {
43
164
  if (isFragnoDatabase(value)) {
44
165
  fragnoDatabases.push(value);
45
- console.log(`Found FragnoDatabase instance: ${key}`);
46
166
  } else if (isFragnoInstantiatedFragment(value)) {
47
167
  const additionalContext = value.additionalContext;
48
168
 
49
169
  if (!additionalContext || !additionalContextIsDatabaseContext(additionalContext)) {
50
- console.warn(`Instantiated fragment ${key} has no database context`);
51
170
  continue;
52
171
  }
53
172
 
@@ -61,7 +180,6 @@ export function findFragnoDatabases(
61
180
  adapter: databaseAdapter,
62
181
  }),
63
182
  );
64
- console.log(`Found database context in instantiated fragment: ${key}`);
65
183
  }
66
184
  }
67
185