@fragno-dev/cli 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +16 -0
- package/dist/cli.d.ts +33 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +212 -227
- package/dist/cli.js.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +12 -80
- package/src/commands/db/generate.ts +164 -157
- package/src/commands/db/info.ts +161 -76
- package/src/commands/db/migrate.ts +116 -114
- package/.turbo/turbo-types$colon$check.log +0 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -7,9 +7,9 @@ $ tsdown
|
|
|
7
7
|
[34mℹ[39m Build start
|
|
8
8
|
[34mℹ[39m Granting execute permission to [4mdist/cli.d.ts[24m
|
|
9
9
|
[34mℹ[39m Granting execute permission to [4mdist/cli.js[24m
|
|
10
|
-
[34mℹ[39m [2mdist/[22m[1mcli.js[22m [2m16.
|
|
11
|
-
[34mℹ[39m [2mdist/[22mcli.js.map [
|
|
12
|
-
[34mℹ[39m [2mdist/[22mcli.d.ts.map [2m 0.
|
|
13
|
-
[34mℹ[39m [2mdist/[22m[32m[1mcli.d.ts[22m[39m [2m 0.
|
|
14
|
-
[34mℹ[39m 4 files, total:
|
|
15
|
-
[32m✔[39m Build complete in [
|
|
10
|
+
[34mℹ[39m [2mdist/[22m[1mcli.js[22m [2m16.59 kB[22m [2m│ gzip: 3.65 kB[22m
|
|
11
|
+
[34mℹ[39m [2mdist/[22mcli.js.map [2m33.18 kB[22m [2m│ gzip: 7.26 kB[22m
|
|
12
|
+
[34mℹ[39m [2mdist/[22mcli.d.ts.map [2m 0.51 kB[22m [2m│ gzip: 0.30 kB[22m
|
|
13
|
+
[34mℹ[39m [2mdist/[22m[32m[1mcli.d.ts[22m[39m [2m 0.94 kB[22m [2m│ gzip: 0.30 kB[22m
|
|
14
|
+
[34mℹ[39m 4 files, total: 51.22 kB
|
|
15
|
+
[32m✔[39m Build complete in [32m15136ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
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
|
+
|
|
3
19
|
## 0.1.2
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
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
|
|
6
|
-
declare const
|
|
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":";;;;
|
|
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,
|
|
4
|
-
import {
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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: "
|
|
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:
|
|
257
|
-
|
|
258
|
-
const
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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: {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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) {
|