fragment-ts 1.0.45 โ 1.0.47
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/README.md +96 -91
- package/dist/cli/commands/build.command.d.ts.map +1 -1
- package/dist/cli/commands/build.command.js +12 -19
- package/dist/cli/commands/build.command.js.map +1 -1
- package/dist/cli/commands/diagnostics.command.d.ts.map +1 -1
- package/dist/cli/commands/diagnostics.command.js +68 -14
- package/dist/cli/commands/diagnostics.command.js.map +1 -1
- package/dist/cli/commands/env.config.d.ts +10 -0
- package/dist/cli/commands/env.config.d.ts.map +1 -0
- package/dist/cli/commands/env.config.js +202 -0
- package/dist/cli/commands/env.config.js.map +1 -0
- package/dist/cli/commands/generate.command.js +9 -9
- package/dist/cli/commands/init.command.d.ts.map +1 -1
- package/dist/cli/commands/init.command.js +16 -59
- package/dist/cli/commands/init.command.js.map +1 -1
- package/dist/cli/commands/migrate.command.d.ts +5 -4
- package/dist/cli/commands/migrate.command.d.ts.map +1 -1
- package/dist/cli/commands/migrate.command.js +34 -9
- package/dist/cli/commands/migrate.command.js.map +1 -1
- package/dist/cli/commands/serve.command.d.ts.map +1 -1
- package/dist/cli/commands/serve.command.js +74 -38
- package/dist/cli/commands/serve.command.js.map +1 -1
- package/dist/cli/commands/test.command.d.ts.map +1 -1
- package/dist/cli/commands/test.command.js +21 -9
- package/dist/cli/commands/test.command.js.map +1 -1
- package/dist/cli/index.js +3 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/core/scanner/component-scanner.d.ts +4 -0
- package/dist/core/scanner/component-scanner.d.ts.map +1 -1
- package/dist/core/scanner/component-scanner.js +15 -0
- package/dist/core/scanner/component-scanner.js.map +1 -1
- package/dist/shared/config.utils.d.ts +2 -3
- package/dist/shared/config.utils.d.ts.map +1 -1
- package/dist/shared/config.utils.js +2 -3
- package/dist/shared/config.utils.js.map +1 -1
- package/dist/shared/env.utils.d.ts +6 -6
- package/dist/shared/env.utils.d.ts.map +1 -1
- package/dist/shared/env.utils.js +25 -84
- package/dist/shared/env.utils.js.map +1 -1
- package/dist/testing/runner.d.ts.map +1 -1
- package/dist/testing/runner.js +12 -2
- package/dist/testing/runner.js.map +1 -1
- package/dist/typeorm/typeorm-module.d.ts.map +1 -1
- package/dist/typeorm/typeorm-module.js +3 -6
- package/dist/typeorm/typeorm-module.js.map +1 -1
- package/dist/web/application.d.ts.map +1 -1
- package/dist/web/application.js +20 -1
- package/dist/web/application.js.map +1 -1
- package/examples/blog-api/package-lock.json +0 -38
- package/package.json +3 -3
- package/src/cli/commands/build.command.ts +13 -29
- package/src/cli/commands/diagnostics.command.ts +74 -13
- package/src/cli/commands/{env.command.ts โ env.config.ts} +9 -32
- package/src/cli/commands/generate.command.ts +9 -9
- package/src/cli/commands/init.command.ts +17 -63
- package/src/cli/commands/migrate.command.ts +37 -9
- package/src/cli/commands/serve.command.ts +205 -170
- package/src/cli/commands/test.command.ts +30 -8
- package/src/cli/index.ts +3 -12
- package/src/core/scanner/component-scanner.ts +15 -0
- package/src/shared/config.utils.ts +5 -6
- package/src/shared/env.utils.ts +27 -55
- package/src/testing/runner.ts +11 -2
- package/src/typeorm/typeorm-module.ts +4 -18
- package/src/web/application.ts +21 -1
|
@@ -6,6 +6,7 @@ import ora from "ora";
|
|
|
6
6
|
import { DataSource } from "typeorm";
|
|
7
7
|
import { TypeORMModule } from "../../typeorm/typeorm-module";
|
|
8
8
|
import { ConfigUtils } from "../../shared/config.utils";
|
|
9
|
+
import { EnvUtils } from "../../shared/env.utils";
|
|
9
10
|
import { TsConfigUtils } from "../../shared/tsconfig.utils";
|
|
10
11
|
import { globSync } from "glob";
|
|
11
12
|
|
|
@@ -100,12 +101,18 @@ export class MigrateCommand {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
/**
|
|
103
|
-
*
|
|
104
|
-
* -
|
|
105
|
-
* -
|
|
106
|
-
* -
|
|
104
|
+
* Use TypeScript only if:
|
|
105
|
+
* - NODE_ENV !== 'production'
|
|
106
|
+
* - tsconfig.json exists with decorators enabled
|
|
107
|
+
* - ts-node is available
|
|
108
|
+
* - TypeScript files exist
|
|
107
109
|
*/
|
|
108
110
|
private static shouldUseTypeScript(): boolean {
|
|
111
|
+
// Never use TS in production
|
|
112
|
+
if (EnvUtils.getEnvironmentMode() === "production") {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
109
116
|
if (!TsConfigUtils.exists()) {
|
|
110
117
|
return false;
|
|
111
118
|
}
|
|
@@ -123,7 +130,9 @@ export class MigrateCommand {
|
|
|
123
130
|
|
|
124
131
|
for (const pattern of includePatterns) {
|
|
125
132
|
try {
|
|
126
|
-
|
|
133
|
+
// Normalize pattern to forward slashes for Windows compatibility
|
|
134
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
135
|
+
const files = globSync(normalizedPattern, { cwd });
|
|
127
136
|
if (
|
|
128
137
|
files.some(
|
|
129
138
|
(file: string) => file.endsWith(".ts") && !file.endsWith(".d.ts"),
|
|
@@ -176,12 +185,14 @@ export class MigrateCommand {
|
|
|
176
185
|
} {
|
|
177
186
|
const dbConfig = ConfigUtils.getDatabaseConfig();
|
|
178
187
|
|
|
188
|
+
// Helper to resolve extension based on mode
|
|
179
189
|
const resolveExtension = (pattern: string): string => {
|
|
180
190
|
return useTypeScript
|
|
181
191
|
? pattern.replace(/\.js$/, ".ts")
|
|
182
192
|
: pattern.replace(/\.ts$/, ".js");
|
|
183
193
|
};
|
|
184
194
|
|
|
195
|
+
// If fragment.json has explicit paths, use them
|
|
185
196
|
if (dbConfig.entities || dbConfig.migrations) {
|
|
186
197
|
const entities = dbConfig.entities
|
|
187
198
|
? dbConfig.entities.map(resolveExtension)
|
|
@@ -195,6 +206,7 @@ export class MigrateCommand {
|
|
|
195
206
|
: "dist/migrations/**/*.js",
|
|
196
207
|
];
|
|
197
208
|
|
|
209
|
+
// Extract migration directory for file creation
|
|
198
210
|
const migrationDir = this.extractMigrationDirectory(
|
|
199
211
|
migrations[0],
|
|
200
212
|
useTypeScript,
|
|
@@ -204,6 +216,7 @@ export class MigrateCommand {
|
|
|
204
216
|
return { entities, migrations, seeds: seedsDir };
|
|
205
217
|
}
|
|
206
218
|
|
|
219
|
+
// No fragment.json config - infer from tsconfig
|
|
207
220
|
const rootDir = TsConfigUtils.getRootDir();
|
|
208
221
|
const outDir = TsConfigUtils.getOutDir();
|
|
209
222
|
|
|
@@ -227,18 +240,23 @@ export class MigrateCommand {
|
|
|
227
240
|
pattern: string,
|
|
228
241
|
useTypeScript: boolean,
|
|
229
242
|
): string {
|
|
243
|
+
// Handle common patterns
|
|
230
244
|
if (pattern.includes("/**/*")) {
|
|
245
|
+
// Pattern like "src/migrations/**/*.ts" โ "src/migrations"
|
|
231
246
|
return pattern.split("/**/*")[0];
|
|
232
247
|
}
|
|
233
248
|
|
|
234
249
|
if (pattern.includes("/*.")) {
|
|
250
|
+
// Pattern like "src/migrations/*.ts" โ "src/migrations"
|
|
235
251
|
return pattern.split("/*")[0];
|
|
236
252
|
}
|
|
237
253
|
|
|
254
|
+
// Fallback: assume it's a directory path
|
|
238
255
|
if (pattern.endsWith(".ts") || pattern.endsWith(".js")) {
|
|
239
256
|
return path.dirname(pattern);
|
|
240
257
|
}
|
|
241
258
|
|
|
259
|
+
// Default fallback
|
|
242
260
|
return useTypeScript ? "src/migrations" : "dist/migrations";
|
|
243
261
|
}
|
|
244
262
|
|
|
@@ -265,6 +283,7 @@ export class MigrateCommand {
|
|
|
265
283
|
|
|
266
284
|
this.verifyEntities(dataSource);
|
|
267
285
|
|
|
286
|
+
// Setup migrations directory - FIXED: Proper directory extraction
|
|
268
287
|
const migrationsPattern = migrations[0];
|
|
269
288
|
const migrationsDir = this.extractMigrationDirectory(
|
|
270
289
|
migrationsPattern,
|
|
@@ -283,6 +302,7 @@ export class MigrateCommand {
|
|
|
283
302
|
: [];
|
|
284
303
|
const isFirstMigration = existingMigrations.length === 0;
|
|
285
304
|
|
|
305
|
+
// Generate schema changes
|
|
286
306
|
spinner.text = "Analyzing schema changes...";
|
|
287
307
|
const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
|
|
288
308
|
const upQueries = sqlInMemory.upQueries || [];
|
|
@@ -294,6 +314,7 @@ export class MigrateCommand {
|
|
|
294
314
|
return;
|
|
295
315
|
}
|
|
296
316
|
|
|
317
|
+
// Create migration
|
|
297
318
|
const migrationName =
|
|
298
319
|
options.name ||
|
|
299
320
|
(nameOrPath ? path.basename(nameOrPath).replace(/\.ts$/, "") : null) ||
|
|
@@ -366,10 +387,11 @@ export class MigrateCommand {
|
|
|
366
387
|
}
|
|
367
388
|
|
|
368
389
|
private static async createMigration(name: string): Promise<void> {
|
|
369
|
-
|
|
390
|
+
// Always create TypeScript migrations (user can compile if needed)
|
|
391
|
+
const useTypeScript = true; // Force TS for creation
|
|
370
392
|
const spinner = ora("Creating TypeScript migration...").start();
|
|
371
393
|
try {
|
|
372
|
-
const { migrations } = this.getPaths(true);
|
|
394
|
+
const { migrations } = this.getPaths(true); // Always use TS for creation
|
|
373
395
|
const migrationsDir = this.extractMigrationDirectory(migrations[0], true);
|
|
374
396
|
const timestamp = Date.now();
|
|
375
397
|
const fileName = `${timestamp}-${name}.ts`;
|
|
@@ -489,6 +511,7 @@ export class ${name}${timestamp} implements MigrationInterface {
|
|
|
489
511
|
});
|
|
490
512
|
}
|
|
491
513
|
|
|
514
|
+
// Show migration directory from config
|
|
492
515
|
const migrationsPattern = migrations[0];
|
|
493
516
|
const migrationsDir = this.extractMigrationDirectory(
|
|
494
517
|
migrationsPattern,
|
|
@@ -506,7 +529,11 @@ export class ${name}${timestamp} implements MigrationInterface {
|
|
|
506
529
|
);
|
|
507
530
|
}
|
|
508
531
|
|
|
532
|
+
// Show detection info
|
|
509
533
|
console.log(chalk.gray(`\n Configuration:`));
|
|
534
|
+
console.log(
|
|
535
|
+
chalk.gray(` - Environment mode: ${EnvUtils.getEnvironmentMode()}`),
|
|
536
|
+
);
|
|
510
537
|
console.log(
|
|
511
538
|
chalk.gray(` - ts-node available: ${this.isTsNodeAvailable()}`),
|
|
512
539
|
);
|
|
@@ -622,10 +649,10 @@ export class ${name}${timestamp} implements MigrationInterface {
|
|
|
622
649
|
}
|
|
623
650
|
|
|
624
651
|
private static async createSeed(name: string): Promise<void> {
|
|
625
|
-
const useTypeScript =
|
|
652
|
+
const useTypeScript = true; // Always create TS seeds
|
|
626
653
|
const spinner = ora("Creating TypeScript seed...").start();
|
|
627
654
|
try {
|
|
628
|
-
const { seeds: seedsDir } = this.getPaths(true);
|
|
655
|
+
const { seeds: seedsDir } = this.getPaths(true); // Always create TS seeds
|
|
629
656
|
const fileName = `${name}.seed.ts`;
|
|
630
657
|
const filePath = path.join(seedsDir, fileName);
|
|
631
658
|
|
|
@@ -647,6 +674,7 @@ export class ${name}${timestamp} implements MigrationInterface {
|
|
|
647
674
|
}
|
|
648
675
|
}
|
|
649
676
|
|
|
677
|
+
// Helper methods remain the same
|
|
650
678
|
private static verifyEntities(dataSource: DataSource): void {
|
|
651
679
|
const entities = dataSource.entityMetadatas;
|
|
652
680
|
if (entities.length === 0) {
|
|
@@ -5,196 +5,231 @@ import * as fs from "fs";
|
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import { TsConfigUtils } from "../../shared/tsconfig.utils";
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
export class ServeCommand {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
static register(program: Command): void {
|
|
10
|
+
program
|
|
11
|
+
.command("serve")
|
|
12
|
+
.description("Start the development server")
|
|
13
|
+
.option("-w, --watch", "Watch for changes", true)
|
|
14
|
+
.option("-p, --port <port>", "Port to run on", "3000")
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
await ServeCommand.execute(options);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async execute(options: any): Promise<void> {
|
|
21
|
+
// If NODE_ENV=production, delegate to npm start
|
|
22
|
+
if (process.env.NODE_ENV === "production") {
|
|
23
|
+
const pkg = require(path.join(process.cwd(), "package.json"));
|
|
24
|
+
if (!pkg.scripts?.start) {
|
|
25
|
+
console.error(
|
|
26
|
+
chalk.red("โ package.json missing 'start' script for production"),
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(chalk.blue("๐ Running production server via npm start..."));
|
|
32
|
+
|
|
33
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const proc = spawn(npmCmd, ["start"], {
|
|
37
|
+
stdio: "inherit",
|
|
38
|
+
env: { ...process.env, PORT: options.port },
|
|
39
|
+
});
|
|
40
|
+
proc.on("exit", (code) => process.exit(code || 0));
|
|
41
|
+
return;
|
|
42
|
+
} catch {
|
|
43
|
+
console.error(chalk.red("Failed to run npm start"));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
19
46
|
}
|
|
20
47
|
|
|
21
|
-
|
|
22
|
-
console.log(chalk.blue("๐ Starting Fragment development server...\n"));
|
|
48
|
+
console.log(chalk.blue("๐ Starting Fragment development server...\n"));
|
|
23
49
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const mainFile = path.join(srcDir, "main.ts");
|
|
50
|
+
const srcDir = TsConfigUtils.getRootDir();
|
|
51
|
+
const mainFile = path.join(srcDir, "main.ts");
|
|
27
52
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
if (!fs.existsSync(mainFile)) {
|
|
54
|
+
console.error(
|
|
55
|
+
chalk.red(
|
|
56
|
+
`Main file not found at ${path.relative(process.cwd(), mainFile)}`,
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
62
|
+
const tsNode = this.findTsNode();
|
|
63
|
+
if (!tsNode) {
|
|
64
|
+
console.error(chalk.red("ts-node not found. Install it:"));
|
|
65
|
+
console.log(chalk.cyan(" npm install --save-dev ts-node"));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
44
68
|
|
|
45
|
-
|
|
46
|
-
|
|
69
|
+
let appProcess: ChildProcess | null = null;
|
|
70
|
+
let isRestarting = false;
|
|
47
71
|
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
const startServer = () => {
|
|
73
|
+
if (isRestarting) return;
|
|
50
74
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
75
|
+
appProcess = spawn(tsNode, [mainFile], {
|
|
76
|
+
stdio: "inherit",
|
|
77
|
+
env: { ...process.env, PORT: options.port },
|
|
78
|
+
});
|
|
56
79
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
appProcess.on("error", (error) => {
|
|
81
|
+
console.error(chalk.red(`Failed to start server: ${error.message}`));
|
|
82
|
+
});
|
|
60
83
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
84
|
+
appProcess.on("exit", (code) => {
|
|
85
|
+
if (!isRestarting && code !== null && code !== 0) {
|
|
86
|
+
console.error(chalk.red(`Server exited with code ${code}`));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const killServer = (): Promise<void> => {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
if (!appProcess || appProcess.killed) {
|
|
94
|
+
resolve();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
67
97
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const timeout = setTimeout(() => {
|
|
76
|
-
if (appProcess && !appProcess.killed) {
|
|
77
|
-
console.log(chalk.yellow("Force killing server..."));
|
|
78
|
-
appProcess.kill("SIGKILL");
|
|
79
|
-
}
|
|
80
|
-
resolve();
|
|
81
|
-
}, 3000);
|
|
82
|
-
|
|
83
|
-
appProcess.once("exit", () => {
|
|
84
|
-
clearTimeout(timeout);
|
|
85
|
-
appProcess = null;
|
|
86
|
-
resolve();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
appProcess.kill("SIGTERM");
|
|
98
|
+
if (process.platform === "win32") {
|
|
99
|
+
try {
|
|
100
|
+
execSync(`taskkill /pid ${appProcess.pid} /T /F`, {
|
|
101
|
+
stdio: "ignore",
|
|
90
102
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
chalk.yellow(
|
|
100
|
-
`\n๐ File changed: ${path.relative(process.cwd(), filePath)}`
|
|
101
|
-
)
|
|
102
|
-
);
|
|
103
|
+
} catch {}
|
|
104
|
+
appProcess = null;
|
|
105
|
+
resolve();
|
|
106
|
+
} else {
|
|
107
|
+
const timeout = setTimeout(() => {
|
|
108
|
+
if (appProcess && !appProcess.killed) {
|
|
109
|
+
console.log(chalk.yellow("Force killing server..."));
|
|
110
|
+
appProcess.kill("SIGKILL");
|
|
103
111
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await killServer();
|
|
107
|
-
|
|
108
|
-
// Wait a bit for port to be released
|
|
109
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
112
|
+
resolve();
|
|
113
|
+
}, 3000);
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
appProcess.once("exit", () => {
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
appProcess = null;
|
|
118
|
+
resolve();
|
|
119
|
+
});
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
startServer();
|
|
117
|
-
|
|
118
|
-
// Watch for changes
|
|
119
|
-
if (options.watch) {
|
|
120
|
-
try {
|
|
121
|
-
const chokidar = await import("chokidar");
|
|
122
|
-
|
|
123
|
-
// Use source directory from tsconfig
|
|
124
|
-
const watchPattern = path.join(srcDir, "**/*.ts");
|
|
125
|
-
|
|
126
|
-
const watcher = chokidar.watch(watchPattern, {
|
|
127
|
-
ignored: /(^|[/\\])\../,
|
|
128
|
-
persistent: true,
|
|
129
|
-
ignoreInitial: true,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
let debounceTimer: NodeJS.Timeout | null = null;
|
|
133
|
-
|
|
134
|
-
watcher.on("change", (filePath) => {
|
|
135
|
-
// Debounce rapid changes
|
|
136
|
-
if (debounceTimer) {
|
|
137
|
-
clearTimeout(debounceTimer);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
debounceTimer = setTimeout(() => {
|
|
141
|
-
restartServer(filePath);
|
|
142
|
-
}, 300);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
watcher.on("error", (error) => {
|
|
146
|
-
console.error(chalk.red(`Watcher error: ${error}`));
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Cleanup on exit
|
|
150
|
-
const cleanup = async () => {
|
|
151
|
-
console.log(chalk.yellow("\n\n๐ Shutting down..."));
|
|
152
|
-
watcher.close();
|
|
153
|
-
await killServer();
|
|
154
|
-
process.exit(0);
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
process.on("SIGINT", cleanup);
|
|
158
|
-
process.on("SIGTERM", cleanup);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
console.error(
|
|
161
|
-
chalk.red(
|
|
162
|
-
"Failed to start file watcher. Install chokidar: npm install chokidar"
|
|
163
|
-
)
|
|
164
|
-
);
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
// No watch mode - just handle cleanup
|
|
169
|
-
const cleanup = async () => {
|
|
170
|
-
await killServer();
|
|
171
|
-
process.exit(0);
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
process.on("SIGINT", cleanup);
|
|
175
|
-
process.on("SIGTERM", cleanup);
|
|
121
|
+
appProcess.kill("SIGTERM");
|
|
176
122
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const restartServer = async (filePath?: string) => {
|
|
127
|
+
if (isRestarting) return;
|
|
128
|
+
isRestarting = true;
|
|
129
|
+
|
|
130
|
+
if (filePath) {
|
|
131
|
+
console.log(
|
|
132
|
+
chalk.yellow(
|
|
133
|
+
`\n๐ File changed: ${path.relative(process.cwd(), filePath)}`,
|
|
134
|
+
),
|
|
186
135
|
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(chalk.blue("Restarting server...\n"));
|
|
139
|
+
await killServer();
|
|
140
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
141
|
+
isRestarting = false;
|
|
142
|
+
startServer();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
startServer();
|
|
146
|
+
|
|
147
|
+
if (options.watch) {
|
|
148
|
+
try {
|
|
149
|
+
const chokidar = await import("chokidar");
|
|
150
|
+
const watchPattern = path.join(srcDir, "**/*.ts");
|
|
151
|
+
|
|
152
|
+
const watcher = chokidar.watch(watchPattern, {
|
|
153
|
+
ignored: /(^|[/\\])\../,
|
|
154
|
+
persistent: true,
|
|
155
|
+
ignoreInitial: true,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
let debounceTimer: NodeJS.Timeout | null = null;
|
|
159
|
+
|
|
160
|
+
watcher.on("change", (filePath) => {
|
|
161
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
162
|
+
debounceTimer = setTimeout(() => restartServer(filePath), 300);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
watcher.on("error", (error) => {
|
|
166
|
+
console.error(chalk.red(`Watcher error: ${error}`));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const cleanup = async () => {
|
|
170
|
+
console.log(chalk.yellow("\n\n๐ Shutting down..."));
|
|
171
|
+
await watcher.close();
|
|
172
|
+
await killServer();
|
|
173
|
+
process.exit(0);
|
|
174
|
+
};
|
|
187
175
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
176
|
+
process.on("exit", cleanup);
|
|
177
|
+
process.on("SIGINT", () => process.exit(0));
|
|
178
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
191
179
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
180
|
+
if (process.platform === "win32") {
|
|
181
|
+
const rl = require("readline").createInterface({
|
|
182
|
+
input: process.stdin,
|
|
183
|
+
output: process.stdout,
|
|
184
|
+
});
|
|
185
|
+
rl.on("close", () => process.exit(0));
|
|
198
186
|
}
|
|
187
|
+
} catch {
|
|
188
|
+
console.error(
|
|
189
|
+
chalk.red(
|
|
190
|
+
"Failed to start file watcher. Install chokidar: npm install chokidar",
|
|
191
|
+
),
|
|
192
|
+
);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
const cleanup = async () => {
|
|
197
|
+
await killServer();
|
|
198
|
+
process.exit(0);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
process.on("exit", cleanup);
|
|
202
|
+
process.on("SIGINT", () => process.exit(0));
|
|
203
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
204
|
+
|
|
205
|
+
if (process.platform === "win32") {
|
|
206
|
+
const rl = require("readline").createInterface({
|
|
207
|
+
input: process.stdin,
|
|
208
|
+
output: process.stdout,
|
|
209
|
+
});
|
|
210
|
+
rl.on("close", () => process.exit(0));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private static findTsNode(): string | null {
|
|
216
|
+
const binName = process.platform === "win32" ? "ts-node.cmd" : "ts-node";
|
|
217
|
+
const localTsNode = path.join(
|
|
218
|
+
process.cwd(),
|
|
219
|
+
"node_modules",
|
|
220
|
+
".bin",
|
|
221
|
+
binName,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (fs.existsSync(localTsNode)) {
|
|
225
|
+
return localTsNode;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
execSync("ts-node --version", { stdio: "ignore" });
|
|
230
|
+
return binName;
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
199
233
|
}
|
|
234
|
+
}
|
|
200
235
|
}
|
|
@@ -23,27 +23,49 @@ export class TestCommand {
|
|
|
23
23
|
|
|
24
24
|
private static async runTests(options: any): Promise<void> {
|
|
25
25
|
console.log(chalk.blue("\n๐งช Running Fragment Tests...\n"));
|
|
26
|
+
|
|
26
27
|
const cwd = process.cwd();
|
|
27
28
|
const hasSource = fs.existsSync(path.join(cwd, "src"));
|
|
28
29
|
const hasDist = fs.existsSync(path.join(cwd, "dist"));
|
|
29
30
|
|
|
31
|
+
// Determine mode based on NODE_ENV
|
|
32
|
+
const isProduction = EnvUtils.getEnvironmentMode() === "production";
|
|
30
33
|
let useTypeScript: boolean;
|
|
34
|
+
let mode: string;
|
|
31
35
|
let basePath: string;
|
|
32
36
|
let pattern: string;
|
|
33
37
|
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
if (isProduction) {
|
|
39
|
+
if (!hasDist) {
|
|
40
|
+
console.log(
|
|
41
|
+
chalk.red(
|
|
42
|
+
"Production mode detected but dist/ directory not found. Run: fragment build",
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
39
47
|
useTypeScript = false;
|
|
48
|
+
mode = "production (dist/)";
|
|
40
49
|
basePath = "dist";
|
|
41
|
-
pattern =
|
|
50
|
+
pattern = options.pattern.replace(/\.ts$/, ".js") || "**/*.spec.js";
|
|
42
51
|
} else {
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
if (!hasSource) {
|
|
53
|
+
console.log(
|
|
54
|
+
chalk.red(
|
|
55
|
+
"Development mode detected but src/ directory not found. Run: fragment init",
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
useTypeScript = true;
|
|
61
|
+
mode = "development (src/)";
|
|
62
|
+
basePath = "src";
|
|
63
|
+
pattern = options.pattern || "**/*.spec.ts";
|
|
45
64
|
}
|
|
46
65
|
|
|
66
|
+
console.log(chalk.gray(` Mode: ${mode}`));
|
|
67
|
+
console.log(chalk.gray(` Pattern: ${basePath}/${pattern}`));
|
|
68
|
+
|
|
47
69
|
// Check if we need to use ts-node for TypeScript files
|
|
48
70
|
const hasTsConfig = TsConfigUtils.exists();
|
|
49
71
|
|
package/src/cli/index.ts
CHANGED
|
@@ -4,21 +4,13 @@ import * as path from "path";
|
|
|
4
4
|
|
|
5
5
|
const cwd = process.cwd();
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
".env", // base
|
|
10
|
-
".env.local", // gitignored local overrides
|
|
11
|
-
".env.prod", // production (or use .env.staging, etc.)
|
|
12
|
-
]
|
|
13
|
-
.map((file) => path.join(cwd, file))
|
|
7
|
+
const envFiles = [".env", ".env.local"]
|
|
8
|
+
.map((f) => path.join(cwd, f))
|
|
14
9
|
.filter(fs.existsSync);
|
|
15
10
|
|
|
16
|
-
envFiles.forEach((
|
|
17
|
-
dotenv.config({ path: file });
|
|
18
|
-
});
|
|
11
|
+
envFiles.forEach((f) => dotenv.config({ path: f }));
|
|
19
12
|
|
|
20
13
|
import { Command } from "commander";
|
|
21
|
-
import { EnvCommand } from "./commands/env.command";
|
|
22
14
|
import { InitCommand } from "./commands/init.command";
|
|
23
15
|
import { GenerateCommand } from "./commands/generate.command";
|
|
24
16
|
import { ServeCommand } from "./commands/serve.command";
|
|
@@ -37,7 +29,6 @@ program
|
|
|
37
29
|
.version("1.0.0");
|
|
38
30
|
|
|
39
31
|
// Register all commands
|
|
40
|
-
EnvCommand.register(program);
|
|
41
32
|
InitCommand.register(program);
|
|
42
33
|
GenerateCommand.register(program);
|
|
43
34
|
ServeCommand.register(program);
|
|
@@ -196,4 +196,19 @@ export class ComponentScanner {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Auto-scan based on current environment mode
|
|
202
|
+
*/
|
|
203
|
+
static async autoScan(): Promise<void> {
|
|
204
|
+
// Detect environment using the same logic as other components
|
|
205
|
+
const isDevMode = EnvUtils.isDevelopmentMode();;
|
|
206
|
+
|
|
207
|
+
if (isDevMode) {
|
|
208
|
+
await this.scanSource();
|
|
209
|
+
} else {
|
|
210
|
+
await this.scan();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
199
214
|
}
|