fragment-ts 1.0.34 → 1.0.35

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 (72) hide show
  1. package/API.md +248 -38
  2. package/DOCS.md +327 -63
  3. package/NewCLIGENERATECOMMANDS.txt +5 -0
  4. package/README.md +168 -3
  5. package/USAGE.md +395 -2
  6. package/dist/cli/commands/build.command.d.ts.map +1 -1
  7. package/dist/cli/commands/build.command.js +20 -8
  8. package/dist/cli/commands/build.command.js.map +1 -1
  9. package/dist/cli/commands/diagnostics.command.d.ts +1 -2
  10. package/dist/cli/commands/diagnostics.command.d.ts.map +1 -1
  11. package/dist/cli/commands/diagnostics.command.js +37 -23
  12. package/dist/cli/commands/diagnostics.command.js.map +1 -1
  13. package/dist/cli/commands/generate.command.d.ts +5 -1
  14. package/dist/cli/commands/generate.command.d.ts.map +1 -1
  15. package/dist/cli/commands/generate.command.js +171 -39
  16. package/dist/cli/commands/generate.command.js.map +1 -1
  17. package/dist/cli/commands/init.command.d.ts.map +1 -1
  18. package/dist/cli/commands/init.command.js +98 -28
  19. package/dist/cli/commands/init.command.js.map +1 -1
  20. package/dist/cli/commands/migrate.command.d.ts +10 -17
  21. package/dist/cli/commands/migrate.command.d.ts.map +1 -1
  22. package/dist/cli/commands/migrate.command.js +133 -170
  23. package/dist/cli/commands/migrate.command.js.map +1 -1
  24. package/dist/cli/commands/serve.command.d.ts.map +1 -1
  25. package/dist/cli/commands/serve.command.js +9 -4
  26. package/dist/cli/commands/serve.command.js.map +1 -1
  27. package/dist/cli/commands/test.command.d.ts.map +1 -1
  28. package/dist/cli/commands/test.command.js +24 -6
  29. package/dist/cli/commands/test.command.js.map +1 -1
  30. package/dist/core/scanner/component-scanner.d.ts +12 -0
  31. package/dist/core/scanner/component-scanner.d.ts.map +1 -1
  32. package/dist/core/scanner/component-scanner.js +72 -14
  33. package/dist/core/scanner/component-scanner.js.map +1 -1
  34. package/dist/shared/config.utils.d.ts +58 -0
  35. package/dist/shared/config.utils.d.ts.map +1 -0
  36. package/dist/shared/config.utils.js +137 -0
  37. package/dist/shared/config.utils.js.map +1 -0
  38. package/dist/shared/env.utils.d.ts +27 -0
  39. package/dist/shared/env.utils.d.ts.map +1 -0
  40. package/dist/shared/env.utils.js +68 -0
  41. package/dist/shared/env.utils.js.map +1 -0
  42. package/dist/shared/tsconfig.utils.d.ts +122 -0
  43. package/dist/shared/tsconfig.utils.d.ts.map +1 -0
  44. package/dist/shared/tsconfig.utils.js +305 -0
  45. package/dist/shared/tsconfig.utils.js.map +1 -0
  46. package/dist/testing/runner.d.ts +9 -1
  47. package/dist/testing/runner.d.ts.map +1 -1
  48. package/dist/testing/runner.js +50 -10
  49. package/dist/testing/runner.js.map +1 -1
  50. package/dist/typeorm/typeorm-module.d.ts +1 -0
  51. package/dist/typeorm/typeorm-module.d.ts.map +1 -1
  52. package/dist/typeorm/typeorm-module.js +193 -85
  53. package/dist/typeorm/typeorm-module.js.map +1 -1
  54. package/dist/web/application.d.ts +0 -1
  55. package/dist/web/application.d.ts.map +1 -1
  56. package/dist/web/application.js +4 -26
  57. package/dist/web/application.js.map +1 -1
  58. package/package.json +1 -1
  59. package/src/cli/commands/build.command.ts +24 -9
  60. package/src/cli/commands/diagnostics.command.ts +42 -30
  61. package/src/cli/commands/generate.command.ts +212 -52
  62. package/src/cli/commands/init.command.ts +100 -29
  63. package/src/cli/commands/migrate.command.ts +145 -198
  64. package/src/cli/commands/serve.command.ts +181 -170
  65. package/src/cli/commands/test.command.ts +25 -11
  66. package/src/core/scanner/component-scanner.ts +100 -18
  67. package/src/shared/config.utils.ts +148 -0
  68. package/src/shared/env.utils.ts +72 -0
  69. package/src/shared/tsconfig.utils.ts +360 -0
  70. package/src/testing/runner.ts +62 -14
  71. package/src/typeorm/typeorm-module.ts +209 -86
  72. package/src/web/application.ts +4 -33
@@ -0,0 +1,148 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { EnvUtils } from "./env.utils";
4
+
5
+ // Define the fragment.json structure
6
+ interface FragmentDatabaseConfig {
7
+ type?: string;
8
+ host?: string;
9
+ port?: number;
10
+ username?: string;
11
+ password?: string;
12
+ database?: string;
13
+ synchronize?: boolean;
14
+ logging?: boolean;
15
+ entities?: string[];
16
+ migrations?: string[];
17
+ subscribers?: string[];
18
+ pool?: any;
19
+ maxQueryExecutionTime?: number;
20
+ [key: string]: any; // Allow additional database-specific properties
21
+ }
22
+
23
+ interface FragmentConfig {
24
+ development?: {
25
+ database?: FragmentDatabaseConfig;
26
+ };
27
+ production?: {
28
+ database?: FragmentDatabaseConfig;
29
+ };
30
+ // Legacy flat structure (for backward compatibility)
31
+ database?: FragmentDatabaseConfig;
32
+ }
33
+
34
+ export class ConfigUtils {
35
+ private static configCache: FragmentConfig | null = null;
36
+
37
+ /**
38
+ * Load and parse fragment.json with caching
39
+ */
40
+ private static loadConfig(): FragmentConfig {
41
+ if (this.configCache) {
42
+ return this.configCache;
43
+ }
44
+
45
+ const configPath = path.join(process.cwd(), "fragment.json");
46
+
47
+ if (!fs.existsSync(configPath)) {
48
+ this.configCache = {};
49
+ return this.configCache;
50
+ }
51
+
52
+ try {
53
+ const raw = fs.readFileSync(configPath, "utf-8");
54
+ this.configCache = JSON.parse(raw);
55
+
56
+ if (!this.configCache) {
57
+ this.configCache = {};
58
+ }
59
+ return this.configCache;
60
+ } catch (error) {
61
+ console.error("❌ Error loading fragment.json:", error);
62
+ this.configCache = {};
63
+ return this.configCache;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Interpolate environment variables in config values
69
+ */
70
+ static interpolateEnvVars(value: any): any {
71
+ if (typeof value === "string") {
72
+ return value.replace(
73
+ /\$\{([^}]+)\}/g,
74
+ (_, varName) => EnvUtils.getString(varName) || "",
75
+ );
76
+ }
77
+ if (Array.isArray(value)) {
78
+ return value.map((item) => this.interpolateEnvVars(item));
79
+ }
80
+ if (value !== null && typeof value === "object") {
81
+ const result: any = {};
82
+ for (const key in value) {
83
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
84
+ result[key] = this.interpolateEnvVars(value[key]);
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+ return value;
90
+ }
91
+
92
+ /**
93
+ * Get the current database configuration based on environment
94
+ */
95
+ static getDatabaseConfig(): FragmentDatabaseConfig {
96
+ const config = this.loadConfig();
97
+ const mode = EnvUtils.getEnvironmentMode();
98
+
99
+ // Try environment-specific config first
100
+ if (config[mode]?.database) {
101
+ const interpolated = this.interpolateEnvVars(config[mode].database);
102
+ return interpolated;
103
+ }
104
+
105
+ // Fallback to legacy flat structure
106
+ if (config.database) {
107
+ const interpolated = this.interpolateEnvVars(config.database);
108
+ return interpolated;
109
+ }
110
+
111
+ // Return empty config if none found
112
+ return {};
113
+ }
114
+
115
+ /**
116
+ * Get a specific database configuration property
117
+ */
118
+ static getDatabaseProperty<T>(property: string, defaultValue: T): T {
119
+ const dbConfig = this.getDatabaseConfig();
120
+ return (
121
+ dbConfig[property] !== undefined ? dbConfig[property] : defaultValue
122
+ ) as T;
123
+ }
124
+
125
+ /**
126
+ * Get the full fragment configuration
127
+ */
128
+ static getFullConfig(): FragmentConfig {
129
+ return this.loadConfig();
130
+ }
131
+
132
+ /**
133
+ * Check if database feature is enabled
134
+ */
135
+ static isDatabaseEnabled(): boolean {
136
+ const config = this.loadConfig();
137
+ const mode = EnvUtils.getEnvironmentMode();
138
+
139
+ return !!(config[mode]?.database || config.database);
140
+ }
141
+
142
+ /**
143
+ * Clear the configuration cache (useful for testing)
144
+ */
145
+ static clearCache(): void {
146
+ this.configCache = null;
147
+ }
148
+ }
@@ -0,0 +1,72 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ export class EnvUtils {
5
+ /**
6
+ * Get a string environment variable with optional default
7
+ */
8
+ static getString(key: string, defaultValue?: string): string {
9
+ const value = process.env[key];
10
+ return value !== undefined ? value : (defaultValue ?? "");
11
+ }
12
+
13
+ /**
14
+ * Get a boolean environment variable (supports 'true'/'false', '1'/'0')
15
+ */
16
+ static getBoolean(key: string, defaultValue: boolean = false): boolean {
17
+ const value = process.env[key];
18
+ if (value === undefined) return defaultValue;
19
+
20
+ return ["true", "1", "yes", "on"].includes(value.toLowerCase());
21
+ }
22
+
23
+ /**
24
+ * Get a number environment variable
25
+ */
26
+ static getNumber(key: string, defaultValue: number = 0): number {
27
+ const value = process.env[key];
28
+ if (value === undefined) return defaultValue;
29
+
30
+ const num = Number(value);
31
+ return isNaN(num) ? defaultValue : num;
32
+ }
33
+
34
+ /**
35
+ * Get a parsed JSON environment variable
36
+ */
37
+ static getJson<T>(key: string, defaultValue: T): T {
38
+ const value = process.env[key];
39
+ if (value === undefined) return defaultValue;
40
+
41
+ try {
42
+ return JSON.parse(value) as T;
43
+ } catch {
44
+ console.warn(`⚠️ Invalid JSON in env var ${key}, using default`);
45
+ return defaultValue;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Check if running in development mode
51
+ */
52
+ static isDevelopmentMode(): boolean {
53
+ // Check explicit FRAGMENT_DEV_MODE
54
+ if (process.env.FRAGMENT_DEV_MODE !== undefined) {
55
+ return process.env.FRAGMENT_DEV_MODE === "true";
56
+ }
57
+
58
+ // Check NODE_ENV
59
+ if (process.env.NODE_ENV) {
60
+ return process.env.NODE_ENV === "development";
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Get current environment mode ('development' or 'production')
68
+ */
69
+ static getEnvironmentMode(): "development" | "production" {
70
+ return this.isDevelopmentMode() ? "development" : "production";
71
+ }
72
+ }
@@ -0,0 +1,360 @@
1
+ // src/utils/tsconfig.utils.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+
5
+ // Define the tsconfig.json structure (partial, covering common properties)
6
+ interface TsConfigCompilerOptions {
7
+ target?: string;
8
+ module?: string;
9
+ moduleResolution?: string;
10
+ outDir?: string;
11
+ rootDir?: string;
12
+ baseUrl?: string;
13
+ paths?: Record<string, string[]>;
14
+ declaration?: boolean;
15
+ sourceMap?: boolean;
16
+ removeComments?: boolean;
17
+ emitDecoratorMetadata?: boolean;
18
+ experimentalDecorators?: boolean;
19
+ allowSyntheticDefaultImports?: boolean;
20
+ esModuleInterop?: boolean;
21
+ strict?: boolean;
22
+ noImplicitAny?: boolean;
23
+ strictNullChecks?: boolean;
24
+ strictFunctionTypes?: boolean;
25
+ strictBindCallApply?: boolean;
26
+ strictPropertyInitialization?: boolean;
27
+ noImplicitThis?: boolean;
28
+ alwaysStrict?: boolean;
29
+ noUnusedLocals?: boolean;
30
+ noUnusedParameters?: boolean;
31
+ noImplicitReturns?: boolean;
32
+ noFallthroughCasesInSwitch?: boolean;
33
+ skipLibCheck?: boolean;
34
+ forceConsistentCasingInFileNames?: boolean;
35
+ [key: string]: any; // Allow additional compiler options
36
+ }
37
+
38
+ interface TsConfig {
39
+ compilerOptions?: TsConfigCompilerOptions;
40
+ include?: string[];
41
+ exclude?: string[];
42
+ files?: string[];
43
+ references?: Array<{ path: string }>;
44
+ extends?: string;
45
+ [key: string]: any; // Allow additional top-level properties
46
+ }
47
+
48
+ export class TsConfigUtils {
49
+ private static configCache: TsConfig | null = null;
50
+ private static readonly DEFAULT_TSCONFIG_PATHS = [
51
+ "tsconfig.json",
52
+ "tsconfig.build.json",
53
+ "tsconfig.app.json",
54
+ ];
55
+
56
+ /**
57
+ * Load and parse tsconfig.json with caching
58
+ * @param configPath Optional custom path to tsconfig file
59
+ */
60
+ private static loadConfig(configPath?: string): TsConfig {
61
+ if (this.configCache) {
62
+ return this.configCache;
63
+ }
64
+
65
+ let finalConfigPath: string | null = null;
66
+
67
+ if (configPath) {
68
+ // Use provided path
69
+ finalConfigPath = path.isAbsolute(configPath)
70
+ ? configPath
71
+ : path.join(process.cwd(), configPath);
72
+ } else {
73
+ // Try default paths
74
+ for (const defaultPath of this.DEFAULT_TSCONFIG_PATHS) {
75
+ const fullPath = path.join(process.cwd(), defaultPath);
76
+ if (fs.existsSync(fullPath)) {
77
+ finalConfigPath = fullPath;
78
+ break;
79
+ }
80
+ }
81
+ }
82
+
83
+ if (!finalConfigPath || !fs.existsSync(finalConfigPath)) {
84
+ this.configCache = {};
85
+ return this.configCache;
86
+ }
87
+
88
+ try {
89
+ const raw = fs.readFileSync(finalConfigPath, "utf-8");
90
+ const parsed = JSON.parse(raw);
91
+
92
+ // Handle "extends" by merging parent config
93
+ if (parsed.extends) {
94
+ const parentConfig = this.loadParentConfig(
95
+ parsed.extends,
96
+ path.dirname(finalConfigPath),
97
+ );
98
+ this.configCache = this.mergeConfigs(parentConfig, parsed);
99
+ } else {
100
+ this.configCache = parsed;
101
+ }
102
+
103
+ if (!this.configCache) {
104
+ this.configCache = {};
105
+ }
106
+
107
+ return this.configCache;
108
+ } catch (error) {
109
+ console.error("❌ Error loading tsconfig.json:", error);
110
+ this.configCache = {};
111
+ return this.configCache;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Load parent config when "extends" is used
117
+ */
118
+ private static loadParentConfig(
119
+ extendsPath: string,
120
+ basePath: string,
121
+ ): TsConfig {
122
+ // Handle relative paths and node_modules
123
+ let parentPath: string;
124
+
125
+ if (extendsPath.startsWith(".")) {
126
+ // Relative path
127
+ parentPath = path.resolve(basePath, extendsPath);
128
+ if (!parentPath.endsWith(".json")) {
129
+ parentPath += ".json";
130
+ }
131
+ } else if (extendsPath.startsWith("/")) {
132
+ // Absolute path
133
+ parentPath = extendsPath;
134
+ if (!parentPath.endsWith(".json")) {
135
+ parentPath += ".json";
136
+ }
137
+ } else {
138
+ // Assume node_modules package
139
+ try {
140
+ // Try to resolve as a package
141
+ const packageJsonPath = require.resolve(`${extendsPath}/package.json`, {
142
+ paths: [basePath],
143
+ });
144
+ const packageDir = path.dirname(packageJsonPath);
145
+ parentPath = path.join(packageDir, "tsconfig.json");
146
+ } catch {
147
+ // Fallback to direct path
148
+ parentPath = path.join(basePath, "node_modules", extendsPath);
149
+ if (!parentPath.endsWith(".json")) {
150
+ parentPath += ".json";
151
+ }
152
+ }
153
+ }
154
+
155
+ if (fs.existsSync(parentPath)) {
156
+ try {
157
+ const raw = fs.readFileSync(parentPath, "utf-8");
158
+ const parsed = JSON.parse(raw);
159
+
160
+ // Recursively handle extends in parent
161
+ if (parsed.extends) {
162
+ const grandParent = this.loadParentConfig(
163
+ parsed.extends,
164
+ path.dirname(parentPath),
165
+ );
166
+ return this.mergeConfigs(grandParent, parsed);
167
+ }
168
+ return parsed;
169
+ } catch (error) {
170
+ console.warn(`⚠️ Could not load parent tsconfig: ${extendsPath}`);
171
+ return {};
172
+ }
173
+ }
174
+
175
+ return {};
176
+ }
177
+
178
+ /**
179
+ * Merge two tsconfig objects (child overrides parent)
180
+ */
181
+ private static mergeConfigs(parent: TsConfig, child: TsConfig): TsConfig {
182
+ const result: TsConfig = { ...parent };
183
+
184
+ // Merge compilerOptions
185
+ if (parent.compilerOptions || child.compilerOptions) {
186
+ result.compilerOptions = {
187
+ ...parent.compilerOptions,
188
+ ...child.compilerOptions,
189
+ };
190
+ }
191
+
192
+ // Override other top-level properties
193
+ for (const key in child) {
194
+ if (key !== "compilerOptions") {
195
+ result[key] = child[key];
196
+ }
197
+ }
198
+
199
+ return result;
200
+ }
201
+
202
+ /**
203
+ * Get the full tsconfig configuration
204
+ * @param configPath Optional custom path to tsconfig file
205
+ */
206
+ static getFullConfig(configPath?: string): TsConfig {
207
+ return this.loadConfig(configPath);
208
+ }
209
+
210
+ /**
211
+ * Get compiler options from tsconfig
212
+ * @param configPath Optional custom path to tsconfig file
213
+ */
214
+ static getCompilerOptions(configPath?: string): TsConfigCompilerOptions {
215
+ const config = this.loadConfig(configPath);
216
+ return config.compilerOptions || {};
217
+ }
218
+
219
+ /**
220
+ * Get a specific compiler option value
221
+ * @param option The compiler option name
222
+ * @param defaultValue Default value if option is not set
223
+ * @param configPath Optional custom path to tsconfig file
224
+ */
225
+ static getCompilerOption<T>(
226
+ option: string,
227
+ defaultValue: T,
228
+ configPath?: string,
229
+ ): T {
230
+ const compilerOptions = this.getCompilerOptions(configPath);
231
+ return (
232
+ compilerOptions[option] !== undefined
233
+ ? compilerOptions[option]
234
+ : defaultValue
235
+ ) as T;
236
+ }
237
+
238
+ /**
239
+ * Check if a specific compiler option is enabled
240
+ * @param option The compiler option name
241
+ * @param configPath Optional custom path to tsconfig file
242
+ */
243
+ static isCompilerOptionEnabled(option: string, configPath?: string): boolean {
244
+ const value = this.getCompilerOption(option, false, configPath);
245
+ return Boolean(value);
246
+ }
247
+
248
+ /**
249
+ * Get the output directory (outDir) with proper resolution
250
+ * @param configPath Optional custom path to tsconfig file
251
+ */
252
+ static getOutDir(configPath?: string): string {
253
+ const outDir = this.getCompilerOption("outDir", "dist", configPath);
254
+ return path.isAbsolute(outDir) ? outDir : path.join(process.cwd(), outDir);
255
+ }
256
+
257
+ /**
258
+ * Get the source root directory (rootDir) with proper resolution
259
+ * @param configPath Optional custom path to tsconfig file
260
+ */
261
+ static getRootDir(configPath?: string): string {
262
+ const rootDir = this.getCompilerOption("rootDir", "src", configPath);
263
+ return path.isAbsolute(rootDir)
264
+ ? rootDir
265
+ : path.join(process.cwd(), rootDir);
266
+ }
267
+
268
+ /**
269
+ * Get include patterns
270
+ * @param configPath Optional custom path to tsconfig file
271
+ */
272
+ static getIncludePatterns(configPath?: string): string[] {
273
+ const config = this.loadConfig(configPath);
274
+ return config.include || ["src/**/*"];
275
+ }
276
+
277
+ /**
278
+ * Get exclude patterns
279
+ * @param configPath Optional custom path to tsconfig file
280
+ */
281
+ static getExcludePatterns(configPath?: string): string[] {
282
+ const config = this.loadConfig(configPath);
283
+ return config.exclude || ["node_modules", "**/*.spec.ts"];
284
+ }
285
+
286
+ /**
287
+ * Check if the project is configured for decorators (required for Fragment)
288
+ */
289
+ static hasDecoratorSupport(configPath?: string): boolean {
290
+ return (
291
+ this.isCompilerOptionEnabled("experimentalDecorators", configPath) &&
292
+ this.isCompilerOptionEnabled("emitDecoratorMetadata", configPath)
293
+ );
294
+ }
295
+
296
+ /**
297
+ * Resolve a file path based on tsconfig baseUrl and paths
298
+ * @param importPath The import path to resolve
299
+ * @param configPath Optional custom path to tsconfig file
300
+ */
301
+ static resolvePath(importPath: string, configPath?: string): string | null {
302
+ const compilerOptions = this.getCompilerOptions(configPath);
303
+
304
+ // Handle absolute paths starting with baseUrl
305
+ if (
306
+ compilerOptions.baseUrl &&
307
+ importPath.startsWith(compilerOptions.baseUrl)
308
+ ) {
309
+ return path.join(process.cwd(), importPath);
310
+ }
311
+
312
+ // Handle path mappings
313
+ if (compilerOptions.paths && compilerOptions.baseUrl) {
314
+ const baseUrl = path.isAbsolute(compilerOptions.baseUrl)
315
+ ? compilerOptions.baseUrl
316
+ : path.join(process.cwd(), compilerOptions.baseUrl);
317
+
318
+ for (const [pattern, paths] of Object.entries(compilerOptions.paths)) {
319
+ // Convert pattern like "@app/*" to regex
320
+ const regexPattern = pattern.replace(/\*/g, "(.*)");
321
+ const regex = new RegExp(`^${regexPattern}$`);
322
+ const match = importPath.match(regex);
323
+
324
+ if (match) {
325
+ const replacement = match[1] || "";
326
+ if (paths[0]) {
327
+ const resolvedPath = paths[0].replace("*", replacement);
328
+ return path.join(baseUrl, resolvedPath);
329
+ }
330
+ }
331
+ }
332
+ }
333
+
334
+ // Return null if cannot resolve
335
+ return null;
336
+ }
337
+
338
+ /**
339
+ * Clear the configuration cache (useful for testing)
340
+ */
341
+ static clearCache(): void {
342
+ this.configCache = null;
343
+ }
344
+
345
+ /**
346
+ * Check if tsconfig.json exists
347
+ */
348
+ static exists(configPath?: string): boolean {
349
+ if (configPath) {
350
+ const fullPath = path.isAbsolute(configPath)
351
+ ? configPath
352
+ : path.join(process.cwd(), configPath);
353
+ return fs.existsSync(fullPath);
354
+ }
355
+
356
+ return this.DEFAULT_TSCONFIG_PATHS.some((defaultPath) =>
357
+ fs.existsSync(path.join(process.cwd(), defaultPath)),
358
+ );
359
+ }
360
+ }