entkapp 4.5.1 → 5.0.0

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.
@@ -1,87 +1,4 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import { pathToFileURL } from 'url';
4
-
5
- /**
6
- * Loads and parses entkapp configuration files.
7
- * Supports scaffold.config.js, .scaffoldrc.json, and .scaffoldrc.
8
- */
9
1
  export class ConfigLoader {
10
- constructor(context) {
11
- this.context = context;
12
- }
13
-
14
- async loadConfig(projectRoot) {
15
- const searchPaths = [
16
- 'entkapp.json',
17
- 'entkapp.jsonc',
18
- '.entkapp.json',
19
- '.entkapp.jsonc',
20
- 'entkapp.js',
21
- 'entkapp.ts',
22
- 'entkapp/config.json',
23
- 'scaffold.config.js',
24
- 'scaffold.config.mjs',
25
- '.scaffoldrc.json',
26
- '.scaffoldrc'
27
- ];
28
-
29
- let config = this.getDefaultConfig();
30
-
31
- // Try package.json first
32
- try {
33
- const pkgPath = path.join(projectRoot, 'package.json');
34
- const pkgContent = await fs.readFile(pkgPath, 'utf8');
35
- const pkg = JSON.parse(pkgContent);
36
- if (pkg['entkapp']) {
37
- Object.assign(config, pkg['entkapp']);
38
- }
39
- } catch (e) {
40
- // ignore
41
- }
42
-
43
- for (const fileName of searchPaths) {
44
- const fullPath = path.join(projectRoot, fileName);
45
- try {
46
- await fs.access(fullPath);
47
-
48
- if (fileName.endsWith('.js') || fileName.endsWith('.mjs') || fileName.endsWith('.ts')) {
49
- const module = await import(pathToFileURL(fullPath).href);
50
- Object.assign(config, module.default || module);
51
- break;
52
- } else {
53
- const content = await fs.readFile(fullPath, 'utf8');
54
- // Very basic JSONC parsing (strip comments)
55
- const stripped = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
56
- Object.assign(config, JSON.parse(stripped));
57
- break;
58
- }
59
- } catch (e) {
60
- continue;
61
- }
62
- }
63
-
64
- return config;
65
- }
66
-
67
- getDefaultConfig() {
68
- return {
69
- entryPoints: ['src/index.ts', 'index.js', 'src/index.js', 'src/main.ts', 'src/main.js'],
70
- exclude: [
71
- 'node_modules/**',
72
- 'dist/**',
73
- 'build/**',
74
- '**/*.test.ts',
75
- '**/*.spec.ts',
76
- '**/*.test.js',
77
- '**/*.spec.js'
78
- ],
79
- plugins: [],
80
- rules: {
81
- 'no-unused-exports': 'error',
82
- 'no-unused-vars': 'warn',
83
- 'no-dead-code': 'error'
84
- }
85
- };
86
- }
2
+ constructor(cwd) { this.cwd = cwd; }
3
+ async loadConfig() { return {}; }
87
4
  }
@@ -1,144 +1,40 @@
1
- import resolve from 'enhanced-resolve';
2
1
  import path from 'path';
3
2
  import { existsSync } from 'fs';
4
3
 
5
- /**
6
- * Industrial-Strength Module Resolution Supervisor
7
- * Integrates path mapping and workspace topologies using enhanced-resolve.
8
- */
9
4
  export class DependencyResolver {
10
5
  constructor(context, pathMapper, workspaceGraph) {
11
6
  this.context = context;
12
7
  this.pathMapper = pathMapper;
13
8
  this.workspaceGraph = workspaceGraph;
14
-
15
- // Instantiate production-grade enhanced-resolve workspace parameters
16
- this.nativeResolver = resolve.create.sync({
17
- conditionNames: ['import', 'module', 'require', 'node', 'types'],
18
- // Extensions prioritized: TS then JS
19
- extensions: ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json', '.vue'],
20
- mainFields: ['module', 'main', 'types'],
21
- exportsFields: ['exports'],
22
- symlinks: true
23
- });
24
9
  }
25
10
 
26
- /**
27
- * Resolves a raw import string from a source file into an absolute file path.
28
- * Handles the "trap" where .ts files import .js files that should resolve to .ts.
29
- */
30
- resolveModulePath(containingFile, importSpecifier) {
31
- if (importSpecifier.startsWith('node:') || [
32
- 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants',
33
- 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'fs/promises', 'http', 'http2',
34
- 'https', 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks', 'process',
35
- 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder',
36
- 'timers', 'tls', 'trace_events', 'tty', 'url', 'util', 'v8', 'vm', 'worker_threads', 'zlib'
37
- ].includes(importSpecifier)) {
38
- return null;
11
+ normalizePath(p) {
12
+ if (!p) return p;
13
+ const original = p;
14
+ // Handle Windows drive letters and backslashes
15
+ let normalized = p.replace(/\\/g, '/');
16
+ if (/^[a-z]:\//i.test(normalized)) {
17
+ normalized = normalized.charAt(0).toUpperCase() + normalized.slice(1);
39
18
  }
40
-
41
- const containingDir = path.dirname(containingFile);
42
-
43
- // --- NEW: Fix for JS-in-TS import trap ---
44
- // If we are in a TS file and importing something ending in .js,
45
- // we should try to resolve the .ts version first.
46
- let effectiveSpecifier = importSpecifier;
47
- const isTsFile = /\.(ts|tsx|mts|cts)$/.test(containingFile);
48
- if (importSpecifier.endsWith('.js')) {
49
- const tsSpecifier = importSpecifier.replace(/\.js$/, '.ts');
50
- try {
51
- const resolvedTs = this.nativeResolver(containingDir, tsSpecifier);
52
- if (this.isAbsoluteInternalPath(resolvedTs)) return resolvedTs;
53
- } catch (e) {
54
- // Fall back to original specifier
55
- }
56
- }
57
- // -----------------------------------------
58
-
59
- // Rule A: Intercept and resolve local monorepo workspace cross-links
60
- if (this.workspaceGraph.isLocalWorkspaceSpecifier(effectiveSpecifier)) {
61
- const match = this.workspaceGraph.getWorkspacePackageMatch(effectiveSpecifier);
62
- if (match) {
63
- if (effectiveSpecifier === match.packageName) {
64
- return match.entryPoints[0] || null;
65
- }
66
-
67
- const subPathOffset = effectiveSpecifier.slice(match.packageName.length + 1);
68
- try {
69
- return this.nativeResolver(match.rootDirectory, `./${subPathOffset}`);
70
- } catch {
71
- }
72
- }
73
- }
74
-
75
- // Rule B: Intercept and expand path mapping aliases (@/*)
76
- const aliasedCandidates = this.pathMapper.resolveCandidatePaths(effectiveSpecifier);
77
- if (aliasedCandidates.length > 0) {
78
- for (const candidate of aliasedCandidates) {
79
- try {
80
- const resolvedPath = this.nativeResolver(containingDir, candidate);
81
- if (this.isAbsoluteInternalPath(resolvedPath)) {
82
- return resolvedPath;
83
- }
84
- } catch {
85
- }
86
- }
19
+ if (this.context.verbose && original !== normalized) {
20
+ // console.log(`[Path-DEBUG] Normalized: ${original} -> ${normalized}`);
87
21
  }
22
+ return normalized;
23
+ }
88
24
 
89
- // Rule C: Standard file system lookups
90
- try {
91
- const resolvedPath = this.nativeResolver(containingDir, effectiveSpecifier);
25
+ resolveModulePath(sourceFile, specifier) {
26
+ const cleanSource = this.normalizePath(sourceFile);
27
+ if (specifier.startsWith('.')) {
28
+ const dir = path.dirname(cleanSource);
29
+ const target = path.resolve(dir, specifier);
30
+ const normalizedTarget = this.normalizePath(target);
92
31
 
93
- // Fix: Improved handling for dynamic exports/imports
94
- if (this.isAbsoluteInternalPath(resolvedPath)) {
95
- return resolvedPath;
96
- }
97
- } catch (err) {
98
- // Fallback: Try to resolve with common extensions if enhanced-resolve fails
99
- const extensions = ['.ts', '.tsx', '.js', '.jsx'];
32
+ const extensions = ['', '.js', '.ts', '.tsx', '.jsx', '/index.js', '/index.ts'];
100
33
  for (const ext of extensions) {
101
- try {
102
- const trialPath = effectiveSpecifier.endsWith(ext) ? effectiveSpecifier : effectiveSpecifier + ext;
103
- const resolvedPath = path.resolve(containingDir, trialPath);
104
- if (existsSync(resolvedPath)) return resolvedPath;
105
- } catch {}
106
- }
107
- if (this.context.verbose) {
108
- console.log(`[Resolution Trace Skip] Specifier unresolvable: ${effectiveSpecifier} inside ${containingFile}`);
34
+ const p = normalizedTarget + ext;
35
+ if (existsSync(p)) return this.normalizePath(p);
109
36
  }
110
37
  }
111
-
112
38
  return null;
113
39
  }
114
-
115
- isAbsoluteInternalPath(resolvedPath) {
116
- if (!resolvedPath) return false;
117
- const normalized = resolvedPath.replace(/\\/g, '/');
118
-
119
- if (normalized.includes('/node_modules/')) {
120
- for (const [name, meta] of this.workspaceGraph.packageManifests.entries()) {
121
- if (normalized.startsWith(meta.rootDirectory.replace(/\\/g, '/'))) {
122
- return true;
123
- }
124
- }
125
- return false;
126
- }
127
-
128
- return path.isAbsolute(resolvedPath);
129
- }
130
-
131
- determineIntentProfile(filePath, declaredExportsManifest) {
132
- const fileName = path.basename(filePath);
133
- const isPublicContractFile = /(^index|^public\-api|^entry)\.(ts|js|tsx|jsx)$/i.test(fileName);
134
-
135
- if (isPublicContractFile) {
136
- for (const [symbolKey, metadata] of declaredExportsManifest.entries()) {
137
- metadata.isLibraryContract = true;
138
- }
139
- return 'LIBRARY_CONSUMPTION_TARGET';
140
- }
141
-
142
- return 'INTERNAL_CODEBASE_ELEMENT';
143
- }
144
40
  }
@@ -0,0 +1,134 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export class EntryPointDetector {
5
+ constructor(targetDir, packageJson, stats) {
6
+ this.targetDir = targetDir;
7
+ this.packageJson = packageJson || {};
8
+ this.stats = stats;
9
+ }
10
+
11
+ /**
12
+ * Ermittelt alle potenziellen Entry-Points eines Projekts.
13
+ * @returns {Set<string>} Set von absoluten Pfaden.
14
+ */
15
+ detect() {
16
+ const entries = new Set();
17
+
18
+ // 1. package.json Standards
19
+ this._addFromPackageJson(entries);
20
+
21
+ // 2. Framework-spezifische Pfade
22
+ this._addFromFrameworks(entries);
23
+
24
+ // 3. Fallbacks / Konventionen
25
+ this._addFromConventions(entries);
26
+
27
+ return entries;
28
+ }
29
+
30
+ _addFromPackageJson(entries) {
31
+ const fields = ['main', 'module', 'browser', 'bin'];
32
+ fields.forEach(field => {
33
+ const val = this.packageJson[field];
34
+ if (typeof val === 'string') {
35
+ this._addIfExist(entries, val);
36
+ } else if (typeof val === 'object' && val !== null) {
37
+ Object.values(val).forEach(v => {
38
+ if (typeof v === 'string') this._addIfExist(entries, v);
39
+ });
40
+ }
41
+ });
42
+
43
+ // Exports field (modern Node.js)
44
+ if (this.packageJson.exports) {
45
+ this._parseExports(this.packageJson.exports, entries);
46
+ }
47
+ }
48
+
49
+ _parseExports(exports, entries) {
50
+ if (typeof exports === 'string') {
51
+ this._addIfExist(entries, exports);
52
+ } else if (typeof exports === 'object' && exports !== null) {
53
+ for (const key in exports) {
54
+ const val = exports[key];
55
+ if (typeof val === 'string') {
56
+ this._addIfExist(entries, val);
57
+ } else if (typeof val === 'object') {
58
+ this._parseExports(val, entries);
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ _addFromFrameworks(entries) {
65
+ if (!this.stats || !this.stats.detectedFrameworks) return;
66
+
67
+ const frameworks = this.stats.detectedFrameworks;
68
+
69
+ if (frameworks.includes('next')) {
70
+ this._scanDir(path.join(this.targetDir, 'pages'), entries);
71
+ this._scanDir(path.join(this.targetDir, 'app'), entries, ['page.tsx', 'page.js', 'route.ts', 'route.js']);
72
+ }
73
+
74
+ if (frameworks.includes('nuxt')) {
75
+ this._scanDir(path.join(this.targetDir, 'pages'), entries);
76
+ }
77
+
78
+ if (frameworks.includes('svelte')) {
79
+ this._scanDir(path.join(this.targetDir, 'src/routes'), entries);
80
+ }
81
+ }
82
+
83
+ _addFromConventions(entries) {
84
+ const fallbacks = [
85
+ 'index.js', 'index.ts', 'index.jsx', 'index.tsx',
86
+ 'src/index.js', 'src/index.ts', 'src/index.jsx', 'src/index.tsx',
87
+ 'main.js', 'main.ts', 'src/main.js', 'src/main.ts',
88
+ 'app.js', 'app.ts', 'src/app.js', 'src/app.ts',
89
+ 'server.js', 'server.ts', 'src/server.js', 'src/server.ts'
90
+ ];
91
+ fallbacks.forEach(f => this._addIfExist(entries, f));
92
+
93
+ // If no entries found yet, and we are in a simple src structure,
94
+ // protect common top-level files in src/
95
+ if (entries.size === 0) {
96
+ const commonSrcFiles = ['src/app.ts', 'src/main.ts', 'src/index.ts', 'src/app.js', 'src/main.js', 'src/index.js'];
97
+ commonSrcFiles.forEach(f => this._addIfExist(entries, f));
98
+ }
99
+ }
100
+
101
+ _addIfExist(entries, relativePath) {
102
+ // Bereinige Pfad (entferne ./ etc)
103
+ const cleanPath = relativePath.replace(/^(\.\/|\/)/, '');
104
+ const absolutePath = path.resolve(this.targetDir, cleanPath);
105
+
106
+ // Prüfe verschiedene Erweiterungen falls keine angegeben
107
+ const extensions = ['', '.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
108
+ for (const ext of extensions) {
109
+ const p = absolutePath + ext;
110
+ if (fs.existsSync(p) && fs.statSync(p).isFile()) {
111
+ entries.add(p);
112
+ return;
113
+ }
114
+ }
115
+ }
116
+
117
+ _scanDir(dir, entries, specificFiles = null) {
118
+ if (!fs.existsSync(dir)) return;
119
+
120
+ const files = fs.readdirSync(dir, { recursive: true });
121
+ files.forEach(file => {
122
+ const fullPath = path.join(dir, file);
123
+ if (fs.statSync(fullPath).isFile()) {
124
+ if (specificFiles) {
125
+ if (specificFiles.includes(path.basename(file))) {
126
+ entries.add(fullPath);
127
+ }
128
+ } else if (/\.(js|ts|jsx|tsx|vue|svelte)$/.test(file)) {
129
+ entries.add(fullPath);
130
+ }
131
+ }
132
+ });
133
+ }
134
+ }
@@ -1,125 +1,5 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
-
4
- /**
5
- * Advanced TSConfig / JSConfig Compilation Path Alias Mapper
6
- * Resolves deeply nested route mappings, wildcards, and base URL overrides.
7
- */
8
1
  export class PathMapper {
9
- constructor(context) {
10
- this.context = context;
11
- this.baseUrl = '.';
12
- this.absoluteBaseUrl = context.cwd;
13
- this.mappings = []; // Collection of { prefix, suffix, targets[] }
14
- }
15
-
16
- /**
17
- * Reads, cleans, and indexes custom alias entries from tsconfig.json files.
18
- * @param {string} tsconfigFilename - Target designator (typically tsconfig.json)
19
- */
20
- async loadMappings(tsconfigFilename = 'tsconfig.json') {
21
- const configPath = path.resolve(this.context.cwd, tsconfigFilename);
22
-
23
- try {
24
- await fs.access(configPath);
25
- const rawText = await fs.readFile(configPath, 'utf8');
26
-
27
- // Strip inline single-line and block comments before parsing
28
- // Improved regex to handle more edge cases in tsconfig comments
29
- const jsonCleanText = rawText
30
- .replace(/\/\*[\s\S]*?\*\/|(?<=[^\\:])\/\/.*$/gm, '')
31
- .replace(/,(\s*[\]}])/g, '$1'); // Remove trailing commas
32
-
33
- const tsconfig = JSON.parse(jsonCleanText);
34
-
35
- if (!tsconfig.compilerOptions) return;
36
-
37
- const opts = tsconfig.compilerOptions;
38
-
39
- // v6 Path Resolution Fix (Knip Issue #1794)
40
- // Ensure baseUrl is correctly resolved relative to the tsconfig file location
41
- const configDir = path.dirname(configPath);
42
- if (opts.baseUrl) {
43
- this.baseUrl = opts.baseUrl;
44
- this.absoluteBaseUrl = path.resolve(configDir, this.baseUrl);
45
- } else {
46
- this.absoluteBaseUrl = configDir;
47
- }
48
-
49
- if (opts.paths) {
50
- for (const [aliasPattern, targetArrays] of Object.entries(opts.paths)) {
51
- this.registerPatternRule(aliasPattern, targetArrays);
52
- }
53
- }
54
- } catch (error) {
55
- if (this.context.verbose) {
56
- console.warn(`⚠️ [PathMapper Override] Proceeding without custom path configurations. Source: ${error.message}`);
57
- }
58
- }
59
- }
60
-
61
- /**
62
- * Registers structural lookup parameters for alias strings.
63
- */
64
- registerPatternRule(pattern, targets) {
65
- const wildcardIndex = pattern.indexOf('*');
66
-
67
- if (wildcardIndex === -1) {
68
- this.mappings.push({
69
- isExact: true,
70
- pattern,
71
- targets: targets.map(t => path.normalize(t))
72
- });
73
- return;
74
- }
75
-
76
- const prefix = pattern.slice(0, wildcardIndex);
77
- const suffix = pattern.slice(wildcardIndex + 1);
78
-
79
- this.mappings.push({
80
- isExact: false,
81
- prefix,
82
- suffix,
83
- targets: targets.map(t => path.normalize(t))
84
- });
85
- }
86
-
87
- /**
88
- * Resolves a raw import specifier against mapped path patterns.
89
- * @param {string} specifier - Raw text from import declaration (e.g., '@ui/button')
90
- * @returns {Array<string>} Candidates of absolute filesystem paths to try resolving
91
- */
92
- resolveCandidatePaths(specifier) {
93
- const matchingCandidates = [];
94
-
95
- for (const rule of this.mappings) {
96
- if (rule.isExact) {
97
- if (specifier === rule.pattern) {
98
- rule.targets.forEach(target => {
99
- matchingCandidates.push(path.resolve(this.absoluteBaseUrl, target));
100
- });
101
- }
102
- } else {
103
- // Evaluate wildcard pattern matches
104
- if (specifier.startsWith(rule.prefix) && specifier.endsWith(rule.suffix)) {
105
- const extractedWildcardContent = specifier.slice(
106
- rule.prefix.length,
107
- specifier.length - rule.suffix.length
108
- );
109
-
110
- rule.targets.forEach(targetTemplate => {
111
- const interpolatedTarget = targetTemplate.replace('*', extractedWildcardContent);
112
- matchingCandidates.push(path.resolve(this.absoluteBaseUrl, interpolatedTarget));
113
- });
114
- }
115
- }
116
- }
117
-
118
- // Fall back to direct lookup relative to the base URL
119
- if (!specifier.startsWith('.') && !path.isAbsolute(specifier)) {
120
- matchingCandidates.push(path.resolve(this.absoluteBaseUrl, specifier));
121
- }
122
-
123
- return matchingCandidates;
124
- }
2
+ constructor(context) { this.context = context; }
3
+ async loadMappings() {}
4
+ resolvePath(p) { return p; }
125
5
  }
@@ -0,0 +1,76 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import ts from 'typescript';
4
+
5
+ export class TSConfigLoader {
6
+ constructor(targetDir) {
7
+ this.targetDir = targetDir;
8
+ }
9
+
10
+ /**
11
+ * Lädt und parst die tsconfig.json.
12
+ * @returns {Object|null} Parsed config oder null.
13
+ */
14
+ load() {
15
+ const configPath = path.join(this.targetDir, 'tsconfig.json');
16
+ if (!fs.existsSync(configPath)) return null;
17
+
18
+ try {
19
+ const content = fs.readFileSync(configPath, 'utf8');
20
+ const result = ts.parseConfigFileTextWithComments(configPath, content);
21
+
22
+ if (result.error) {
23
+ return null;
24
+ }
25
+
26
+ const parsed = ts.parseJsonConfigFileContent(
27
+ result.config,
28
+ ts.sys,
29
+ this.targetDir
30
+ );
31
+
32
+ return parsed;
33
+ } catch (e) {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Erstellt eine Mapping-Funktion für Aliases aus der tsconfig.
40
+ * @param {Object} parsedConfig
41
+ * @returns {Function} Mapper function.
42
+ */
43
+ getAliasMapper(parsedConfig) {
44
+ if (!parsedConfig || !parsedConfig.options || !parsedConfig.options.paths) {
45
+ return (source) => source;
46
+ }
47
+
48
+ const { paths, baseUrl } = parsedConfig.options;
49
+ const base = baseUrl ? path.resolve(this.targetDir, baseUrl) : this.targetDir;
50
+
51
+ return (source) => {
52
+ for (const pattern in paths) {
53
+ const regexPattern = pattern.replace(/\*/, '(.*)');
54
+ const regex = new RegExp(`^${regexPattern}$`);
55
+ const match = source.match(regex);
56
+
57
+ if (match) {
58
+ const replacements = paths[pattern];
59
+ for (const replacement of replacements) {
60
+ const resolvedReplacement = replacement.replace(/\*/, match[1]);
61
+ const fullPath = path.resolve(base, resolvedReplacement);
62
+
63
+ // Prüfe ob Datei existiert
64
+ const extensions = ['', '.ts', '.tsx', '.js', '.jsx'];
65
+ for (const ext of extensions) {
66
+ if (fs.existsSync(fullPath + ext)) {
67
+ return fullPath + ext;
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return source;
74
+ };
75
+ }
76
+ }