@fragno-dev/cli 0.1.1 → 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.
@@ -7,9 +7,9 @@ $ tsdown
7
7
  ℹ Build start
8
8
  ℹ Granting execute permission to dist/cli.d.ts
9
9
  ℹ Granting execute permission to dist/cli.js
10
- ℹ dist/cli.js 16.07 kB │ gzip: 3.50 kB
11
- ℹ dist/cli.js.map 30.92 kB │ gzip: 6.79 kB
12
- ℹ dist/cli.d.ts.map  0.23 kB │ gzip: 0.19 kB
13
- ℹ dist/cli.d.ts  0.39 kB │ gzip: 0.19 kB
14
- ℹ 4 files, total: 47.61 kB
15
- ✔ Build complete in 10597ms
10
+ ℹ dist/cli.js 16.59 kB │ gzip: 3.65 kB
11
+ ℹ dist/cli.js.map 33.18 kB │ gzip: 7.26 kB
12
+ ℹ dist/cli.d.ts.map  0.51 kB │ gzip: 0.30 kB
13
+ ℹ dist/cli.d.ts  0.94 kB │ gzip: 0.30 kB
14
+ ℹ 4 files, total: 51.22 kB
15
+ ✔ Build complete in 15136ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @fragno-dev/cli
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - a8b1f81: `db generate` command now supports targeting multiple Fragment files
8
+ - a8b1f81: `db info` command now supports targeting multiple files
9
+ - be17727: Added support for generating migrations in multi-Fragment applications
10
+ - Updated dependencies [e7122f2]
11
+ - Updated dependencies [921ef11]
12
+ - Updated dependencies [be17727]
13
+ - Updated dependencies [8362d9a]
14
+ - Updated dependencies [8362d9a]
15
+ - Updated dependencies [c70de59]
16
+ - @fragno-dev/db@0.1.2
17
+ - @fragno-dev/core@0.1.2
18
+
19
+ ## 0.1.2
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [4c1c806]
24
+ - @fragno-dev/db@0.1.1
25
+ - @fragno-dev/core@0.1.1
26
+
3
27
  ## 0.1.1
4
28
 
5
29
  ### Patch Changes
package/dist/cli.d.ts CHANGED
@@ -1,12 +1,39 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "gunshi";
2
+ import * as gunshi4 from "gunshi";
3
3
 
4
+ //#region src/commands/db/generate.d.ts
5
+ declare const generateCommand: gunshi4.Command<{
6
+ output: {
7
+ type: "string";
8
+ short: string;
9
+ description: string;
10
+ };
11
+ from: {
12
+ type: "number";
13
+ short: string;
14
+ description: string;
15
+ };
16
+ to: {
17
+ type: "number";
18
+ short: string;
19
+ description: string;
20
+ };
21
+ prefix: {
22
+ type: "string";
23
+ short: string;
24
+ description: string;
25
+ };
26
+ }>;
27
+ //#endregion
28
+ //#region src/commands/db/migrate.d.ts
29
+ declare const migrateCommand: gunshi4.Command<{}>;
30
+ //#endregion
31
+ //#region src/commands/db/info.d.ts
32
+ declare const infoCommand: gunshi4.Command<{}>;
33
+ //#endregion
4
34
  //#region src/cli.d.ts
5
- declare const generateCommand: Command;
6
- declare const migrateCommand: Command;
7
- declare const infoCommand: Command;
8
- declare const dbCommand: Command;
9
- declare const mainCommand: Command;
35
+ declare const dbCommand: gunshi4.Command<gunshi4.Args>;
36
+ declare const mainCommand: gunshi4.Command<gunshi4.Args>;
10
37
  //#endregion
11
38
  export { dbCommand, generateCommand, infoCommand, mainCommand, migrateCommand };
12
39
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","names":[],"sources":["../src/cli.ts"],"sourcesContent":[],"mappings":";;;;cAQa,iBAAiB;cAkCjB,gBAAgB;AAlChB,cAyDA,WA1BZ,EA0ByB,OA1BzB;AAGY,cAwDA,SApCZ,EAoCuB,OAxDK;AAuBhB,cA4CA,WA5Ca,EA4CA,OAlCzB"}
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"}
package/dist/cli.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { cli, parseArgs, resolveArgs } from "gunshi";
3
- import { mkdir, stat, writeFile } from "node:fs/promises";
4
- import { basename, dirname, join, resolve } from "node:path";
2
+ import { cli, define, parseArgs, resolveArgs } from "gunshi";
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { dirname, resolve } from "node:path";
5
5
  import { FragnoDatabase, isFragnoDatabase } from "@fragno-dev/db";
6
6
  import { instantiatedFragmentFakeSymbol } from "@fragno-dev/core/api/fragment-instantiation";
7
+ import { executeMigrations, generateMigrationsOrSchema } from "@fragno-dev/db/generation-engine";
7
8
 
8
9
  //#region src/utils/find-fragno-databases.ts
9
10
  function isFragnoInstantiatedFragment(value) {
@@ -40,210 +41,22 @@ function findFragnoDatabases(targetModule) {
40
41
 
41
42
  //#endregion
42
43
  //#region src/commands/db/generate.ts
43
- async function generate(ctx) {
44
- const target = ctx.values["target"];
45
- const output = ctx.values["output"];
46
- const toVersion = ctx.values["to"];
47
- const fromVersion = ctx.values["from"];
48
- const prefix = ctx.values["prefix"];
49
- if (!target || typeof target !== "string") throw new Error("Target file path is required and must be a string");
50
- if (typeof output === "number" || typeof output === "boolean") throw new Error("Output file path is required and must be a string");
51
- if (toVersion !== void 0 && typeof toVersion !== "string" && typeof toVersion !== "number") throw new Error("Version must be a number or string");
52
- if (fromVersion !== void 0 && typeof fromVersion !== "string" && typeof fromVersion !== "number") throw new Error("Version must be a number or string");
53
- if (prefix !== void 0 && typeof prefix !== "string") throw new Error("Prefix must be a string");
54
- const targetPath = resolve(process.cwd(), target);
55
- console.log(`Loading target file: ${targetPath}`);
56
- let targetModule;
57
- try {
58
- targetModule = await import(targetPath);
59
- } catch (error) {
60
- throw new Error(`Failed to import target file: ${error instanceof Error ? error.message : String(error)}`);
61
- }
62
- const fragnoDatabases = findFragnoDatabases(targetModule);
63
- if (fragnoDatabases.length === 0) throw new Error(`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`);
64
- if (fragnoDatabases.length > 1) console.warn(`Warning: Multiple FragnoDatabase instances found (${fragnoDatabases.length}). Using the first one.`);
65
- const fragnoDb = fragnoDatabases[0];
66
- const targetVersion = toVersion !== void 0 ? parseInt(String(toVersion), 10) : void 0;
67
- const sourceVersion = fromVersion !== void 0 ? parseInt(String(fromVersion), 10) : void 0;
68
- if (targetVersion !== void 0 && isNaN(targetVersion)) throw new Error(`Invalid version number: ${toVersion}`);
69
- if (sourceVersion !== void 0 && isNaN(sourceVersion)) throw new Error(`Invalid version number: ${fromVersion}`);
70
- if (sourceVersion !== void 0 && targetVersion !== void 0) console.log(`Generating schema for namespace: ${fragnoDb.namespace} (from version ${sourceVersion} to version ${targetVersion})`);
71
- else if (targetVersion !== void 0) console.log(`Generating schema for namespace: ${fragnoDb.namespace} (to version ${targetVersion})`);
72
- else if (sourceVersion !== void 0) console.log(`Generating schema for namespace: ${fragnoDb.namespace} (from version ${sourceVersion})`);
73
- else console.log(`Generating schema for namespace: ${fragnoDb.namespace}`);
74
- let isDirectory = false;
75
- if (output) {
76
- const resolvedOutput = resolve(process.cwd(), output);
77
- try {
78
- isDirectory = (await stat(resolvedOutput)).isDirectory();
79
- } catch {
80
- isDirectory = output.endsWith("/");
81
- }
82
- }
83
- let result;
84
- try {
85
- result = await fragnoDb.generateSchema({
86
- path: isDirectory ? void 0 : output,
87
- toVersion: targetVersion,
88
- fromVersion: sourceVersion
89
- });
90
- } catch (error) {
91
- throw new Error(`Failed to generate schema: ${error instanceof Error ? error.message : String(error)}`);
92
- }
93
- let finalOutputPath;
94
- if (isDirectory && output) finalOutputPath = join(resolve(process.cwd(), output), basename(result.path));
95
- else if (output) finalOutputPath = resolve(process.cwd(), output);
96
- else finalOutputPath = resolve(process.cwd(), result.path);
97
- const parentDir = dirname(finalOutputPath);
98
- try {
99
- await mkdir(parentDir, { recursive: true });
100
- } catch (error) {
101
- throw new Error(`Failed to create directory: ${error instanceof Error ? error.message : String(error)}`);
102
- }
103
- try {
104
- const content = prefix ? `${prefix}\n${result.schema}` : result.schema;
105
- await writeFile(finalOutputPath, content, { encoding: "utf-8" });
106
- } catch (error) {
107
- throw new Error(`Failed to write schema file: ${error instanceof Error ? error.message : String(error)}`);
108
- }
109
- console.log(`✓ Schema generated successfully: ${finalOutputPath}`);
110
- console.log(` Namespace: ${fragnoDb.namespace}`);
111
- console.log(` Schema version: ${fragnoDb.schema.version}`);
112
- }
113
-
114
- //#endregion
115
- //#region src/commands/db/migrate.ts
116
- async function migrate(ctx) {
117
- const target = ctx.values["target"];
118
- const version = ctx.values["to"];
119
- const fromVersion = ctx.values["from"];
120
- if (!target || typeof target !== "string") throw new Error("Target file path is required and must be a string");
121
- if (version !== void 0 && typeof version !== "string" && typeof version !== "number") throw new Error("Version must be a number or string");
122
- if (fromVersion !== void 0 && typeof fromVersion !== "string" && typeof fromVersion !== "number") throw new Error("Version must be a number or string");
123
- const targetPath = resolve(process.cwd(), target);
124
- console.log(`Loading target file: ${targetPath}`);
125
- let targetModule;
126
- try {
127
- targetModule = await import(targetPath);
128
- } catch (error) {
129
- throw new Error(`Failed to import target file: ${error instanceof Error ? error.message : String(error)}`);
130
- }
131
- const fragnoDatabases = findFragnoDatabases(targetModule);
132
- if (fragnoDatabases.length === 0) throw new Error(`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`);
133
- if (fragnoDatabases.length > 1) console.warn(`Warning: Multiple FragnoDatabase instances found (${fragnoDatabases.length}). Using the first one.`);
134
- const fragnoDb = fragnoDatabases[0];
135
- console.log(`Migrating database for namespace: ${fragnoDb.namespace}`);
136
- if (!fragnoDb.adapter.createMigrationEngine) throw new Error("Adapter does not support running migrations. The adapter only supports schema generation.\nTry using 'fragno db generate' instead to generate schema files.");
137
- const targetVersion = version !== void 0 ? parseInt(String(version), 10) : void 0;
138
- const expectedFromVersion = fromVersion !== void 0 ? parseInt(String(fromVersion), 10) : void 0;
139
- if (targetVersion !== void 0 && isNaN(targetVersion)) throw new Error(`Invalid version number: ${version}`);
140
- if (expectedFromVersion !== void 0 && isNaN(expectedFromVersion)) throw new Error(`Invalid version number: ${fromVersion}`);
141
- let didMigrate;
142
- try {
143
- if (targetVersion !== void 0) {
144
- console.log(`Migrating to version ${targetVersion}...`);
145
- const migrator = fragnoDb.adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace);
146
- const currentVersion = await migrator.getVersion();
147
- console.log(`Current version: ${currentVersion}`);
148
- if (expectedFromVersion !== void 0 && currentVersion !== expectedFromVersion) throw new Error(`Current database version (${currentVersion}) does not match expected --from version (${expectedFromVersion})`);
149
- const preparedMigration = await migrator.prepareMigrationTo(targetVersion, { updateSettings: true });
150
- if (preparedMigration.operations.length === 0) {
151
- console.log("✓ Database is already at the target version. No migrations needed.");
152
- didMigrate = false;
153
- } else {
154
- await preparedMigration.execute();
155
- didMigrate = true;
156
- }
157
- } else {
158
- console.log(`Migrating to latest version (${fragnoDb.schema.version})...`);
159
- didMigrate = await fragnoDb.runMigrations();
160
- }
161
- } catch (error) {
162
- throw new Error(`Failed to run migrations: ${error instanceof Error ? error.message : String(error)}`);
163
- }
164
- if (didMigrate) {
165
- console.log(`✓ Migration completed successfully`);
166
- console.log(` Namespace: ${fragnoDb.namespace}`);
167
- if (targetVersion !== void 0) console.log(` New version: ${targetVersion}`);
168
- else console.log(` New version: ${fragnoDb.schema.version}`);
169
- }
170
- }
171
-
172
- //#endregion
173
- //#region src/commands/db/info.ts
174
- async function info(ctx) {
175
- const target = ctx.values["target"];
176
- if (!target || typeof target !== "string") throw new Error("Target file path is required and must be a string");
177
- const targetPath = resolve(process.cwd(), target);
178
- console.log(`Loading target file: ${targetPath}`);
179
- let targetModule;
180
- try {
181
- targetModule = await import(targetPath);
182
- } catch (error) {
183
- throw new Error(`Failed to import target file: ${error instanceof Error ? error.message : String(error)}`);
184
- }
185
- const fragnoDatabases = [];
186
- for (const [key, value] of Object.entries(targetModule)) if (isFragnoDatabase(value)) {
187
- fragnoDatabases.push(value);
188
- console.log(`Found FragnoDatabase instance: ${key}`);
189
- }
190
- if (fragnoDatabases.length === 0) throw new Error(`No FragnoDatabase instances found in ${target}.\n`);
191
- if (fragnoDatabases.length > 1) console.warn(`Warning: Multiple FragnoDatabase instances found (${fragnoDatabases.length}). Using the first one.`);
192
- const fragnoDb = fragnoDatabases[0];
193
- console.log("");
194
- console.log("Database Information");
195
- console.log("=".repeat(50));
196
- console.log(`Namespace: ${fragnoDb.namespace}`);
197
- console.log(`Latest Schema Version: ${fragnoDb.schema.version}`);
198
- if (!fragnoDb.adapter.createMigrationEngine) {
199
- console.log(`Migration Support: No`);
200
- console.log("");
201
- console.log("Note: This adapter does not support running migrations.");
202
- console.log("Use 'fragno db generate' to generate schema files.");
203
- return;
204
- }
205
- console.log(`Migration Support: Yes`);
206
- try {
207
- const currentVersion = await fragnoDb.adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace).getVersion();
208
- console.log(`Current Database Version: ${currentVersion}`);
209
- const pendingVersions = fragnoDb.schema.version - currentVersion;
210
- if (pendingVersions > 0) {
211
- console.log("");
212
- console.log(`⚠ Pending Migrations: ${pendingVersions} version(s) behind`);
213
- console.log(` Run '@fragno-dev/cli db migrate --target ${target}' to update`);
214
- } else if (pendingVersions === 0) {
215
- console.log("");
216
- console.log(`✓ Database is up to date`);
217
- }
218
- } catch (error) {
219
- console.log("");
220
- console.log(`Warning: Could not retrieve current version: ${error instanceof Error ? error.message : String(error)}`);
221
- }
222
- console.log("=".repeat(50));
223
- }
224
-
225
- //#endregion
226
- //#region src/cli.ts
227
- const generateCommand = {
44
+ const generateCommand = define({
228
45
  name: "generate",
229
46
  description: "Generate schema files from FragnoDatabase definitions",
230
47
  args: {
231
- target: {
232
- type: "positional",
233
- description: "Path to the file that exports a FragnoDatabase instance"
234
- },
235
48
  output: {
236
49
  type: "string",
237
50
  short: "o",
238
- description: "Output path for the generated schema file (default: schema.sql for Kysely, schema.ts for Drizzle)"
51
+ description: "Output path: for single file, exact file path; for multiple files, output directory (default: current directory)"
239
52
  },
240
53
  from: {
241
- type: "string",
54
+ type: "number",
242
55
  short: "f",
243
56
  description: "Source version to generate migration from (default: current database version)"
244
57
  },
245
58
  to: {
246
- type: "string",
59
+ type: "number",
247
60
  short: "t",
248
61
  description: "Target version to generate migration to (default: latest schema version)"
249
62
  },
@@ -253,38 +66,210 @@ const generateCommand = {
253
66
  description: "String to prepend to the generated file (e.g., '/* eslint-disable */')"
254
67
  }
255
68
  },
256
- run: generate
257
- };
258
- const migrateCommand = {
69
+ run: async (ctx) => {
70
+ const targets = ctx.positionals;
71
+ const output = ctx.values.output;
72
+ const toVersion = ctx.values.to;
73
+ const fromVersion = ctx.values.from;
74
+ 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.");
100
+ console.log("Generating schema...");
101
+ let results;
102
+ try {
103
+ results = await generateMigrationsOrSchema(allFragnoDatabases, {
104
+ path: output,
105
+ toVersion,
106
+ fromVersion
107
+ });
108
+ } catch (error) {
109
+ throw new Error(`Failed to generate schema: ${error instanceof Error ? error.message : String(error)}`);
110
+ }
111
+ for (const result of results) {
112
+ const finalOutputPath = output && results.length === 1 ? resolve(process.cwd(), output) : output ? resolve(process.cwd(), output, result.path) : resolve(process.cwd(), result.path);
113
+ const parentDir = dirname(finalOutputPath);
114
+ try {
115
+ await mkdir(parentDir, { recursive: true });
116
+ } catch (error) {
117
+ throw new Error(`Failed to create directory: ${error instanceof Error ? error.message : String(error)}`);
118
+ }
119
+ try {
120
+ await writeFile(finalOutputPath, prefix ? `${prefix}\n${result.schema}` : result.schema, { encoding: "utf-8" });
121
+ } catch (error) {
122
+ throw new Error(`Failed to write schema file: ${error instanceof Error ? error.message : String(error)}`);
123
+ }
124
+ console.log(`✓ Generated: ${finalOutputPath}`);
125
+ }
126
+ console.log(`\n✓ Schema generated successfully!`);
127
+ console.log(` Files generated: ${results.length}`);
128
+ console.log(` Fragments:`);
129
+ for (const db of allFragnoDatabases) console.log(` - ${db.namespace} (version ${db.schema.version})`);
130
+ }
131
+ });
132
+
133
+ //#endregion
134
+ //#region src/commands/db/migrate.ts
135
+ const migrateCommand = define({
259
136
  name: "migrate",
260
- description: "Run database migrations",
261
- args: {
262
- target: {
263
- type: "positional",
264
- description: "Path to the file that exports a FragnoDatabase instance"
265
- },
266
- from: {
267
- type: "string",
268
- short: "f",
269
- description: "Expected current database version (validates before migrating)"
270
- },
271
- to: {
272
- type: "string",
273
- short: "t",
274
- description: "Target version to migrate to (default: latest schema version)"
137
+ description: "Run database migrations for all fragments to their latest versions",
138
+ args: {},
139
+ run: async (ctx) => {
140
+ const targets = ctx.positionals;
141
+ 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);
275
160
  }
276
- },
277
- run: migrate
278
- };
279
- const infoCommand = {
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.");
165
+ console.log("\nMigrating all fragments to their latest versions...\n");
166
+ let results;
167
+ try {
168
+ results = await executeMigrations(allFragnoDatabases);
169
+ } catch (error) {
170
+ throw new Error(`Migration failed: ${error instanceof Error ? error.message : String(error)}`);
171
+ }
172
+ for (const result of results) {
173
+ console.log(`Fragment: ${result.namespace}`);
174
+ console.log(` Current version: ${result.fromVersion}`);
175
+ console.log(` Target version: ${result.toVersion}`);
176
+ if (result.didMigrate) console.log(` ✓ Migration completed: v${result.fromVersion} → v${result.toVersion}\n`);
177
+ else console.log(` ✓ Already at latest version. No migration needed.\n`);
178
+ }
179
+ console.log("═══════════════════════════════════════");
180
+ console.log("Migration Summary");
181
+ console.log("═══════════════════════════════════════");
182
+ const migrated = results.filter((r) => r.didMigrate);
183
+ const skipped = results.filter((r) => !r.didMigrate);
184
+ if (migrated.length > 0) {
185
+ console.log(`\n✓ Migrated ${migrated.length} fragment(s):`);
186
+ for (const r of migrated) console.log(` - ${r.namespace}: v${r.fromVersion} → v${r.toVersion}`);
187
+ }
188
+ if (skipped.length > 0) {
189
+ console.log(`\n○ Skipped ${skipped.length} fragment(s) (already up-to-date):`);
190
+ for (const r of skipped) console.log(` - ${r.namespace}: v${r.toVersion}`);
191
+ }
192
+ console.log("\n✓ All migrations completed successfully");
193
+ }
194
+ });
195
+
196
+ //#endregion
197
+ //#region src/commands/db/info.ts
198
+ const infoCommand = define({
280
199
  name: "info",
281
200
  description: "Display database information and migration status",
282
- args: { target: {
283
- type: "positional",
284
- description: "Path to the file that exports a FragnoDatabase instance"
285
- } },
286
- run: info
287
- };
201
+ args: {},
202
+ run: async (ctx) => {
203
+ const targets = ctx.positionals;
204
+ 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");
225
+ const dbInfos = await Promise.all(allFragnoDatabases.map(async (fragnoDb) => {
226
+ const info = {
227
+ namespace: fragnoDb.namespace,
228
+ schemaVersion: fragnoDb.schema.version,
229
+ migrationSupport: !!fragnoDb.adapter.createMigrationEngine
230
+ };
231
+ if (fragnoDb.adapter.createMigrationEngine) try {
232
+ const currentVersion = await fragnoDb.adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace).getVersion();
233
+ info.currentVersion = currentVersion;
234
+ info.pendingVersions = fragnoDb.schema.version - currentVersion;
235
+ if (info.pendingVersions > 0) info.status = `Pending (${info.pendingVersions} migration(s))`;
236
+ else if (info.pendingVersions === 0) info.status = "Up to date";
237
+ } catch (error) {
238
+ info.error = error instanceof Error ? error.message : String(error);
239
+ info.status = "Error";
240
+ }
241
+ else info.status = "Schema only";
242
+ return info;
243
+ }));
244
+ const hasMigrationSupport = dbInfos.some((info) => info.migrationSupport);
245
+ console.log("");
246
+ console.log(`Found ${allFragnoDatabases.length} database(s) across ${uniqueTargets.length} file(s):`);
247
+ console.log("");
248
+ const namespaceHeader = "Namespace";
249
+ const versionHeader = "Schema";
250
+ const currentHeader = "Current";
251
+ const statusHeader = "Status";
252
+ const maxNamespaceLen = Math.max(9, ...dbInfos.map((info) => info.namespace.length));
253
+ const namespaceWidth = Math.max(maxNamespaceLen + 2, 20);
254
+ const versionWidth = 8;
255
+ const currentWidth = 9;
256
+ const statusWidth = 25;
257
+ console.log(namespaceHeader.padEnd(namespaceWidth) + versionHeader.padEnd(versionWidth) + (hasMigrationSupport ? currentHeader.padEnd(currentWidth) : "") + statusHeader);
258
+ console.log("-".repeat(namespaceWidth) + "-".repeat(versionWidth) + (hasMigrationSupport ? "-".repeat(currentWidth) : "") + "-".repeat(statusWidth));
259
+ for (const info of dbInfos) {
260
+ const currentVersionStr = info.currentVersion !== void 0 ? String(info.currentVersion) : "-";
261
+ console.log(info.namespace.padEnd(namespaceWidth) + String(info.schemaVersion).padEnd(versionWidth) + (hasMigrationSupport ? currentVersionStr.padEnd(currentWidth) : "") + (info.status || "-"));
262
+ }
263
+ console.log("");
264
+ if (!hasMigrationSupport) {
265
+ console.log("Note: These adapters do not support migrations.");
266
+ console.log("Use '@fragno-dev/cli db generate' to generate schema files.");
267
+ } else if (dbInfos.some((info) => info.pendingVersions && info.pendingVersions > 0)) console.log("Run '@fragno-dev/cli db migrate <target>' to apply pending migrations.");
268
+ }
269
+ });
270
+
271
+ //#endregion
272
+ //#region src/cli.ts
288
273
  const dbSubCommands = /* @__PURE__ */ new Map();
289
274
  dbSubCommands.set("generate", generateCommand);
290
275
  dbSubCommands.set("migrate", migrateCommand);
@@ -301,14 +286,14 @@ function printDbHelp() {
301
286
  console.log("");
302
287
  console.log("Run '@fragno-dev/cli db <command> --help' for more information.");
303
288
  }
304
- const dbCommand = {
289
+ const dbCommand = define({
305
290
  name: "db",
306
291
  description: "Database management commands",
307
292
  run: printDbHelp
308
- };
293
+ });
309
294
  const rootSubCommands = /* @__PURE__ */ new Map();
310
295
  rootSubCommands.set("db", dbCommand);
311
- const mainCommand = {
296
+ const mainCommand = define({
312
297
  name: "@fragno-dev/cli",
313
298
  description: "Fragno CLI - Tools for working with Fragno fragments",
314
299
  run: () => {
@@ -321,7 +306,7 @@ const mainCommand = {
321
306
  console.log("");
322
307
  console.log("Run '@fragno-dev/cli <command> --help' for more information.");
323
308
  }
324
- };
309
+ });
325
310
  if (import.meta.main) try {
326
311
  const args = process.argv.slice(2);
327
312
  if (args[0] === "db" && args.length > 1) {