fragment-ts 1.0.43 → 1.0.45

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.
Files changed (55) hide show
  1. package/dist/cli/commands/diagnostics.command.d.ts.map +1 -1
  2. package/dist/cli/commands/diagnostics.command.js +17 -73
  3. package/dist/cli/commands/diagnostics.command.js.map +1 -1
  4. package/dist/cli/commands/env.command.d.ts +10 -0
  5. package/dist/cli/commands/env.command.d.ts.map +1 -0
  6. package/dist/cli/commands/env.command.js +222 -0
  7. package/dist/cli/commands/env.command.js.map +1 -0
  8. package/dist/cli/commands/generate.command.js +9 -9
  9. package/dist/cli/commands/init.command.d.ts.map +1 -1
  10. package/dist/cli/commands/init.command.js +65 -35
  11. package/dist/cli/commands/init.command.js.map +1 -1
  12. package/dist/cli/commands/migrate.command.d.ts +14 -41
  13. package/dist/cli/commands/migrate.command.d.ts.map +1 -1
  14. package/dist/cli/commands/migrate.command.js +182 -424
  15. package/dist/cli/commands/migrate.command.js.map +1 -1
  16. package/dist/cli/commands/test.command.d.ts.map +1 -1
  17. package/dist/cli/commands/test.command.js +5 -51
  18. package/dist/cli/commands/test.command.js.map +1 -1
  19. package/dist/cli/index.js +50 -0
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/core/scanner/component-scanner.d.ts +0 -4
  22. package/dist/core/scanner/component-scanner.d.ts.map +1 -1
  23. package/dist/core/scanner/component-scanner.js +0 -15
  24. package/dist/core/scanner/component-scanner.js.map +1 -1
  25. package/dist/shared/config.utils.d.ts +0 -27
  26. package/dist/shared/config.utils.d.ts.map +1 -1
  27. package/dist/shared/config.utils.js +7 -38
  28. package/dist/shared/config.utils.js.map +1 -1
  29. package/dist/shared/env.utils.d.ts +6 -18
  30. package/dist/shared/env.utils.d.ts.map +1 -1
  31. package/dist/shared/env.utils.js +84 -39
  32. package/dist/shared/env.utils.js.map +1 -1
  33. package/dist/testing/runner.d.ts.map +1 -1
  34. package/dist/testing/runner.js +2 -12
  35. package/dist/testing/runner.js.map +1 -1
  36. package/dist/typeorm/typeorm-module.d.ts.map +1 -1
  37. package/dist/typeorm/typeorm-module.js +4 -16
  38. package/dist/typeorm/typeorm-module.js.map +1 -1
  39. package/dist/web/application.d.ts.map +1 -1
  40. package/dist/web/application.js +1 -20
  41. package/dist/web/application.js.map +1 -1
  42. package/package.json +2 -1
  43. package/src/cli/commands/diagnostics.command.ts +16 -87
  44. package/src/cli/commands/env.command.ts +224 -0
  45. package/src/cli/commands/generate.command.ts +9 -9
  46. package/src/cli/commands/init.command.ts +68 -37
  47. package/src/cli/commands/migrate.command.ts +244 -528
  48. package/src/cli/commands/test.command.ts +5 -61
  49. package/src/cli/index.ts +21 -0
  50. package/src/core/scanner/component-scanner.ts +0 -15
  51. package/src/shared/config.utils.ts +10 -54
  52. package/src/shared/env.utils.ts +50 -44
  53. package/src/testing/runner.ts +2 -11
  54. package/src/typeorm/typeorm-module.ts +5 -14
  55. package/src/web/application.ts +1 -21
@@ -3,22 +3,19 @@ import * as fs from "fs-extra";
3
3
  import * as path from "path";
4
4
  import chalk from "chalk";
5
5
  import ora from "ora";
6
- import { execSync, spawn } from "child_process";
7
- import { TypeORMModule } from "../../typeorm/typeorm-module";
8
6
  import { DataSource } from "typeorm";
7
+ import { TypeORMModule } from "../../typeorm/typeorm-module";
9
8
  import { ConfigUtils } from "../../shared/config.utils";
10
- import { EnvUtils } from "../../shared/env.utils";
9
+ import { TsConfigUtils } from "../../shared/tsconfig.utils";
10
+ import { globSync } from "glob";
11
11
 
12
12
  export class MigrateCommand {
13
13
  private static tsNodeAvailable: boolean | null = null;
14
- private static tsConfigExists: boolean | null = null;
15
14
 
16
15
  static register(program: Command): void {
17
16
  program
18
17
  .command("migrate")
19
- .description(
20
- "Run all pending migrations (auto-detect TypeScript/JavaScript)",
21
- )
18
+ .description("Run all pending migrations")
22
19
  .action(async () => {
23
20
  await this.runMigrations();
24
21
  });
@@ -95,257 +92,208 @@ export class MigrateCommand {
95
92
  });
96
93
  }
97
94
 
98
- // -----------------------------------------
99
- // DETECTION METHODS
100
- // -----------------------------------------
101
-
102
95
  /**
103
- * Check if ts-node is available in the user's project
96
+ * Detect if we should use TypeScript or JavaScript
104
97
  */
105
- private static isTsNodeAvailable(): boolean {
106
- if (this.tsNodeAvailable !== null) return this.tsNodeAvailable;
107
-
108
- try {
109
- // Check if ts-node is installed in the user's project
110
- require.resolve("ts-node");
111
- this.tsNodeAvailable = true;
112
- } catch {
113
- try {
114
- // Check if ts-node is installed globally or in parent modules
115
- require("ts-node");
116
- this.tsNodeAvailable = true;
117
- } catch {
118
- this.tsNodeAvailable = false;
119
- }
120
- }
121
-
122
- return this.tsNodeAvailable;
98
+ private static setupEnvironment(): boolean {
99
+ return this.shouldUseTypeScript();
123
100
  }
124
101
 
125
102
  /**
126
- * Check if tsconfig.json exists
103
+ * Detect if we should use TypeScript or JavaScript
104
+ * - Check if tsconfig.json exists AND has decorator support
105
+ * - Check if TypeScript files exist in include patterns
106
+ * - Check if ts-node is available
127
107
  */
128
- private static hasTsConfig(): boolean {
129
- if (this.tsConfigExists !== null) return this.tsConfigExists;
130
-
131
- this.tsConfigExists = fs.existsSync(
132
- path.join(process.cwd(), "tsconfig.json"),
133
- );
134
- return this.tsConfigExists;
135
- }
136
-
137
- /**
138
- * Detect if TypeScript files exist in the project
139
- */
140
- private static detectProjectType(): "typescript" | "javascript" | "mixed" {
141
- const srcDir = path.join(process.cwd(), "src");
142
- const hasSrcDir = fs.existsSync(srcDir);
108
+ private static shouldUseTypeScript(): boolean {
109
+ if (!TsConfigUtils.exists()) {
110
+ return false;
111
+ }
143
112
 
144
- const hasTsFiles =
145
- hasSrcDir && fs.readdirSync(srcDir).some((file) => file.endsWith(".ts"));
113
+ if (!TsConfigUtils.hasDecoratorSupport()) {
114
+ return false;
115
+ }
146
116
 
147
- const distDir = path.join(process.cwd(), "dist");
148
- const hasDistDir = fs.existsSync(distDir);
117
+ if (!this.isTsNodeAvailable()) {
118
+ return false;
119
+ }
149
120
 
150
- const hasJsFiles =
151
- hasDistDir &&
152
- fs.readdirSync(distDir).some((file) => file.endsWith(".js"));
121
+ const includePatterns = TsConfigUtils.getIncludePatterns();
122
+ const cwd = process.cwd();
153
123
 
154
- if (hasTsFiles && !hasJsFiles) return "typescript";
155
- if (!hasTsFiles && hasJsFiles) return "javascript";
156
- if (hasTsFiles && hasJsFiles) return "mixed";
124
+ for (const pattern of includePatterns) {
125
+ try {
126
+ const files = globSync(pattern, { cwd });
127
+ if (
128
+ files.some(
129
+ (file: string) => file.endsWith(".ts") && !file.endsWith(".d.ts"),
130
+ )
131
+ ) {
132
+ return true;
133
+ }
134
+ } catch {
135
+ // Ignore glob errors
136
+ }
137
+ }
157
138
 
158
- // Default to checking for tsconfig
159
- return this.hasTsConfig() ? "typescript" : "javascript";
139
+ return false;
160
140
  }
161
141
 
162
- /**
163
- * Determine whether to use TypeScript or JavaScript for migrations
164
- */
165
- private static shouldUseTypeScript(): boolean {
166
- const projectType = this.detectProjectType();
167
- const tsNodeAvailable = this.isTsNodeAvailable();
168
-
169
- // Use the same logic as EnvUtils for consistency
170
- const isDevMode = EnvUtils.isDevelopmentMode();
171
-
172
- // In development mode with ts-node available, use TypeScript
173
- if (isDevMode && tsNodeAvailable) {
174
- return true;
142
+ private static isTsNodeAvailable(): boolean {
143
+ if (this.tsNodeAvailable !== null) {
144
+ return this.tsNodeAvailable;
175
145
  }
176
146
 
177
- // In production mode, always use JavaScript
178
- if (!isDevMode) {
179
- return false;
147
+ try {
148
+ require.resolve("ts-node");
149
+ this.tsNodeAvailable = true;
150
+ } catch {
151
+ this.tsNodeAvailable = false;
180
152
  }
181
153
 
182
- // Fallback to project type detection
183
- return projectType === "typescript" && tsNodeAvailable;
154
+ return this.tsNodeAvailable;
184
155
  }
185
156
 
186
- /**
187
- * Register ts-node if needed
188
- */
189
157
  private static registerTsNodeIfNeeded(useTypeScript: boolean): void {
190
- if (!useTypeScript) return;
191
-
192
- try {
193
- // Register ts-node for TypeScript support
194
- require("ts-node/register");
195
-
196
- // If tsconfig-paths is available, register it too
197
- try {
198
- require("tsconfig-paths/register");
199
- } catch {
200
- // tsconfig-paths not available, that's okay
201
- }
202
-
203
- console.log(chalk.gray("šŸ“ Using ts-node for TypeScript migrations"));
204
- } catch (error) {
205
- console.error(
206
- chalk.yellow(
207
- "āš ļø Failed to register ts-node. Falling back to JavaScript.",
208
- ),
209
- );
158
+ if (useTypeScript && this.isTsNodeAvailable()) {
159
+ require("ts-node").register({
160
+ transpileOnly: true,
161
+ compilerOptions: {
162
+ module: "commonjs",
163
+ },
164
+ });
210
165
  }
211
166
  }
212
167
 
213
168
  /**
214
- * Get entity paths from fragment.json config
169
+ * Get entity and migration paths based on current mode
170
+ * Uses fragment.json config when available, otherwise infers from tsconfig
215
171
  */
216
- private static getEntityPathsFromConfig(): string[] {
172
+ private static getPaths(useTypeScript: boolean): {
173
+ entities: string[];
174
+ migrations: string[];
175
+ seeds: string;
176
+ } {
217
177
  const dbConfig = ConfigUtils.getDatabaseConfig();
218
- return dbConfig.entities || [];
219
- }
220
178
 
221
- /**
222
- * Get migration paths from fragment.json config
223
- */
224
- private static getMigrationPathsFromConfig(): string[] {
225
- const dbConfig = ConfigUtils.getDatabaseConfig();
226
- return dbConfig.migrations || [];
227
- }
179
+ const resolveExtension = (pattern: string): string => {
180
+ return useTypeScript
181
+ ? pattern.replace(/\.js$/, ".ts")
182
+ : pattern.replace(/\.ts$/, ".js");
183
+ };
184
+
185
+ if (dbConfig.entities || dbConfig.migrations) {
186
+ const entities = dbConfig.entities
187
+ ? dbConfig.entities.map(resolveExtension)
188
+ : [useTypeScript ? "src/**/*.entity.ts" : "dist/**/*.entity.js"];
189
+
190
+ const migrations = dbConfig.migrations
191
+ ? dbConfig.migrations.map(resolveExtension)
192
+ : [
193
+ useTypeScript
194
+ ? "src/migrations/**/*.ts"
195
+ : "dist/migrations/**/*.js",
196
+ ];
197
+
198
+ const migrationDir = this.extractMigrationDirectory(
199
+ migrations[0],
200
+ useTypeScript,
201
+ );
202
+ const seedsDir = path.join(migrationDir, "..", "seeds");
228
203
 
229
- /**
230
- * Get resolved entity paths based on current mode
231
- */
232
- private static getResolvedEntityPaths(useTypeScript: boolean): string[] {
233
- const configPaths = this.getEntityPathsFromConfig();
234
-
235
- if (configPaths.length > 0) {
236
- // Use configured paths but resolve based on current mode
237
- return configPaths.map((pattern) => {
238
- if (useTypeScript) {
239
- return pattern.replace(/\.js$/, ".ts");
240
- } else {
241
- return pattern.replace(/\.ts$/, ".js");
242
- }
243
- });
204
+ return { entities, migrations, seeds: seedsDir };
244
205
  }
245
206
 
246
- // Fallback to default paths
247
- return useTypeScript ? ["src/**/*.entity.ts"] : ["dist/**/*.entity.js"];
207
+ const rootDir = TsConfigUtils.getRootDir();
208
+ const outDir = TsConfigUtils.getOutDir();
209
+
210
+ const entities = [
211
+ useTypeScript ? `${rootDir}/**/*.entity.ts` : `${outDir}/**/*.entity.js`,
212
+ ];
213
+ const migrations = [
214
+ useTypeScript
215
+ ? `${rootDir}/migrations/**/*.ts`
216
+ : `${outDir}/migrations/**/*.js`,
217
+ ];
218
+ const seeds = useTypeScript ? `${rootDir}/seeds` : `${outDir}/seeds`;
219
+
220
+ return { entities, migrations, seeds };
248
221
  }
249
222
 
250
223
  /**
251
- * Get resolved migration paths based on current mode
224
+ * Extract the actual directory path from a glob pattern for file creation
252
225
  */
253
- private static getResolvedMigrationPaths(useTypeScript: boolean): string[] {
254
- const configPaths = this.getMigrationPathsFromConfig();
255
-
256
- if (configPaths.length > 0) {
257
- // Use configured paths but resolve based on current mode
258
- return configPaths.map((pattern) => {
259
- if (useTypeScript) {
260
- return pattern.replace(/\.js$/, ".ts");
261
- } else {
262
- return pattern.replace(/\.ts$/, ".js");
263
- }
264
- });
226
+ private static extractMigrationDirectory(
227
+ pattern: string,
228
+ useTypeScript: boolean,
229
+ ): string {
230
+ if (pattern.includes("/**/*")) {
231
+ return pattern.split("/**/*")[0];
265
232
  }
266
233
 
267
- // Fallback to default paths
268
- return useTypeScript
269
- ? ["src/migrations/**/*.ts"]
270
- : ["dist/migrations/**/*.js"];
271
- }
234
+ if (pattern.includes("/*.")) {
235
+ return pattern.split("/*")[0];
236
+ }
237
+
238
+ if (pattern.endsWith(".ts") || pattern.endsWith(".js")) {
239
+ return path.dirname(pattern);
240
+ }
272
241
 
273
- // -----------------------------------------
274
- // MIGRATION GENERATE
275
- // -----------------------------------------
242
+ return useTypeScript ? "src/migrations" : "dist/migrations";
243
+ }
276
244
 
277
245
  private static async generateMigration(
278
246
  nameOrPath: string | undefined,
279
247
  options: { name?: string },
280
248
  ): Promise<void> {
281
- const useTypeScript = this.shouldUseTypeScript();
282
- const spinner = ora("Generating migration...").start();
249
+ const useTypeScript = this.setupEnvironment();
250
+ const spinner = ora(
251
+ `Generating ${useTypeScript ? "TypeScript" : "JavaScript"} migration...`,
252
+ ).start();
283
253
  let dataSource: DataSource | null = null;
284
254
 
285
255
  try {
286
- // Register ts-node if using TypeScript
287
256
  this.registerTsNodeIfNeeded(useTypeScript);
288
257
 
289
- // Initialize DataSource with paths from fragment.json config
290
- const configOverride = {
291
- entities: this.getResolvedEntityPaths(useTypeScript),
292
- migrations: this.getResolvedMigrationPaths(useTypeScript),
293
- };
258
+ const { entities, migrations } = this.getPaths(useTypeScript);
259
+ const configOverride = { entities, migrations };
294
260
 
295
261
  dataSource = await TypeORMModule.initialize(configOverride);
296
262
  if (!dataSource?.isInitialized) {
297
263
  throw new Error("Failed to initialize DataSource");
298
264
  }
299
265
 
300
- // Verify entities are loaded
301
266
  this.verifyEntities(dataSource);
302
267
 
303
- // Setup migrations directory from config
304
- const migrationPaths = this.getResolvedMigrationPaths(useTypeScript);
305
- const migrationsDir = path.dirname(
306
- migrationPaths[0].replace("/**/*", ""),
268
+ const migrationsPattern = migrations[0];
269
+ const migrationsDir = this.extractMigrationDirectory(
270
+ migrationsPattern,
271
+ useTypeScript,
307
272
  );
308
273
  await fs.ensureDir(migrationsDir);
309
274
 
310
275
  const existingMigrations = fs.existsSync(migrationsDir)
311
276
  ? fs
312
277
  .readdirSync(migrationsDir)
313
- .filter((f) => f.endsWith(useTypeScript ? ".ts" : ".js"))
278
+ .filter(
279
+ (f: string) =>
280
+ f.endsWith(useTypeScript ? ".ts" : ".js") &&
281
+ !f.endsWith(".d.ts"),
282
+ )
314
283
  : [];
315
-
316
284
  const isFirstMigration = existingMigrations.length === 0;
317
285
 
318
- // Generate schema SQL by comparing entities with database
319
286
  spinner.text = "Analyzing schema changes...";
320
287
  const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
321
-
322
288
  const upQueries = sqlInMemory.upQueries || [];
323
289
  const downQueries = sqlInMemory.downQueries || [];
324
290
  const hasChanges = upQueries.length > 0;
325
291
 
326
- // Handle no changes scenario
327
292
  if (!hasChanges && !isFirstMigration) {
328
293
  spinner.info("No schema changes detected. Migration not generated.");
329
294
  return;
330
295
  }
331
296
 
332
- if (!hasChanges && isFirstMigration) {
333
- const helpMessage = useTypeScript
334
- ? " - Your entities are in the correct location\n" +
335
- " - Entity paths in fragment.json are correct\n" +
336
- " - Entities are decorated with @Entity()"
337
- : " - Your entities are compiled (run 'npm run build')\n" +
338
- " - Entity paths in fragment.json point to dist/**/*.entity.js\n" +
339
- " - Entities are decorated with @Entity()";
340
-
341
- spinner.warn(
342
- "No schema changes detected on first migration. Check that:\n" +
343
- helpMessage,
344
- );
345
- return;
346
- }
347
-
348
- // Determine migration name
349
297
  const migrationName =
350
298
  options.name ||
351
299
  (nameOrPath ? path.basename(nameOrPath).replace(/\.ts$/, "") : null) ||
@@ -353,18 +301,10 @@ export class MigrateCommand {
353
301
 
354
302
  const timestamp = Date.now();
355
303
  const className = `${migrationName}${timestamp}`;
356
-
357
- // Build queries
358
- spinner.text = "Building migration file...";
359
- const upStatements = upQueries.map((query) => {
360
- return this.buildQueryStatement(query);
361
- });
362
-
363
- const downStatements = downQueries.map((query) => {
364
- return this.buildQueryStatement(query);
365
- });
366
-
367
- // Generate migration file content
304
+ const upStatements = upQueries.map((q) => this.buildQueryStatement(q));
305
+ const downStatements = downQueries.map((q) =>
306
+ this.buildQueryStatement(q),
307
+ );
368
308
  const migrationContent = this.buildMigrationTemplate(
369
309
  className,
370
310
  upStatements,
@@ -372,193 +312,51 @@ export class MigrateCommand {
372
312
  isFirstMigration,
373
313
  );
374
314
 
375
- // Write migration file using config directory
376
315
  const fileExt = useTypeScript ? ".ts" : ".js";
377
316
  const fileName = `${timestamp}-${migrationName}${fileExt}`;
378
317
  const filePath = path.join(migrationsDir, fileName);
379
-
380
318
  await fs.writeFile(filePath, migrationContent, "utf-8");
381
319
 
382
- const fileType = useTypeScript ? "TypeScript" : "JavaScript";
383
320
  spinner.succeed(
384
- `${fileType} migration generated: ${chalk.cyan(fileName)}\n` +
385
- ` ${chalk.gray("Location:")} ${filePath}\n` +
386
- ` ${chalk.gray("Queries:")} ${upQueries.length} up, ${downQueries.length} down\n` +
387
- ` ${chalk.gray("Mode:")} ${useTypeScript ? "TypeScript (ts-node)" : "JavaScript"}`,
321
+ `${useTypeScript ? "TypeScript" : "JavaScript"} migration generated: ${chalk.cyan(fileName)}`,
388
322
  );
389
323
  } catch (error) {
390
324
  spinner.fail("Migration generation failed");
391
-
392
- if (error instanceof Error) {
393
- console.error(chalk.red("\nāŒ Error: " + error.message));
394
-
395
- if (error.message.includes("Cannot find module") && useTypeScript) {
396
- console.error(
397
- chalk.yellow(
398
- "\nšŸ’” Tip: Try one of these solutions:\n" +
399
- " 1. Install ts-node: npm install -D ts-node\n" +
400
- " 2. Compile your project first: npm run build\n" +
401
- " 3. Ensure your entities are in src/**/*.entity.ts",
402
- ),
403
- );
404
- }
405
-
406
- if (error.stack) {
407
- console.error(chalk.gray("\nStack trace:"));
408
- console.error(chalk.gray(error.stack));
409
- }
410
- } else {
411
- console.error(chalk.red(String(error)));
412
- }
413
-
325
+ console.error(
326
+ chalk.red(error instanceof Error ? error.message : String(error)),
327
+ );
414
328
  process.exit(1);
415
329
  } finally {
416
330
  if (dataSource?.isInitialized) {
417
331
  try {
418
332
  await dataSource.destroy();
419
- } catch (err) {
420
- // Ignore cleanup errors
421
- }
333
+ } catch {}
422
334
  }
423
335
  }
424
336
  }
425
337
 
426
- /**
427
- * Verify entities are loaded and provide helpful debugging
428
- */
429
- private static verifyEntities(dataSource: DataSource): void {
430
- const entities = dataSource.entityMetadatas;
431
-
432
- if (entities.length === 0) {
433
- throw new Error(
434
- "No entities found! Make sure:\n" +
435
- " 1. Your entities are decorated with @Entity()\n" +
436
- " 2. Entity paths in fragment.json are correct\n" +
437
- " 3. For JavaScript mode: run 'npm run build' to compile\n" +
438
- " 4. For TypeScript mode: entities should be in src/**/*.entity.ts",
439
- );
440
- }
441
-
442
- console.log(chalk.gray(`\nšŸ“¦ Loaded ${entities.length} entity/entities:`));
443
- entities.forEach((entity) => {
444
- console.log(chalk.gray(` - ${entity.name} (${entity.tableName})`));
445
- });
446
- console.log();
447
- }
448
-
449
- /**
450
- * Build a single query statement with proper escaping
451
- */
452
- private static buildQueryStatement(query: {
453
- query: string;
454
- parameters?: any[];
455
- }): string {
456
- // Escape backslashes, backticks, and dollar signs for template literals
457
- let sql = query.query
458
- .replace(/\\/g, "\\\\") // Escape backslashes first
459
- .replace(/`/g, "\\`") // Escape backticks
460
- .replace(/\$/g, "\\$") // Escape dollar signs
461
- .replace(/\r\n/g, " ") // Remove Windows line breaks
462
- .replace(/\n/g, " ") // Remove Unix line breaks
463
- .replace(/\s+/g, " ") // Collapse multiple spaces
464
- .trim();
465
-
466
- // Handle parameters if present
467
- if (query.parameters && query.parameters.length > 0) {
468
- const params = JSON.stringify(query.parameters);
469
- return `await queryRunner.query(\`${sql}\`, ${params});`;
470
- }
471
-
472
- return `await queryRunner.query(\`${sql}\`);`;
473
- }
474
-
475
- // -----------------------------------------
476
- // MIGRATION EXECUTION METHODS
477
- // -----------------------------------------
478
-
479
338
  private static async runMigrations(): Promise<void> {
480
- const useTypeScript = this.shouldUseTypeScript();
481
- const spinner = ora("Running migrations...").start();
339
+ const useTypeScript = this.setupEnvironment();
340
+ const spinner = ora(
341
+ `Running ${useTypeScript ? "TypeScript" : "JavaScript"} migrations...`,
342
+ ).start();
482
343
  let dataSource: DataSource | null = null;
483
344
 
484
345
  try {
485
- // Register ts-node if using TypeScript
486
346
  this.registerTsNodeIfNeeded(useTypeScript);
487
-
488
- // Initialize DataSource with paths from fragment.json config
489
- const configOverride = {
490
- entities: this.getResolvedEntityPaths(useTypeScript),
491
- migrations: this.getResolvedMigrationPaths(useTypeScript),
492
- };
347
+ const { entities, migrations } = this.getPaths(useTypeScript);
348
+ const configOverride = { entities, migrations };
493
349
 
494
350
  dataSource = await TypeORMModule.initialize(configOverride);
495
-
496
- // Check if migrations exist using config paths
497
- const migrationPaths = this.getResolvedMigrationPaths(useTypeScript);
498
- const migrationsDir = path.dirname(
499
- migrationPaths[0].replace("/**/*", ""),
500
- );
501
- const fileExt = useTypeScript ? ".ts" : ".js";
502
-
503
- const hasMigrations =
504
- fs.existsSync(migrationsDir) &&
505
- fs.readdirSync(migrationsDir).filter((f) => f.endsWith(fileExt))
506
- .length > 0;
507
-
508
- if (!hasMigrations) {
509
- const mode = useTypeScript ? "TypeScript" : "JavaScript";
510
- spinner.info(
511
- `No ${mode.toLowerCase()} migrations found in ${migrationsDir}.`,
512
- );
513
-
514
- if (useTypeScript && this.hasJavaScriptMigrations()) {
515
- console.log(
516
- chalk.yellow(
517
- `\nšŸ’” JavaScript migrations found. Try:\n` +
518
- ` 1. fragment migrate:run (in JavaScript mode)\n` +
519
- ` 2. Or compile your TypeScript: npm run build`,
520
- ),
521
- );
522
- } else if (!useTypeScript && this.hasTypeScriptMigrations()) {
523
- console.log(
524
- chalk.yellow(
525
- `\nšŸ’” TypeScript migrations found. Try:\n` +
526
- ` 1. Install ts-node: npm install -D ts-node\n` +
527
- ` 2. Then run: fragment migrate:run\n` +
528
- ` 3. Or compile first: npm run build`,
529
- ),
530
- );
531
- }
532
-
533
- return;
534
- }
535
-
536
- const mode = useTypeScript ? "TypeScript" : "JavaScript";
537
- spinner.text = `Running ${mode} migrations...`;
538
-
539
351
  await TypeORMModule.runMigrations();
540
- spinner.succeed(`${mode} migrations completed successfully`);
352
+ spinner.succeed(
353
+ `${useTypeScript ? "TypeScript" : "JavaScript"} migrations completed successfully`,
354
+ );
541
355
  } catch (error) {
542
356
  spinner.fail("Migration failed");
543
-
544
- if (error instanceof Error) {
545
- if (error.message.includes("Cannot find module") && useTypeScript) {
546
- console.error(chalk.red("\nāŒ TypeScript module not found."));
547
- console.error(
548
- chalk.yellow(
549
- "\nšŸ’” Try one of these solutions:\n" +
550
- " 1. Install ts-node: npm install -D ts-node\n" +
551
- " 2. Compile your TypeScript: npm run build\n" +
552
- " 3. Check that your migration files are in src/migrations/*.ts",
553
- ),
554
- );
555
- } else {
556
- console.error(chalk.red(error.message));
557
- }
558
- } else {
559
- console.error(chalk.red(String(error)));
560
- }
561
-
357
+ console.error(
358
+ chalk.red(error instanceof Error ? error.message : String(error)),
359
+ );
562
360
  process.exit(1);
563
361
  } finally {
564
362
  if (dataSource?.isInitialized) {
@@ -568,20 +366,13 @@ export class MigrateCommand {
568
366
  }
569
367
 
570
368
  private static async createMigration(name: string): Promise<void> {
571
- // Always create TypeScript migrations
572
- const useTypeScript = true;
369
+ const useTypeScript = this.setupEnvironment();
573
370
  const spinner = ora("Creating TypeScript migration...").start();
574
-
575
371
  try {
372
+ const { migrations } = this.getPaths(true);
373
+ const migrationsDir = this.extractMigrationDirectory(migrations[0], true);
576
374
  const timestamp = Date.now();
577
- const fileExt = useTypeScript ? ".ts" : ".js";
578
-
579
- // Get migration directory from config
580
- const migrationPaths = this.getResolvedMigrationPaths(useTypeScript);
581
- const migrationsDir = path.dirname(
582
- migrationPaths[0].replace("/**/*", ""),
583
- );
584
- const fileName = `${timestamp}-${name}${fileExt}`;
375
+ const fileName = `${timestamp}-${name}.ts`;
585
376
  const filePath = path.join(migrationsDir, fileName);
586
377
 
587
378
  const content = `import { MigrationInterface, QueryRunner } from 'typeorm';
@@ -595,16 +386,7 @@ export class ${name}${timestamp} implements MigrationInterface {
595
386
 
596
387
  await fs.ensureDir(path.dirname(filePath));
597
388
  await fs.writeFile(filePath, content);
598
-
599
389
  spinner.succeed(`TypeScript migration created: ${fileName}`);
600
- console.log(
601
- chalk.gray(
602
- `\nšŸ’” Note: Migration created as TypeScript file.\n` +
603
- ` To use it, either:\n` +
604
- ` 1. Install ts-node: npm install -D ts-node\n` +
605
- ` 2. Or compile to JavaScript: npm run build`,
606
- ),
607
- );
608
390
  } catch (error) {
609
391
  spinner.fail("Failed to create migration");
610
392
  console.error(
@@ -615,25 +397,22 @@ export class ${name}${timestamp} implements MigrationInterface {
615
397
  }
616
398
 
617
399
  private static async revertMigration(): Promise<void> {
618
- const useTypeScript = this.shouldUseTypeScript();
619
- const spinner = ora("Reverting migration...").start();
400
+ const useTypeScript = this.setupEnvironment();
401
+ const spinner = ora(
402
+ `Reverting ${useTypeScript ? "TypeScript" : "JavaScript"} migration...`,
403
+ ).start();
620
404
  let dataSource: DataSource | null = null;
621
405
 
622
406
  try {
623
- // Register ts-node if using TypeScript
624
407
  this.registerTsNodeIfNeeded(useTypeScript);
625
-
626
- // Initialize DataSource with paths from fragment.json config
627
- const configOverride = {
628
- entities: this.getResolvedEntityPaths(useTypeScript),
629
- migrations: this.getResolvedMigrationPaths(useTypeScript),
630
- };
408
+ const { entities, migrations } = this.getPaths(useTypeScript);
409
+ const configOverride = { entities, migrations };
631
410
 
632
411
  dataSource = await TypeORMModule.initialize(configOverride);
633
412
  await TypeORMModule.revertMigration();
634
-
635
- const mode = useTypeScript ? "TypeScript" : "JavaScript";
636
- spinner.succeed(`${mode} migration reverted successfully`);
413
+ spinner.succeed(
414
+ `${useTypeScript ? "TypeScript" : "JavaScript"} migration reverted successfully`,
415
+ );
637
416
  } catch (error) {
638
417
  spinner.fail("Revert failed");
639
418
  console.error(
@@ -648,26 +427,23 @@ export class ${name}${timestamp} implements MigrationInterface {
648
427
  }
649
428
 
650
429
  private static async refreshMigrations(): Promise<void> {
651
- const useTypeScript = this.shouldUseTypeScript();
652
- const spinner = ora("Refreshing migrations...").start();
430
+ const useTypeScript = this.setupEnvironment();
431
+ const spinner = ora(
432
+ `Refreshing ${useTypeScript ? "TypeScript" : "JavaScript"} migrations...`,
433
+ ).start();
653
434
  let dataSource: DataSource | null = null;
654
435
 
655
436
  try {
656
- // Register ts-node if using TypeScript
657
437
  this.registerTsNodeIfNeeded(useTypeScript);
658
-
659
- // Initialize DataSource with paths from fragment.json config
660
- const configOverride = {
661
- entities: this.getResolvedEntityPaths(useTypeScript),
662
- migrations: this.getResolvedMigrationPaths(useTypeScript),
663
- };
438
+ const { entities, migrations } = this.getPaths(useTypeScript);
439
+ const configOverride = { entities, migrations };
664
440
 
665
441
  dataSource = await TypeORMModule.initialize(configOverride);
666
442
  await TypeORMModule.dropSchema();
667
443
  await TypeORMModule.runMigrations();
668
-
669
- const mode = useTypeScript ? "TypeScript" : "JavaScript";
670
- spinner.succeed(`${mode} migrations refreshed successfully`);
444
+ spinner.succeed(
445
+ `${useTypeScript ? "TypeScript" : "JavaScript"} migrations refreshed successfully`,
446
+ );
671
447
  } catch (error) {
672
448
  spinner.fail("Refresh failed");
673
449
  console.error(
@@ -682,18 +458,13 @@ export class ${name}${timestamp} implements MigrationInterface {
682
458
  }
683
459
 
684
460
  private static async showStatus(): Promise<void> {
685
- const useTypeScript = this.shouldUseTypeScript();
461
+ const useTypeScript = this.setupEnvironment();
686
462
  let dataSource: DataSource | null = null;
687
463
 
688
464
  try {
689
- // Register ts-node if using TypeScript
690
465
  this.registerTsNodeIfNeeded(useTypeScript);
691
-
692
- // Initialize DataSource with paths from fragment.json config
693
- const configOverride = {
694
- entities: this.getResolvedEntityPaths(useTypeScript),
695
- migrations: this.getResolvedMigrationPaths(useTypeScript),
696
- };
466
+ const { entities, migrations } = this.getPaths(useTypeScript);
467
+ const configOverride = { entities, migrations };
697
468
 
698
469
  dataSource = await TypeORMModule.initialize(configOverride);
699
470
  const executedMigrations = await dataSource.query(
@@ -718,16 +489,16 @@ export class ${name}${timestamp} implements MigrationInterface {
718
489
  });
719
490
  }
720
491
 
721
- // Show migration directory from config
722
- const migrationPaths = this.getResolvedMigrationPaths(useTypeScript);
723
- const migrationsDir = path.dirname(
724
- migrationPaths[0].replace("/**/*", ""),
492
+ const migrationsPattern = migrations[0];
493
+ const migrationsDir = this.extractMigrationDirectory(
494
+ migrationsPattern,
495
+ useTypeScript,
725
496
  );
726
497
  const fileExt = useTypeScript ? ".ts" : ".js";
727
498
  if (fs.existsSync(migrationsDir)) {
728
499
  const files = fs
729
500
  .readdirSync(migrationsDir)
730
- .filter((f) => f.endsWith(fileExt));
501
+ .filter((f: string) => f.endsWith(fileExt));
731
502
  console.log(
732
503
  chalk.blue(
733
504
  `\n Available ${mode.toLowerCase()} migration files: ${files.length}`,
@@ -735,11 +506,7 @@ export class ${name}${timestamp} implements MigrationInterface {
735
506
  );
736
507
  }
737
508
 
738
- // Show detection info
739
- console.log(chalk.gray(`\n Mode detection:`));
740
- console.log(
741
- chalk.gray(` - Environment mode: ${EnvUtils.getEnvironmentMode()}`),
742
- );
509
+ console.log(chalk.gray(`\n Configuration:`));
743
510
  console.log(
744
511
  chalk.gray(` - ts-node available: ${this.isTsNodeAvailable()}`),
745
512
  );
@@ -761,18 +528,16 @@ export class ${name}${timestamp} implements MigrationInterface {
761
528
  }
762
529
 
763
530
  private static async syncSchema(): Promise<void> {
764
- const useTypeScript = this.shouldUseTypeScript();
765
- const spinner = ora("Synchronizing schema...").start();
531
+ const useTypeScript = this.setupEnvironment();
532
+ const spinner = ora(
533
+ `Synchronizing ${useTypeScript ? "TypeScript" : "JavaScript"} schema...`,
534
+ ).start();
766
535
  let dataSource: DataSource | null = null;
767
536
 
768
537
  try {
769
- // Register ts-node if using TypeScript
770
538
  this.registerTsNodeIfNeeded(useTypeScript);
771
-
772
- // Initialize DataSource with entity paths from config
773
- const configOverride = {
774
- entities: this.getResolvedEntityPaths(useTypeScript),
775
- };
539
+ const { entities } = this.getPaths(useTypeScript);
540
+ const configOverride = { entities };
776
541
 
777
542
  dataSource = await TypeORMModule.initialize(configOverride);
778
543
  await TypeORMModule.syncSchema();
@@ -791,18 +556,16 @@ export class ${name}${timestamp} implements MigrationInterface {
791
556
  }
792
557
 
793
558
  private static async dropSchema(): Promise<void> {
794
- const useTypeScript = this.shouldUseTypeScript();
795
- const spinner = ora("Dropping schema...").start();
559
+ const useTypeScript = this.setupEnvironment();
560
+ const spinner = ora(
561
+ `Dropping ${useTypeScript ? "TypeScript" : "JavaScript"} schema...`,
562
+ ).start();
796
563
  let dataSource: DataSource | null = null;
797
564
 
798
565
  try {
799
- // Register ts-node if using TypeScript
800
566
  this.registerTsNodeIfNeeded(useTypeScript);
801
-
802
- // Initialize DataSource with entity paths from config
803
- const configOverride = {
804
- entities: this.getResolvedEntityPaths(useTypeScript),
805
- };
567
+ const { entities } = this.getPaths(useTypeScript);
568
+ const configOverride = { entities };
806
569
 
807
570
  dataSource = await TypeORMModule.initialize(configOverride);
808
571
  await TypeORMModule.dropSchema();
@@ -821,47 +584,33 @@ export class ${name}${timestamp} implements MigrationInterface {
821
584
  }
822
585
 
823
586
  private static async runSeeds(): Promise<void> {
824
- const spinner = ora("Running seeds...").start();
825
-
587
+ const useTypeScript = this.setupEnvironment();
588
+ const spinner = ora(
589
+ `Running ${useTypeScript ? "TypeScript" : "JavaScript"} seeds...`,
590
+ ).start();
826
591
  try {
827
- // Try TypeScript first if available
828
- const useTypeScript = this.shouldUseTypeScript();
829
-
830
- // Get seed directory from config or default
831
- const dbConfig = ConfigUtils.getDatabaseConfig();
832
- let seedsDir = "";
592
+ const { seeds: seedsDir } = this.getPaths(useTypeScript);
833
593
 
834
- if (dbConfig.subscribers && dbConfig.subscribers.length > 0) {
835
- // Infer seeds directory from subscribers or migrations
836
- const migrationPaths = this.getResolvedMigrationPaths(useTypeScript);
837
- seedsDir = path.join(path.dirname(migrationPaths[0]), "..", "seeds");
838
- } else {
839
- // seedsDir = useTypeScript
840
- // ? path.join(process.cwd(), "src", "seeds")
841
- // : path.join(process.cwd(), "dist", "seeds");
842
- throw new Error("No seeds directory found");
594
+ if (!fs.existsSync(seedsDir)) {
595
+ spinner.info(`No seeds directory found at ${seedsDir}`);
596
+ return;
843
597
  }
844
598
 
845
- if (fs.existsSync(seedsDir)) {
846
- const files = fs.readdirSync(seedsDir);
599
+ const files = fs.readdirSync(seedsDir);
600
+ for (const file of files) {
601
+ const isTsFile = file.endsWith(".ts") && !file.endsWith(".d.ts");
602
+ const isJsFile = file.endsWith(".js");
847
603
 
848
- for (const file of files) {
849
- if (useTypeScript && file.endsWith(".ts")) {
850
- // For TypeScript, we need to register ts-node
604
+ if ((useTypeScript && isTsFile) || (!useTypeScript && isJsFile)) {
605
+ if (useTypeScript) {
851
606
  this.registerTsNodeIfNeeded(true);
852
- const seedModule = require(path.join(seedsDir, file));
853
- if (seedModule.default?.run) {
854
- await seedModule.default.run();
855
- }
856
- } else if (!useTypeScript && file.endsWith(".js")) {
857
- const seedModule = require(path.join(seedsDir, file));
858
- if (seedModule.default?.run) {
859
- await seedModule.default.run();
860
- }
607
+ }
608
+ const seedModule = require(path.join(seedsDir, file));
609
+ if (seedModule.default?.run) {
610
+ await seedModule.default.run();
861
611
  }
862
612
  }
863
613
  }
864
-
865
614
  spinner.succeed("Seeds completed successfully");
866
615
  } catch (error) {
867
616
  spinner.fail("Seed failed");
@@ -873,21 +622,10 @@ export class ${name}${timestamp} implements MigrationInterface {
873
622
  }
874
623
 
875
624
  private static async createSeed(name: string): Promise<void> {
876
- // Always create TypeScript seeds
625
+ const useTypeScript = this.setupEnvironment();
877
626
  const spinner = ora("Creating TypeScript seed...").start();
878
-
879
627
  try {
880
- // Get seed directory from config or default
881
- const dbConfig = ConfigUtils.getDatabaseConfig();
882
- let seedsDir = "";
883
-
884
- if (dbConfig.subscribers && dbConfig.subscribers.length > 0) {
885
- const migrationPaths = this.getResolvedMigrationPaths(true);
886
- seedsDir = path.join(path.dirname(migrationPaths[0]), "..", "seeds");
887
- } else {
888
- seedsDir = path.join(process.cwd(), "src", "seeds");
889
- }
890
-
628
+ const { seeds: seedsDir } = this.getPaths(true);
891
629
  const fileName = `${name}.seed.ts`;
892
630
  const filePath = path.join(seedsDir, fileName);
893
631
 
@@ -895,12 +633,10 @@ export class ${name}${timestamp} implements MigrationInterface {
895
633
  static async run() {
896
634
  console.log('Running ${name} seed...');
897
635
  }
898
- }
899
- `;
636
+ }`;
900
637
 
901
638
  await fs.ensureDir(path.dirname(filePath));
902
639
  await fs.writeFile(filePath, content);
903
-
904
640
  spinner.succeed(`TypeScript seed created: ${fileName}`);
905
641
  } catch (error) {
906
642
  spinner.fail("Failed to create seed");
@@ -911,51 +647,38 @@ export class ${name}${timestamp} implements MigrationInterface {
911
647
  }
912
648
  }
913
649
 
914
- // Keep existing helper methods unchanged
915
- private static hasTypeScriptMigrations(): boolean {
916
- const srcMigrationsDir = path.join(process.cwd(), "src", "migrations");
917
-
918
- if (!fs.existsSync(srcMigrationsDir)) return false;
919
-
920
- const tsFiles = fs
921
- .readdirSync(srcMigrationsDir)
922
- .filter((file) => file.endsWith(".ts"));
923
-
924
- return tsFiles.length > 0;
925
- }
926
-
927
- private static hasJavaScriptMigrations(): boolean {
928
- const distMigrationsDir = path.join(process.cwd(), "dist", "migrations");
929
-
930
- if (!fs.existsSync(distMigrationsDir)) return false;
931
-
932
- const jsFiles = fs
933
- .readdirSync(distMigrationsDir)
934
- .filter((file) => file.endsWith(".js"));
935
-
936
- return jsFiles.length > 0;
650
+ private static verifyEntities(dataSource: DataSource): void {
651
+ const entities = dataSource.entityMetadatas;
652
+ if (entities.length === 0) {
653
+ throw new Error("No entities found!");
654
+ }
655
+ console.log(chalk.gray(`\nšŸ“¦ Loaded ${entities.length} entity/entities:`));
656
+ entities.forEach((entity) => {
657
+ console.log(chalk.gray(` - ${entity.name} (${entity.tableName})`));
658
+ });
659
+ console.log();
937
660
  }
938
661
 
939
- private static findEntityFiles(dir: string, files: string[] = []): string[] {
940
- const items = fs.readdirSync(dir);
941
-
942
- for (const item of items) {
943
- const fullPath = path.join(dir, item);
944
- const stat = fs.statSync(fullPath);
662
+ private static buildQueryStatement(query: {
663
+ query: string;
664
+ parameters?: any[];
665
+ }): string {
666
+ let sql = query.query
667
+ .replace(/\\/g, "\\\\")
668
+ .replace(/`/g, "\\`")
669
+ .replace(/\$/g, "\\$")
670
+ .replace(/\r\n/g, " ")
671
+ .replace(/\n/g, " ")
672
+ .replace(/\s+/g, " ")
673
+ .trim();
945
674
 
946
- if (stat.isDirectory()) {
947
- this.findEntityFiles(fullPath, files);
948
- } else if (item.endsWith(".entity.js")) {
949
- files.push(fullPath.replace(process.cwd(), "."));
950
- }
675
+ if (query.parameters && query.parameters.length > 0) {
676
+ const params = JSON.stringify(query.parameters);
677
+ return `await queryRunner.query(\`${sql}\`, ${params});`;
951
678
  }
952
-
953
- return files;
679
+ return `await queryRunner.query(\`${sql}\`);`;
954
680
  }
955
681
 
956
- /**
957
- * Build the complete migration file template
958
- */
959
682
  private static buildMigrationTemplate(
960
683
  className: string,
961
684
  upStatements: string[],
@@ -966,20 +689,14 @@ export class ${name}${timestamp} implements MigrationInterface {
966
689
  upStatements.length > 0
967
690
  ? upStatements.map((s) => ` ${s}`).join("\n")
968
691
  : " // No changes";
969
-
970
692
  const downBody =
971
693
  downStatements.length > 0
972
694
  ? downStatements.map((s) => ` ${s}`).join("\n")
973
695
  : " // No changes";
974
696
 
975
- const comment = isFirstMigration
976
- ? " // Initial migration - creates all tables\n"
977
- : "";
978
-
979
- return `import { MigrationInterface, QueryRunner } from "typeorm";
697
+ return `import { MigrationInterface, QueryRunner } from "fragment-ts";
980
698
 
981
699
  export class ${className} implements MigrationInterface {
982
- ${comment}
983
700
  public async up(queryRunner: QueryRunner): Promise<void> {
984
701
  ${upBody}
985
702
  }
@@ -987,7 +704,6 @@ ${upBody}
987
704
  public async down(queryRunner: QueryRunner): Promise<void> {
988
705
  ${downBody}
989
706
  }
990
- }
991
- `;
707
+ }`;
992
708
  }
993
709
  }