entkapp 4.5.1 → 5.1.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,32 +1,35 @@
1
+ import path from 'path';
2
+
1
3
  export class CircularDetector {
2
4
  constructor(context) {
3
5
  this.context = context;
4
6
  this.cycles = [];
5
7
  }
6
8
 
7
- detectCycles(graph, context = null) {
8
- if (context) this.context = context;
9
- this.cwd = context?.cwd || this.context?.cwd || process.cwd();
10
- this.cycles = [];
11
-
12
- let index = 0;
9
+ detectCycles(projectGraph) {
13
10
  const stack = [];
11
+ const onStack = new Set();
14
12
  const indices = new Map();
15
13
  const lowlink = new Map();
16
- const onStack = new Set();
14
+ const sccs = [];
15
+ let index = 0;
17
16
 
18
- const strongconnect = (v) => {
17
+ const strongConnect = (v) => {
19
18
  indices.set(v, index);
20
19
  lowlink.set(v, index);
21
20
  index++;
22
21
  stack.push(v);
23
22
  onStack.add(v);
24
23
 
25
- const node = graph.get(v);
24
+ const node = projectGraph.get(v);
26
25
  if (node && node.outgoingEdges) {
27
- for (const w of node.outgoingEdges) {
26
+ // Ensure outgoingEdges is treated as an array-like structure
27
+ const edges = Array.isArray(node.outgoingEdges) ? node.outgoingEdges : Array.from(node.outgoingEdges);
28
+
29
+ for (let i = 0; i < edges.length; i++) {
30
+ const w = edges[i];
28
31
  if (!indices.has(w)) {
29
- strongconnect(w);
32
+ strongConnect(w);
30
33
  lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
31
34
  } else if (onStack.has(w)) {
32
35
  lowlink.set(v, Math.min(lowlink.get(v), indices.get(w)));
@@ -35,57 +38,54 @@ export class CircularDetector {
35
38
  }
36
39
 
37
40
  if (lowlink.get(v) === indices.get(v)) {
38
- const component = [];
41
+ const scc = [];
39
42
  let w;
40
43
  do {
41
44
  w = stack.pop();
42
45
  onStack.delete(w);
43
- component.push(w);
46
+ scc.push(w);
44
47
  } while (w !== v);
45
48
 
46
- if (component.length > 1) {
47
- this.cycles.push(component.reverse());
48
- } else {
49
- const node = graph.get(v);
50
- if (node && node.outgoingEdges && node.outgoingEdges.has(v)) {
51
- this.cycles.push([v]);
49
+ if (scc.length > 1) {
50
+ const cycle = scc.slice().reverse();
51
+ cycle.push(cycle[0]);
52
+ sccs.push(cycle);
53
+ } else if (scc.length === 1) {
54
+ const singleNode = projectGraph.get(scc[0]);
55
+ if (singleNode && singleNode.outgoingEdges) {
56
+ const edges = Array.isArray(singleNode.outgoingEdges) ? singleNode.outgoingEdges : Array.from(singleNode.outgoingEdges);
57
+ let hasSelfLoop = false;
58
+ for (let i = 0; i < edges.length; i++) {
59
+ if (edges[i] === scc[0]) {
60
+ hasSelfLoop = true;
61
+ break;
62
+ }
63
+ }
64
+ if (hasSelfLoop) {
65
+ sccs.push([scc[0], scc[0]]);
66
+ }
52
67
  }
53
68
  }
54
69
  }
55
70
  };
56
71
 
57
- for (const v of graph.keys()) {
58
- if (!indices.has(v)) {
59
- strongconnect(v);
72
+ const keys = Array.from(projectGraph.keys());
73
+ for (let i = 0; i < keys.length; i++) {
74
+ if (!indices.has(keys[i])) {
75
+ strongConnect(keys[i]);
60
76
  }
61
77
  }
62
78
 
63
- return this.cycles;
79
+ this.cycles = sccs;
80
+ return sccs;
64
81
  }
65
82
 
66
83
  formatCycles() {
67
84
  return this.cycles.map(cycle => {
68
- const paths = cycle.map(p => {
69
- let rel = p.replace(this.context.cwd, '').replace(/^\//, '');
70
- if (rel.includes(':\\')) rel = rel.split(':\\')[1] || rel;
71
- return rel;
72
- });
73
- if (cycle.length === 1) return `${paths[0]} -> (self-loop)`;
74
- return paths.join(' -> ') + ' -> ' + paths[0];
85
+ return cycle.map(p => {
86
+ const relativePath = path.relative(this.context.cwd, p);
87
+ return relativePath.replace(/\\/g, '/');
88
+ }).join(' -> ');
75
89
  });
76
90
  }
77
-
78
- getCycleDetails() {
79
- return this.cycles.map((cycle, idx) => ({
80
- cycleId: idx + 1,
81
- files: cycle.map(p => {
82
- let rel = p.replace(this.context.cwd, '').replace(/^\//, '');
83
- if (rel.includes(':\\')) rel = rel.split(':\\')[1] || rel;
84
- return rel;
85
- }),
86
- length: cycle.length,
87
- isSelfLoop: cycle.length === 1
88
- }));
89
- }
90
91
  }
91
- export default CircularDetector;
@@ -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,51 @@
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;
39
- }
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
- }
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);
56
18
  }
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
- }
19
+ if (this.context.verbose && original !== normalized) {
20
+ // console.log(`[Path-DEBUG] Normalized: ${original} -> ${normalized}`);
73
21
  }
22
+ return normalized;
23
+ }
74
24
 
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
- }
25
+ resolveModulePath(sourceFile, specifier) {
26
+ const cleanSource = this.normalizePath(sourceFile);
27
+
28
+ // UPGRADE: Use PathMapper for sophisticated resolution (TS-to-JS, aliases, etc.)
29
+ if (this.pathMapper) {
30
+ const dir = path.dirname(cleanSource);
31
+ const target = path.resolve(dir, specifier);
32
+ const resolved = this.pathMapper.resolvePath(target);
33
+ if (resolved && existsSync(resolved)) {
34
+ return this.normalizePath(resolved);
86
35
  }
87
36
  }
88
37
 
89
- // Rule C: Standard file system lookups
90
- try {
91
- const resolvedPath = this.nativeResolver(containingDir, effectiveSpecifier);
38
+ if (specifier.startsWith('.')) {
39
+ const dir = path.dirname(cleanSource);
40
+ const target = path.resolve(dir, specifier);
41
+ const normalizedTarget = this.normalizePath(target);
92
42
 
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'];
43
+ const extensions = ['', '.js', '.ts', '.tsx', '.jsx', '/index.js', '/index.ts', '/index.tsx'];
100
44
  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}`);
45
+ const p = normalizedTarget + ext;
46
+ if (existsSync(p)) return this.normalizePath(p);
109
47
  }
110
48
  }
111
-
112
49
  return null;
113
50
  }
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
51
  }
@@ -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
+ }