@fragno-dev/cli 0.1.6 → 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.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +18 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +77 -87
- package/dist/cli.js.map +1 -1
- package/package.json +7 -6
- package/src/cli.ts +5 -5
- package/src/commands/db/generate.ts +6 -68
- package/src/commands/db/info.ts +8 -57
- package/src/commands/db/migrate.ts +9 -67
- package/src/utils/find-fragno-databases.ts +122 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
> @fragno-dev/cli@0.1.
|
|
2
|
+
> @fragno-dev/cli@0.1.8 build /home/runner/work/fragno/fragno/apps/fragno-cli
|
|
3
3
|
> tsdown
|
|
4
4
|
|
|
5
|
-
[34mℹ[39m tsdown [2mv0.15.
|
|
5
|
+
[34mℹ[39m tsdown [2mv0.15.11[22m powered by rolldown [2mv1.0.0-beta.45[22m
|
|
6
6
|
[34mℹ[39m Using tsdown config: [4m/home/runner/work/fragno/fragno/apps/fragno-cli/tsdown.config.ts[24m
|
|
7
7
|
[34mℹ[39m entry: [34msrc/cli.ts[39m
|
|
8
8
|
[34mℹ[39m target: [34mnode22.0.0[39m
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
[34mℹ[39m Build start
|
|
11
11
|
[34mℹ[39m Granting execute permission to [4mdist/cli.d.ts[24m
|
|
12
12
|
[34mℹ[39m Granting execute permission to [4mdist/cli.js[24m
|
|
13
|
-
[34mℹ[39m [2mdist/[22m[1mcli.js[22m [
|
|
14
|
-
[34mℹ[39m [2mdist/[22mcli.js.map [
|
|
15
|
-
[34mℹ[39m [2mdist/[22mcli.d.ts.map [2m 0.51 kB[22m [2m│ gzip: 0.
|
|
13
|
+
[34mℹ[39m [2mdist/[22m[1mcli.js[22m [2m15.22 kB[22m [2m│ gzip: 3.96 kB[22m
|
|
14
|
+
[34mℹ[39m [2mdist/[22mcli.js.map [2m30.08 kB[22m [2m│ gzip: 7.78 kB[22m
|
|
15
|
+
[34mℹ[39m [2mdist/[22mcli.d.ts.map [2m 0.51 kB[22m [2m│ gzip: 0.29 kB[22m
|
|
16
16
|
[34mℹ[39m [2mdist/[22m[32m[1mcli.d.ts[22m[39m [2m 0.94 kB[22m [2m│ gzip: 0.30 kB[22m
|
|
17
|
-
[34mℹ[39m 4 files, total:
|
|
18
|
-
[32m✔[39m Build complete in [
|
|
17
|
+
[34mℹ[39m 4 files, total: 46.75 kB
|
|
18
|
+
[32m✔[39m Build complete in [32m13305ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
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
|
+
|
|
13
|
+
## 0.1.7
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- d330bb9: fix: change `bin` to `fragno-cli`
|
|
18
|
+
- Updated dependencies [70bdcb2]
|
|
19
|
+
- @fragno-dev/db@0.1.6
|
|
20
|
+
|
|
3
21
|
## 0.1.6
|
|
4
22
|
|
|
5
23
|
### 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":";;;;
|
|
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 {
|
|
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 [
|
|
23
|
-
|
|
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
|
|
76
|
-
|
|
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
|
|
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
|
|
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(`
|
|
236
|
+
console.log(`Database Information:`);
|
|
247
237
|
console.log("");
|
|
248
238
|
const namespaceHeader = "Namespace";
|
|
249
239
|
const versionHeader = "Schema";
|
|
@@ -263,8 +253,8 @@ const infoCommand = define({
|
|
|
263
253
|
console.log("");
|
|
264
254
|
if (!hasMigrationSupport) {
|
|
265
255
|
console.log("Note: These adapters do not support migrations.");
|
|
266
|
-
console.log("Use '
|
|
267
|
-
} else if (dbInfos.some((info) => info.pendingVersions && info.pendingVersions > 0)) console.log("Run '
|
|
256
|
+
console.log("Use 'fragno-cli db generate' to generate schema files.");
|
|
257
|
+
} else if (dbInfos.some((info) => info.pendingVersions && info.pendingVersions > 0)) console.log("Run 'fragno-cli db migrate <target>' to apply pending migrations.");
|
|
268
258
|
}
|
|
269
259
|
});
|
|
270
260
|
|
|
@@ -277,14 +267,14 @@ dbSubCommands.set("info", infoCommand);
|
|
|
277
267
|
function printDbHelp() {
|
|
278
268
|
console.log("Database management commands for Fragno");
|
|
279
269
|
console.log("");
|
|
280
|
-
console.log("Usage:
|
|
270
|
+
console.log("Usage: fragno-cli db <command> [options]");
|
|
281
271
|
console.log("");
|
|
282
272
|
console.log("Commands:");
|
|
283
273
|
console.log(" generate Generate schema files from FragnoDatabase definitions");
|
|
284
274
|
console.log(" migrate Run database migrations");
|
|
285
275
|
console.log(" info Display database information and migration status");
|
|
286
276
|
console.log("");
|
|
287
|
-
console.log("Run '
|
|
277
|
+
console.log("Run 'fragno-cli db <command> --help' for more information.");
|
|
288
278
|
}
|
|
289
279
|
const dbCommand = define({
|
|
290
280
|
name: "db",
|
|
@@ -294,17 +284,17 @@ const dbCommand = define({
|
|
|
294
284
|
const rootSubCommands = /* @__PURE__ */ new Map();
|
|
295
285
|
rootSubCommands.set("db", dbCommand);
|
|
296
286
|
const mainCommand = define({
|
|
297
|
-
name: "
|
|
287
|
+
name: "fragno-cli",
|
|
298
288
|
description: "Fragno CLI - Tools for working with Fragno fragments",
|
|
299
289
|
run: () => {
|
|
300
290
|
console.log("Fragno CLI - Tools for working with Fragno fragments");
|
|
301
291
|
console.log("");
|
|
302
|
-
console.log("Usage:
|
|
292
|
+
console.log("Usage: fragno-cli <command> [options]");
|
|
303
293
|
console.log("");
|
|
304
294
|
console.log("Commands:");
|
|
305
295
|
console.log(" db Database management commands");
|
|
306
296
|
console.log("");
|
|
307
|
-
console.log("Run '
|
|
297
|
+
console.log("Run 'fragno-cli <command> --help' for more information.");
|
|
308
298
|
}
|
|
309
299
|
});
|
|
310
300
|
if (import.meta.main) try {
|
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-dev/cli db generate' to generate schema files.\");\n } else {\n const hasPendingMigrations = dbInfos.some(\n (info) => info.pendingVersions && info.pendingVersions > 0,\n );\n if (hasPendingMigrations) {\n console.log(\"Run '@fragno-dev/cli db migrate <target>' to apply pending migrations.\");\n }\n }\n },\n});\n","#!/usr/bin/env node\n\nimport { cli, define, parseArgs, resolveArgs } from \"gunshi\";\nimport { generateCommand } from \"./commands/db/generate.js\";\nimport { migrateCommand } from \"./commands/db/migrate.js\";\nimport { infoCommand } from \"./commands/db/info.js\";\n\n// Create a Map of db sub-commands\nconst dbSubCommands = new Map();\ndbSubCommands.set(\"generate\", generateCommand);\ndbSubCommands.set(\"migrate\", migrateCommand);\ndbSubCommands.set(\"info\", infoCommand);\n\n// Helper function to print db command help\nfunction printDbHelp() {\n console.log(\"Database management commands for Fragno\");\n console.log(\"\");\n console.log(\"Usage: @fragno-dev/cli db <command> [options]\");\n console.log(\"\");\n console.log(\"Commands:\");\n console.log(\" generate Generate schema files from FragnoDatabase definitions\");\n console.log(\" migrate Run database migrations\");\n console.log(\" info Display database information and migration status\");\n console.log(\"\");\n console.log(\"Run '@fragno-dev/cli db <command> --help' for more information.\");\n}\n\n// Define the db command with type safety\nexport const dbCommand = define({\n name: \"db\",\n description: \"Database management commands\",\n run: printDbHelp,\n});\n\n// Create a Map of root sub-commands\nconst rootSubCommands = new Map();\nrootSubCommands.set(\"db\", dbCommand);\n\n// Define the main command with type safety\nexport const mainCommand = define({\n name: \"@fragno-dev/cli\",\n description: \"Fragno CLI - Tools for working with Fragno fragments\",\n run: () => {\n console.log(\"Fragno CLI - Tools for working with Fragno fragments\");\n console.log(\"\");\n console.log(\"Usage: @fragno-dev/cli <command> [options]\");\n console.log(\"\");\n console.log(\"Commands:\");\n console.log(\" db Database management commands\");\n console.log(\"\");\n console.log(\"Run '@fragno-dev/cli <command> --help' for more information.\");\n },\n});\n\nif (import.meta.main) {\n try {\n // Parse arguments to handle nested subcommands\n const args = process.argv.slice(2);\n\n // Check if we're calling a db subcommand directly\n if (args[0] === \"db\" && args.length > 1) {\n const subCommandName = args[1];\n\n // Check if it's a help request\n if (subCommandName === \"--help\" || subCommandName === \"-h\") {\n printDbHelp();\n process.exit(0);\n }\n\n const subCommand = dbSubCommands.get(subCommandName);\n\n if (!subCommand) {\n console.error(`Unknown command: ${subCommandName}`);\n console.log(\"\");\n printDbHelp();\n process.exit(1);\n }\n\n // Run the specific subcommand with its args\n const subArgs = args.slice(2);\n const isSubCommandHelp = subArgs.includes(\"--help\") || subArgs.includes(\"-h\");\n\n // Check for validation errors before running\n let hasValidationError = false;\n if (!isSubCommandHelp && subCommand.args) {\n const tokens = parseArgs(subArgs);\n const resolved = resolveArgs(subCommand.args, tokens);\n hasValidationError = !!resolved.error;\n }\n\n // Run the command (let gunshi handle printing errors/help)\n await cli(subArgs, subCommand);\n\n // Exit with error code if there was a validation error\n if (hasValidationError) {\n process.exit(1);\n }\n } else if (args[0] === \"db\") {\n // \"db\" command with no subcommand - show db help\n printDbHelp();\n } else {\n // Run the main CLI\n await cli(args, mainCommand, {\n subCommands: rootSubCommands,\n });\n }\n } catch (error) {\n console.error(\"Error:\", error instanceof Error ? error.message : error);\n process.exit(1);\n }\n}\n\nexport { generateCommand, migrateCommand, infoCommand };\n"],"mappings":"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"}
|
|
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.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"development": "./src/cli.ts",
|
|
@@ -10,21 +10,22 @@
|
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"bin": {
|
|
13
|
-
"
|
|
13
|
+
"fragno-cli": "./dist/cli.js"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=22"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^22",
|
|
20
|
-
"@fragno-private/
|
|
21
|
-
"@fragno-private/
|
|
20
|
+
"@fragno-private/vitest-config": "0.0.0",
|
|
21
|
+
"@fragno-private/typescript-config": "0.0.1"
|
|
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/
|
|
27
|
-
"@fragno-dev/
|
|
27
|
+
"@fragno-dev/db": "0.1.7",
|
|
28
|
+
"@fragno-dev/core": "0.1.3"
|
|
28
29
|
},
|
|
29
30
|
"main": "./dist/cli.js",
|
|
30
31
|
"module": "./dist/cli.js",
|
package/src/cli.ts
CHANGED
|
@@ -15,14 +15,14 @@ dbSubCommands.set("info", infoCommand);
|
|
|
15
15
|
function printDbHelp() {
|
|
16
16
|
console.log("Database management commands for Fragno");
|
|
17
17
|
console.log("");
|
|
18
|
-
console.log("Usage:
|
|
18
|
+
console.log("Usage: fragno-cli db <command> [options]");
|
|
19
19
|
console.log("");
|
|
20
20
|
console.log("Commands:");
|
|
21
21
|
console.log(" generate Generate schema files from FragnoDatabase definitions");
|
|
22
22
|
console.log(" migrate Run database migrations");
|
|
23
23
|
console.log(" info Display database information and migration status");
|
|
24
24
|
console.log("");
|
|
25
|
-
console.log("Run '
|
|
25
|
+
console.log("Run 'fragno-cli db <command> --help' for more information.");
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Define the db command with type safety
|
|
@@ -38,17 +38,17 @@ rootSubCommands.set("db", dbCommand);
|
|
|
38
38
|
|
|
39
39
|
// Define the main command with type safety
|
|
40
40
|
export const mainCommand = define({
|
|
41
|
-
name: "
|
|
41
|
+
name: "fragno-cli",
|
|
42
42
|
description: "Fragno CLI - Tools for working with Fragno fragments",
|
|
43
43
|
run: () => {
|
|
44
44
|
console.log("Fragno CLI - Tools for working with Fragno fragments");
|
|
45
45
|
console.log("");
|
|
46
|
-
console.log("Usage:
|
|
46
|
+
console.log("Usage: fragno-cli <command> [options]");
|
|
47
47
|
console.log("");
|
|
48
48
|
console.log("Commands:");
|
|
49
49
|
console.log(" db Database management commands");
|
|
50
50
|
console.log("");
|
|
51
|
-
console.log("Run '
|
|
51
|
+
console.log("Run 'fragno-cli <command> --help' for more information.");
|
|
52
52
|
},
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -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
|
-
//
|
|
45
|
-
const
|
|
42
|
+
// Resolve all target paths
|
|
43
|
+
const targetPaths = targets.map((target) => resolve(process.cwd(), target));
|
|
46
44
|
|
|
47
|
-
//
|
|
48
|
-
const
|
|
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 (!
|
|
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.`,
|
package/src/commands/db/info.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { define } from "gunshi";
|
|
3
|
-
import {
|
|
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
|
-
//
|
|
19
|
-
const
|
|
16
|
+
// Resolve all target paths
|
|
17
|
+
const targetPaths = targets.map((target) => resolve(process.cwd(), target));
|
|
20
18
|
|
|
21
|
-
//
|
|
22
|
-
const
|
|
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
|
|
@@ -165,13 +116,13 @@ export const infoCommand = define({
|
|
|
165
116
|
console.log("");
|
|
166
117
|
if (!hasMigrationSupport) {
|
|
167
118
|
console.log("Note: These adapters do not support migrations.");
|
|
168
|
-
console.log("Use '
|
|
119
|
+
console.log("Use 'fragno-cli db generate' to generate schema files.");
|
|
169
120
|
} else {
|
|
170
121
|
const hasPendingMigrations = dbInfos.some(
|
|
171
122
|
(info) => info.pendingVersions && info.pendingVersions > 0,
|
|
172
123
|
);
|
|
173
124
|
if (hasPendingMigrations) {
|
|
174
|
-
console.log("Run '
|
|
125
|
+
console.log("Run 'fragno-cli db migrate <target>' to apply pending migrations.");
|
|
175
126
|
}
|
|
176
127
|
}
|
|
177
128
|
},
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { define } from "gunshi";
|
|
3
|
-
import {
|
|
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
|
-
//
|
|
20
|
-
const
|
|
17
|
+
// Resolve all target paths
|
|
18
|
+
const targetPaths = targets.map((target) => resolve(process.cwd(), target));
|
|
21
19
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
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 [
|
|
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
|
|