@fragno-dev/cli 0.1.2 → 0.1.4

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,131 +1,133 @@
1
1
  import { resolve } from "node:path";
2
- import type { CommandContext } from "gunshi";
2
+ import { define } from "gunshi";
3
3
  import { findFragnoDatabases } from "../../utils/find-fragno-databases";
4
+ import { type FragnoDatabase } from "@fragno-dev/db";
5
+ import { executeMigrations, type ExecuteMigrationResult } from "@fragno-dev/db/generation-engine";
6
+ import type { AnySchema } from "@fragno-dev/db/schema";
7
+
8
+ export const migrateCommand = define({
9
+ name: "migrate",
10
+ description: "Run database migrations for all fragments to their latest versions",
11
+ args: {},
12
+ run: async (ctx) => {
13
+ const targets = ctx.positionals;
14
+
15
+ if (targets.length === 0) {
16
+ throw new Error("At least one target file path is required");
17
+ }
4
18
 
5
- export async function migrate(ctx: CommandContext) {
6
- const target = ctx.values["target"];
7
- const version = ctx.values["to"];
8
- const fromVersion = ctx.values["from"];
9
-
10
- if (!target || typeof target !== "string") {
11
- throw new Error("Target file path is required and must be a string");
12
- }
13
-
14
- if (version !== undefined && typeof version !== "string" && typeof version !== "number") {
15
- throw new Error("Version must be a number or string");
16
- }
17
-
18
- if (
19
- fromVersion !== undefined &&
20
- typeof fromVersion !== "string" &&
21
- typeof fromVersion !== "number"
22
- ) {
23
- throw new Error("Version must be a number or string");
24
- }
25
-
26
- // Resolve the target file path relative to current working directory
27
- const targetPath = resolve(process.cwd(), target);
28
-
29
- console.log(`Loading target file: ${targetPath}`);
30
-
31
- // Dynamically import the target file
32
- let targetModule: Record<string, unknown>;
33
- try {
34
- targetModule = await import(targetPath);
35
- } catch (error) {
36
- throw new Error(
37
- `Failed to import target file: ${error instanceof Error ? error.message : String(error)}`,
38
- );
39
- }
19
+ // De-duplicate targets
20
+ const uniqueTargets = Array.from(new Set(targets));
40
21
 
41
- // Find all FragnoDatabase instances or instantiated fragments with databases
42
- const fragnoDatabases = findFragnoDatabases(targetModule);
22
+ // Load all target files and collect FragnoDatabase instances
23
+ const allFragnoDatabases: FragnoDatabase<AnySchema>[] = [];
43
24
 
44
- if (fragnoDatabases.length === 0) {
45
- throw new Error(
46
- `No FragnoDatabase instances found in ${target}.\n` +
47
- `Make sure you export either:\n` +
48
- ` - A FragnoDatabase instance created with .create(adapter)\n` +
49
- ` - An instantiated fragment with embedded database definition\n`,
50
- );
51
- }
25
+ for (const target of uniqueTargets) {
26
+ const targetPath = resolve(process.cwd(), target);
27
+ console.log(`Loading target file: ${targetPath}`);
52
28
 
53
- if (fragnoDatabases.length > 1) {
54
- console.warn(
55
- `Warning: Multiple FragnoDatabase instances found (${fragnoDatabases.length}). Using the first one.`,
56
- );
57
- }
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
+ }
58
38
 
59
- // Use the first FragnoDatabase instance
60
- const fragnoDb = fragnoDatabases[0];
39
+ // Find all FragnoDatabase instances or instantiated fragments with databases
40
+ const fragnoDatabases = findFragnoDatabases(targetModule);
61
41
 
62
- console.log(`Migrating database for namespace: ${fragnoDb.namespace}`);
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
+ }
63
51
 
64
- // Check if the adapter supports migrations
65
- if (!fragnoDb.adapter.createMigrationEngine) {
66
- throw new Error(
67
- `Adapter does not support running migrations. The adapter only supports schema generation.\n` +
68
- `Try using 'fragno db generate' instead to generate schema files.`,
69
- );
70
- }
71
-
72
- // Parse versions if provided
73
- const targetVersion = version !== undefined ? parseInt(String(version), 10) : undefined;
74
- const expectedFromVersion =
75
- fromVersion !== undefined ? parseInt(String(fromVersion), 10) : undefined;
76
-
77
- if (targetVersion !== undefined && isNaN(targetVersion)) {
78
- throw new Error(`Invalid version number: ${version}`);
79
- }
80
-
81
- if (expectedFromVersion !== undefined && isNaN(expectedFromVersion)) {
82
- throw new Error(`Invalid version number: ${fromVersion}`);
83
- }
84
-
85
- // Run migrations
86
- let didMigrate: boolean;
87
- try {
88
- if (targetVersion !== undefined) {
89
- console.log(`Migrating to version ${targetVersion}...`);
90
- const migrator = fragnoDb.adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace);
91
- const currentVersion = await migrator.getVersion();
92
- console.log(`Current version: ${currentVersion}`);
93
-
94
- // Validate from version if provided
95
- if (expectedFromVersion !== undefined && currentVersion !== expectedFromVersion) {
96
- throw new Error(
97
- `Current database version (${currentVersion}) does not match expected --from version (${expectedFromVersion})`,
52
+ if (fragnoDatabases.length > 1) {
53
+ console.warn(
54
+ `Warning: Multiple FragnoDatabase instances found in ${target} (${fragnoDatabases.length}). Using all of them.`,
98
55
  );
99
56
  }
100
57
 
101
- const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {
102
- updateSettings: true,
103
- });
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
+ }
84
+
85
+ console.log("\nMigrating all fragments to their latest versions...\n");
104
86
 
105
- if (preparedMigration.operations.length === 0) {
106
- console.log("✓ Database is already at the target version. No migrations needed.");
107
- didMigrate = false;
87
+ let results: ExecuteMigrationResult[];
88
+ try {
89
+ results = await executeMigrations(allFragnoDatabases);
90
+ } catch (error) {
91
+ throw new Error(
92
+ `Migration failed: ${error instanceof Error ? error.message : String(error)}`,
93
+ );
94
+ }
95
+
96
+ // Display progress for each result
97
+ for (const result of results) {
98
+ console.log(`Fragment: ${result.namespace}`);
99
+ console.log(` Current version: ${result.fromVersion}`);
100
+ console.log(` Target version: ${result.toVersion}`);
101
+
102
+ if (result.didMigrate) {
103
+ console.log(` ✓ Migration completed: v${result.fromVersion} → v${result.toVersion}\n`);
108
104
  } else {
109
- await preparedMigration.execute();
110
- didMigrate = true;
105
+ console.log(` ✓ Already at latest version. No migration needed.\n`);
111
106
  }
112
- } else {
113
- console.log(`Migrating to latest version (${fragnoDb.schema.version})...`);
114
- didMigrate = await fragnoDb.runMigrations();
115
107
  }
116
- } catch (error) {
117
- throw new Error(
118
- `Failed to run migrations: ${error instanceof Error ? error.message : String(error)}`,
119
- );
120
- }
121
-
122
- if (didMigrate) {
123
- console.log(`✓ Migration completed successfully`);
124
- console.log(` Namespace: ${fragnoDb.namespace}`);
125
- if (targetVersion !== undefined) {
126
- console.log(` New version: ${targetVersion}`);
127
- } else {
128
- console.log(` New version: ${fragnoDb.schema.version}`);
108
+
109
+ // Summary
110
+ console.log("═══════════════════════════════════════");
111
+ console.log("Migration Summary");
112
+ console.log("═══════════════════════════════════════");
113
+
114
+ const migrated = results.filter((r) => r.didMigrate);
115
+ const skipped = results.filter((r) => !r.didMigrate);
116
+
117
+ if (migrated.length > 0) {
118
+ console.log(`\n✓ Migrated ${migrated.length} fragment(s):`);
119
+ for (const r of migrated) {
120
+ console.log(` - ${r.namespace}: v${r.fromVersion} → v${r.toVersion}`);
121
+ }
129
122
  }
130
- }
131
- }
123
+
124
+ if (skipped.length > 0) {
125
+ console.log(`\n○ Skipped ${skipped.length} fragment(s) (already up-to-date):`);
126
+ for (const r of skipped) {
127
+ console.log(` - ${r.namespace}: v${r.toVersion}`);
128
+ }
129
+ }
130
+
131
+ console.log("\n✓ All migrations completed successfully");
132
+ },
133
+ });
@@ -1 +0,0 @@
1
- $ tsc --noEmit