entkapp 5.4.0 → 5.6.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.
- package/README.md +7 -1
- package/bin/cli.js +117 -58
- package/bin/cli.mjs +175 -0
- package/index.cjs +18 -0
- package/index.mjs +51 -0
- package/package.json +7 -6
- package/src/EngineContext.js +0 -6
- package/src/EngineContext.mjs +428 -0
- package/src/Initializer.mjs +82 -0
- package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
- package/src/analyzers/OxcAnalyzer.mjs +11 -0
- package/src/api/HeadlessAPI.js +31 -16
- package/src/api/HeadlessAPI.mjs +369 -0
- package/src/api/PluginSDK.mjs +135 -0
- package/src/ast/ASTAnalyzer.js +23 -97
- package/src/ast/ASTAnalyzer.mjs +742 -0
- package/src/ast/AdvancedAnalysis.mjs +586 -0
- package/src/ast/BarrelParser.mjs +230 -0
- package/src/ast/DeadCodeDetector.js +7 -5
- package/src/ast/DeadCodeDetector.mjs +92 -0
- package/src/ast/MagicDetector.js +43 -23
- package/src/ast/MagicDetector.mjs +203 -0
- package/src/ast/OxcAnalyzer.js +27 -190
- package/src/ast/OxcAnalyzer.mjs +188 -0
- package/src/ast/SecretScanner.mjs +374 -0
- package/src/healing/GitSandbox.mjs +82 -0
- package/src/healing/SelfHealer.mjs +48 -0
- package/src/index.js +41 -88
- package/src/index.mjs +1176 -0
- package/src/performance/GraphCache.js +0 -27
- package/src/performance/GraphCache.mjs +108 -0
- package/src/performance/SupplyChainGuard.mjs +92 -0
- package/src/performance/WorkerPool.js +4 -22
- package/src/performance/WorkerPool.mjs +132 -0
- package/src/performance/WorkerTaskRunner.js +0 -26
- package/src/performance/WorkerTaskRunner.mjs +144 -0
- package/src/plugins/BasePlugin.js +27 -16
- package/src/plugins/BasePlugin.mjs +240 -0
- package/src/plugins/PluginRegistry.js +0 -44
- package/src/plugins/PluginRegistry.mjs +203 -0
- package/src/plugins/ecosystems/BackendServices.mjs +197 -0
- package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
- package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
- package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
- package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
- package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
- package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
- package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
- package/src/plugins/ecosystems/UltimateBundle.js +0 -259
- package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
- package/src/refractor/ImpactAnalyzer.mjs +92 -0
- package/src/refractor/SourceRewriter.mjs +86 -0
- package/src/refractor/TransactionManager.mjs +5 -0
- package/src/refractor/TypeIntegrity.mjs +4 -0
- package/src/resolution/BuildOrchestrator.mjs +46 -0
- package/src/resolution/CircularDetector.mjs +91 -0
- package/src/resolution/ConfigGenerator.mjs +83 -0
- package/src/resolution/ConfigLoader.js +26 -190
- package/src/resolution/ConfigLoader.mjs +94 -0
- package/src/resolution/DepencyResolver.mjs +66 -0
- package/src/resolution/DependencyFixer.mjs +88 -0
- package/src/resolution/DependencyProfiler.mjs +286 -0
- package/src/resolution/EntryPointDetector.mjs +134 -0
- package/src/resolution/FrameworkConfigParser.mjs +119 -0
- package/src/resolution/GraphAnalyzer.js +1 -65
- package/src/resolution/GraphAnalyzer.mjs +80 -0
- package/src/resolution/MigrationAnalyzer.mjs +60 -0
- package/src/resolution/PathMapper.js +0 -81
- package/src/resolution/PathMapper.mjs +82 -0
- package/src/resolution/TSConfigLoader.js +1 -3
- package/src/resolution/TSConfigLoader.mjs +162 -0
- package/src/resolution/WorkSpaceGraph.js +89 -260
- package/src/resolution/WorkSpaceGraph.mjs +183 -0
- package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
- package/test.js +76 -0
- package/wrangler.toml +6 -0
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
* Unterstützt JSONC (Kommentare), extends, und project references.
|
|
13
|
+
* @param {string} [filename='tsconfig.json'] - Name der tsconfig-Datei
|
|
14
|
+
* @returns {Object|null} Parsed config oder null.
|
|
15
|
+
*/
|
|
16
|
+
load(filename = 'tsconfig.json') {
|
|
17
|
+
const configPath = path.join(this.targetDir, filename);
|
|
18
|
+
if (!fs.existsSync(configPath)) return null;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
22
|
+
const readResult = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
23
|
+
|
|
24
|
+
if (readResult.error) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
29
|
+
readResult.config,
|
|
30
|
+
ts.sys,
|
|
31
|
+
this.targetDir
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Attach raw config for reference inspection
|
|
35
|
+
parsed._rawConfig = readResult.config;
|
|
36
|
+
|
|
37
|
+
return parsed;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extracts project references from tsconfig (composite monorepo support).
|
|
45
|
+
* @returns {string[]} Array of referenced tsconfig paths
|
|
46
|
+
*/
|
|
47
|
+
getProjectReferences(filename = 'tsconfig.json') {
|
|
48
|
+
const configPath = path.join(this.targetDir, filename);
|
|
49
|
+
if (!fs.existsSync(configPath)) return [];
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
53
|
+
// Strip comments
|
|
54
|
+
const stripped = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
55
|
+
const raw = JSON.parse(stripped);
|
|
56
|
+
const refs = raw.references || [];
|
|
57
|
+
return refs.map(ref => {
|
|
58
|
+
const refPath = path.resolve(this.targetDir, ref.path);
|
|
59
|
+
// If it's a directory, look for tsconfig.json inside
|
|
60
|
+
if (fs.existsSync(refPath) && fs.statSync(refPath).isDirectory()) {
|
|
61
|
+
return path.join(refPath, 'tsconfig.json');
|
|
62
|
+
}
|
|
63
|
+
return refPath;
|
|
64
|
+
});
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extracts the "extends" chain from tsconfig.
|
|
72
|
+
* @returns {string[]} Array of extended tsconfig paths
|
|
73
|
+
*/
|
|
74
|
+
getExtendsChain(filename = 'tsconfig.json') {
|
|
75
|
+
const configPath = path.join(this.targetDir, filename);
|
|
76
|
+
if (!fs.existsSync(configPath)) return [];
|
|
77
|
+
|
|
78
|
+
const chain = [];
|
|
79
|
+
let currentPath = configPath;
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < 10; i++) { // limit depth to avoid infinite loops
|
|
82
|
+
try {
|
|
83
|
+
const content = fs.readFileSync(currentPath, 'utf8');
|
|
84
|
+
const stripped = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
85
|
+
const raw = JSON.parse(stripped);
|
|
86
|
+
if (!raw.extends) break;
|
|
87
|
+
const extendedPath = path.resolve(path.dirname(currentPath), raw.extends);
|
|
88
|
+
const resolvedPath = extendedPath.endsWith('.json') ? extendedPath : extendedPath + '.json';
|
|
89
|
+
if (!fs.existsSync(resolvedPath)) break;
|
|
90
|
+
chain.push(resolvedPath);
|
|
91
|
+
currentPath = resolvedPath;
|
|
92
|
+
} catch (e) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return chain;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Erstellt eine Mapping-Funktion für Aliases aus der tsconfig.
|
|
102
|
+
* Berücksichtigt baseUrl, paths und project references.
|
|
103
|
+
* @param {Object} parsedConfig
|
|
104
|
+
* @returns {Function} Mapper function.
|
|
105
|
+
*/
|
|
106
|
+
getAliasMapper(parsedConfig) {
|
|
107
|
+
if (!parsedConfig || !parsedConfig.options) {
|
|
108
|
+
return (source) => source;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { paths, baseUrl } = parsedConfig.options;
|
|
112
|
+
const base = baseUrl ? path.resolve(this.targetDir, baseUrl) : this.targetDir;
|
|
113
|
+
|
|
114
|
+
return (source) => {
|
|
115
|
+
// If no paths configured, still try baseUrl resolution
|
|
116
|
+
if (!paths) {
|
|
117
|
+
if (baseUrl && !source.startsWith('.') && !source.startsWith('/') && !source.startsWith('@')) {
|
|
118
|
+
const candidate = path.resolve(base, source);
|
|
119
|
+
const extensions = ['', '.ts', '.tsx', '.mjs', '.jsx'];
|
|
120
|
+
for (const ext of extensions) {
|
|
121
|
+
if (fs.existsSync(candidate + ext)) return candidate + ext;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return source;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const pattern in paths) {
|
|
128
|
+
const regexPattern = pattern.replace(/\*/g, '(.*)');
|
|
129
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
130
|
+
const match = source.match(regex);
|
|
131
|
+
|
|
132
|
+
if (match) {
|
|
133
|
+
const replacements = paths[pattern];
|
|
134
|
+
for (const replacement of replacements) {
|
|
135
|
+
const resolvedReplacement = replacement.replace(/\*/g, match[1] || '');
|
|
136
|
+
const fullPath = path.resolve(base, resolvedReplacement);
|
|
137
|
+
|
|
138
|
+
// Check with common extensions
|
|
139
|
+
const extensions = ['', '.ts', '.tsx', '.mjs', '.jsx', '/index.ts', '/index.tsx', '/index.mjs'];
|
|
140
|
+
for (const ext of extensions) {
|
|
141
|
+
if (fs.existsSync(fullPath + ext)) {
|
|
142
|
+
return fullPath + ext;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return source;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Returns all include/exclude glob patterns from tsconfig.
|
|
154
|
+
*/
|
|
155
|
+
getIncludeExcludePatterns(parsedConfig) {
|
|
156
|
+
if (!parsedConfig || !parsedConfig._rawConfig) return { include: [], exclude: [] };
|
|
157
|
+
return {
|
|
158
|
+
include: parsedConfig._rawConfig.include || [],
|
|
159
|
+
exclude: parsedConfig._rawConfig.exclude || []
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -1,208 +1,82 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* WorkspaceGraph
|
|
6
|
-
*
|
|
7
|
-
* Discovers and maps all packages in a monorepo/workspace setup.
|
|
8
|
-
* Supported workspace manifests (in priority order):
|
|
9
|
-
* 1. pnpm-workspace.yaml (pnpm)
|
|
10
|
-
* 2. package.json "workspaces" field (npm / yarn / bun)
|
|
11
|
-
* 3. lerna.json (Lerna)
|
|
12
|
-
* 4. workspace.yaml (generic / custom toolchains)
|
|
13
|
-
*/
|
|
14
4
|
export class WorkspaceGraph {
|
|
15
|
-
constructor(context) {
|
|
16
|
-
this.context = context;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
/** @type {Map<string, string>} packageName -> dirPath */
|
|
22
|
-
this.workspacePackages = new Map();
|
|
23
|
-
|
|
24
|
-
/** @type {Map<string, Object>} packageName -> parsed tsconfig data */
|
|
25
|
-
this.tsconfigPaths = new Map();
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Per-package framework / tool config files.
|
|
29
|
-
* @type {Map<string, Array<{fileName: string, filePath: string, content: string}>>}
|
|
30
|
-
*/
|
|
31
|
-
this.packageConfigFiles = new Map();
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Global pipeline / monorepo configs (turbo.json, nx.json, etc.)
|
|
35
|
-
* @type {Map<string, Object>} fileName -> parsed content
|
|
36
|
-
*/
|
|
37
|
-
this.monorepoConfigs = new Map();
|
|
5
|
+
constructor(context) {
|
|
6
|
+
this.context = context;
|
|
7
|
+
this.packageManifests = new Map(); // dirPath -> manifestData
|
|
8
|
+
this.workspacePackages = new Map(); // packageName -> dirPath
|
|
9
|
+
this.tsconfigPaths = new Map(); // packageName -> tsconfigData
|
|
38
10
|
}
|
|
39
11
|
|
|
40
12
|
async initializeWorkspaceMesh() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// 1. pnpm-workspace.yaml
|
|
44
|
-
workspaces = await this._parseWorkspaceYaml('pnpm-workspace.yaml');
|
|
45
|
-
|
|
46
|
-
// 2. package.json "workspaces"
|
|
47
|
-
if (workspaces.length === 0) {
|
|
48
|
-
try {
|
|
49
|
-
const rootPkgPath = path.join(this.context.cwd, 'package.json');
|
|
50
|
-
const rootPkg = JSON.parse(await fs.readFile(rootPkgPath, 'utf8'));
|
|
51
|
-
if (rootPkg.workspaces) {
|
|
52
|
-
workspaces = Array.isArray(rootPkg.workspaces)
|
|
53
|
-
? rootPkg.workspaces
|
|
54
|
-
: rootPkg.workspaces.packages || [];
|
|
55
|
-
}
|
|
56
|
-
} catch (e) {}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 3. lerna.json
|
|
60
|
-
if (workspaces.length === 0) {
|
|
61
|
-
try {
|
|
62
|
-
const lernaPath = path.join(this.context.cwd, 'lerna.json');
|
|
63
|
-
const lerna = JSON.parse(await fs.readFile(lernaPath, 'utf8'));
|
|
64
|
-
workspaces = lerna.packages || [];
|
|
65
|
-
} catch (e) {}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 4. workspace.yaml
|
|
69
|
-
if (workspaces.length === 0) {
|
|
70
|
-
workspaces = await this._parseWorkspaceYaml('workspace.yaml');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
await this._loadGlobalMonorepoConfigs();
|
|
74
|
-
|
|
75
|
-
if (workspaces.length === 0) return;
|
|
76
|
-
|
|
77
|
-
this.context.isWorkspaceEnabled = true;
|
|
78
|
-
if (this.context.verbose) {
|
|
79
|
-
console.log('[Workspace] Detected workspace patterns:', workspaces);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
for (const pattern of workspaces) {
|
|
83
|
-
const matches = await this._expandGlob(pattern, this.context.cwd);
|
|
84
|
-
for (const matchDir of matches) {
|
|
85
|
-
await this._loadPackageDir(matchDir);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async _loadGlobalMonorepoConfigs() {
|
|
91
|
-
const configFiles = ['turbo.json', 'nx.json'];
|
|
92
|
-
for (const file of configFiles) {
|
|
93
|
-
try {
|
|
94
|
-
const filePath = path.join(this.context.cwd, file);
|
|
95
|
-
const content = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
96
|
-
this.monorepoConfigs.set(file, content);
|
|
97
|
-
} catch (e) {}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async _parseWorkspaceYaml(fileName) {
|
|
102
|
-
const yamlPath = path.join(this.context.cwd, fileName);
|
|
13
|
+
const rootPkgPath = path.join(this.context.cwd, 'package.json');
|
|
103
14
|
try {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (inPackages) {
|
|
124
|
-
if (line.match(/^[a-zA-Z0-9_-]/) && trimmed.endsWith(':')) {
|
|
125
|
-
inPackages = false;
|
|
126
|
-
continue;
|
|
15
|
+
const rootPkg = JSON.parse(await fs.readFile(rootPkgPath, 'utf8'));
|
|
16
|
+
let workspaces = [];
|
|
17
|
+
|
|
18
|
+
// 1. Detect Workspaces (npm/yarn/pnpm/lerna)
|
|
19
|
+
if (rootPkg.workspaces) {
|
|
20
|
+
workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces.packages || [];
|
|
21
|
+
} else {
|
|
22
|
+
// Fallback for pnpm-workspace.yaml
|
|
23
|
+
const pnpmWorkspacePath = path.join(this.context.cwd, 'pnpm-workspace.yaml');
|
|
24
|
+
try {
|
|
25
|
+
const yaml = await fs.readFile(pnpmWorkspacePath, 'utf8');
|
|
26
|
+
const match = yaml.match(/packages:\n((?:\s+- .+\n?)+)/);
|
|
27
|
+
if (match) {
|
|
28
|
+
workspaces = match[1].split('\n')
|
|
29
|
+
.filter(line => line.trim().startsWith('-'))
|
|
30
|
+
.map(line => line.replace('-', '').trim().replace(/['"]/g, ''));
|
|
127
31
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
32
|
+
} catch (e) {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (workspaces.length > 0) {
|
|
36
|
+
this.context.isWorkspaceEnabled = true;
|
|
37
|
+
if (this.context.verbose) console.log(`[Workspace] Detected workspaces:`, workspaces);
|
|
38
|
+
|
|
39
|
+
for (const pattern of workspaces) {
|
|
40
|
+
const matches = await this._expandGlob(pattern, this.context.cwd);
|
|
41
|
+
for (const matchDir of matches) {
|
|
42
|
+
const pkgPath = path.join(matchDir, 'package.json');
|
|
43
|
+
try {
|
|
44
|
+
const pkgData = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
45
|
+
const normalizedDir = matchDir.replace(/\\/g, '/');
|
|
46
|
+
|
|
47
|
+
this.packageManifests.set(normalizedDir, {
|
|
48
|
+
rootDirectory: normalizedDir,
|
|
49
|
+
manifestPath: pkgPath.replace(/\\/g, '/'),
|
|
50
|
+
name: pkgData.name,
|
|
51
|
+
dependencies: pkgData.dependencies || {},
|
|
52
|
+
devDependencies: pkgData.devDependencies || {},
|
|
53
|
+
peerDependencies: pkgData.peerDependencies || {},
|
|
54
|
+
scripts: pkgData.scripts || {},
|
|
55
|
+
entryPoints: this.calculatePackageExportsEntries(pkgData, normalizedDir)
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (pkgData.name) {
|
|
59
|
+
this.workspacePackages.set(pkgData.name, normalizedDir);
|
|
60
|
+
this.context.monorepoPackageRoots.add(normalizedDir);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Also try to load tsconfig.json for this workspace
|
|
64
|
+
const tsconfigPath = path.join(matchDir, 'tsconfig.json');
|
|
65
|
+
try {
|
|
66
|
+
const tsconfigData = JSON.parse(await fs.readFile(tsconfigPath, 'utf8'));
|
|
67
|
+
if (pkgData.name) this.tsconfigPaths.set(pkgData.name, tsconfigData);
|
|
68
|
+
} catch(e) {}
|
|
69
|
+
|
|
70
|
+
} catch (e) {}
|
|
131
71
|
}
|
|
132
72
|
}
|
|
133
73
|
}
|
|
134
|
-
return patterns;
|
|
135
74
|
} catch (e) {
|
|
136
|
-
|
|
75
|
+
if (this.context.verbose) console.log('[Workspace] No root package.json found or invalid.');
|
|
137
76
|
}
|
|
138
77
|
}
|
|
139
78
|
|
|
140
|
-
|
|
141
|
-
const normalizedDir = matchDir.replace(/\\/g, '/');
|
|
142
|
-
const pkgPath = path.join(matchDir, 'package.json');
|
|
143
|
-
let pkgData = null;
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
pkgData = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
147
|
-
} catch (e) {
|
|
148
|
-
// UPGRADE: If no package.json, it's not a package – skip silently
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const packageName = pkgData.name || path.basename(matchDir);
|
|
153
|
-
|
|
154
|
-
this.packageManifests.set(normalizedDir, {
|
|
155
|
-
rootDirectory: normalizedDir,
|
|
156
|
-
manifestPath: pkgPath.replace(/\\/g, '/'),
|
|
157
|
-
name: packageName,
|
|
158
|
-
version: pkgData.version,
|
|
159
|
-
dependencies: pkgData.dependencies || {},
|
|
160
|
-
devDependencies: pkgData.devDependencies || {},
|
|
161
|
-
peerDependencies: pkgData.peerDependencies || {},
|
|
162
|
-
scripts: pkgData.scripts || {},
|
|
163
|
-
entryPoints: await this.calculatePackageExportsEntries(pkgData, normalizedDir)
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
this.workspacePackages.set(packageName, normalizedDir);
|
|
167
|
-
this.context.monorepoPackageRoots.add(normalizedDir);
|
|
168
|
-
|
|
169
|
-
// tsconfig.json
|
|
170
|
-
const tsconfigPath = path.join(matchDir, 'tsconfig.json');
|
|
171
|
-
try {
|
|
172
|
-
const tsconfigRaw = await fs.readFile(tsconfigPath, 'utf8');
|
|
173
|
-
const stripped = tsconfigRaw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
174
|
-
const tsconfigData = JSON.parse(stripped);
|
|
175
|
-
this.tsconfigPaths.set(packageName, tsconfigData);
|
|
176
|
-
this.tsconfigPaths.set(normalizedDir, tsconfigData);
|
|
177
|
-
} catch (e) {}
|
|
178
|
-
|
|
179
|
-
await this._loadPackageConfigFiles(matchDir, normalizedDir, packageName);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async _loadPackageConfigFiles(packageDir, normalizedDir, packageName) {
|
|
183
|
-
const configEntries = [];
|
|
184
|
-
let dirEntries;
|
|
185
|
-
try {
|
|
186
|
-
dirEntries = await fs.readdir(packageDir, { withFileTypes: true });
|
|
187
|
-
} catch (e) { return; }
|
|
188
|
-
|
|
189
|
-
for (const entry of dirEntries) {
|
|
190
|
-
if (!entry.isFile()) continue;
|
|
191
|
-
const isConfig = /\.config\.(ts|js|mjs|cjs)$/.test(entry.name);
|
|
192
|
-
const isJson = entry.name === 'package.json' || entry.name === 'tsconfig.json';
|
|
193
|
-
|
|
194
|
-
if (!isConfig && !isJson) continue;
|
|
195
|
-
|
|
196
|
-
const filePath = path.join(packageDir, entry.name).replace(/\\/g, '/');
|
|
197
|
-
try {
|
|
198
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
199
|
-
configEntries.push({ fileName: entry.name, filePath, content });
|
|
200
|
-
} catch (e) {}
|
|
201
|
-
}
|
|
202
|
-
if (configEntries.length > 0) this.packageConfigFiles.set(normalizedDir, configEntries);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async calculatePackageExportsEntries(pkgData, dirPath) {
|
|
79
|
+
calculatePackageExportsEntries(pkgData, dirPath) {
|
|
206
80
|
const entries = [];
|
|
207
81
|
const addEntry = (p) => {
|
|
208
82
|
if (typeof p === 'string') entries.push(path.resolve(dirPath, p).replace(/\\/g, '/'));
|
|
@@ -213,7 +87,7 @@ export class WorkspaceGraph {
|
|
|
213
87
|
if (pkgData.source) addEntry(pkgData.source);
|
|
214
88
|
if (pkgData.types) addEntry(pkgData.types);
|
|
215
89
|
if (pkgData.typings) addEntry(pkgData.typings);
|
|
216
|
-
|
|
90
|
+
|
|
217
91
|
if (pkgData.bin) {
|
|
218
92
|
if (typeof pkgData.bin === 'string') addEntry(pkgData.bin);
|
|
219
93
|
else Object.values(pkgData.bin).forEach(addEntry);
|
|
@@ -221,33 +95,23 @@ export class WorkspaceGraph {
|
|
|
221
95
|
|
|
222
96
|
if (pkgData.exports) {
|
|
223
97
|
const traverseExports = (obj) => {
|
|
224
|
-
if (typeof obj === 'string')
|
|
225
|
-
|
|
98
|
+
if (typeof obj === 'string') {
|
|
99
|
+
addEntry(obj);
|
|
100
|
+
} else if (typeof obj === 'object' && obj !== null) {
|
|
226
101
|
for (const key in obj) traverseExports(obj[key]);
|
|
227
102
|
}
|
|
228
103
|
};
|
|
229
104
|
traverseExports(pkgData.exports);
|
|
230
105
|
}
|
|
231
106
|
|
|
232
|
-
// UPGRADE: If no explicit entry points found, look for standard index files (poly-extension)
|
|
233
|
-
if (entries.length === 0) {
|
|
234
|
-
const standardIndexFiles = ['index.js', 'index.ts', 'index.jsx', 'index.tsx', 'index.mjs', 'index.cjs'];
|
|
235
|
-
for (const fileName of standardIndexFiles) {
|
|
236
|
-
const fullPath = path.join(dirPath, fileName);
|
|
237
|
-
try {
|
|
238
|
-
await fs.access(fullPath);
|
|
239
|
-
addEntry(fileName);
|
|
240
|
-
break; // Take the first one found
|
|
241
|
-
} catch {}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
107
|
return entries;
|
|
246
108
|
}
|
|
247
109
|
|
|
248
110
|
isLocalWorkspaceSpecifier(specifier) {
|
|
249
111
|
if (!specifier) return false;
|
|
112
|
+
// Direct match
|
|
250
113
|
if (this.workspacePackages.has(specifier)) return true;
|
|
114
|
+
// Sub-path match (e.g. @my-org/ui/components)
|
|
251
115
|
for (const pkgName of this.workspacePackages.keys()) {
|
|
252
116
|
if (specifier.startsWith(pkgName + '/')) return true;
|
|
253
117
|
}
|
|
@@ -269,86 +133,51 @@ export class WorkspaceGraph {
|
|
|
269
133
|
}
|
|
270
134
|
|
|
271
135
|
markWorkspacePackagesAsUsed() {
|
|
272
|
-
for (const [pkgName] of this.workspacePackages.entries()) {
|
|
136
|
+
for (const [pkgName, dirPath] of this.workspacePackages.entries()) {
|
|
273
137
|
this.context.usedExternalPackages.add(pkgName);
|
|
274
138
|
}
|
|
275
139
|
}
|
|
276
140
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
for (const { fileName, filePath } of entries) {
|
|
282
|
-
if (/\.config\.(ts|js|mjs|cjs)$/.test(fileName)) configFiles.add(filePath);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
for (const root of this.context.monorepoPackageRoots) {
|
|
286
|
-
const normalizedRoot = root.replace(/\\/g, '/');
|
|
287
|
-
if (!this.packageConfigFiles.has(normalizedRoot)) await this._scanDirForConfigs(root, configFiles);
|
|
288
|
-
}
|
|
289
|
-
return Array.from(configFiles);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async _scanDirForConfigs(dir, configFiles) {
|
|
293
|
-
try {
|
|
294
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
295
|
-
for (const entry of entries) {
|
|
296
|
-
if (entry.isFile() && entry.name.includes('.config.') && /\.(ts|js|mjs|cjs)$/.test(entry.name)) {
|
|
297
|
-
configFiles.add(path.join(dir, entry.name).replace(/\\/g, '/'));
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
} catch (e) {}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
getPackageConfigFiles(dirOrName) {
|
|
304
|
-
const normalized = dirOrName.replace(/\\/g, '/');
|
|
305
|
-
if (this.packageConfigFiles.has(normalized)) return this.packageConfigFiles.get(normalized);
|
|
306
|
-
const dir = this.workspacePackages.get(dirOrName);
|
|
307
|
-
if (dir) return this.packageConfigFiles.get(dir) || [];
|
|
308
|
-
return [];
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
getPackageTsConfig(dirOrName) {
|
|
312
|
-
const normalized = dirOrName.replace(/\\/g, '/');
|
|
313
|
-
if (this.tsconfigPaths.has(normalized)) return this.tsconfigPaths.get(normalized);
|
|
314
|
-
const dir = this.workspacePackages.get(dirOrName);
|
|
315
|
-
if (dir) return this.tsconfigPaths.get(dir);
|
|
316
|
-
return undefined;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
getSummary() {
|
|
320
|
-
return {
|
|
321
|
-
packages: this.packageManifests.size,
|
|
322
|
-
tsconfigsLoaded: this.tsconfigPaths.size,
|
|
323
|
-
configFilesLoaded: Array.from(this.packageConfigFiles.values()).reduce((sum, arr) => sum + arr.length, 0),
|
|
324
|
-
monorepoConfigs: Array.from(this.monorepoConfigs.keys()),
|
|
325
|
-
packageNames: Array.from(this.workspacePackages.keys())
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Expands a workspace glob pattern (e.g. 'packages/*') to absolute directory paths.
|
|
143
|
+
* Supports single-level wildcards and direct paths.
|
|
144
|
+
*/
|
|
329
145
|
async _expandGlob(pattern, cwd) {
|
|
330
146
|
const results = [];
|
|
147
|
+
|
|
148
|
+
// Remove trailing slash
|
|
331
149
|
const cleanPattern = pattern.replace(/\/$/, '');
|
|
150
|
+
|
|
151
|
+
// Handle simple wildcard patterns like 'packages/*' or 'apps/*'
|
|
332
152
|
if (cleanPattern.includes('*')) {
|
|
333
153
|
const parts = cleanPattern.split('/');
|
|
334
154
|
const wildcardIndex = parts.findIndex(p => p.includes('*'));
|
|
335
155
|
const baseDir = path.join(cwd, ...parts.slice(0, wildcardIndex));
|
|
156
|
+
|
|
336
157
|
try {
|
|
337
158
|
const entries = await fs.readdir(baseDir, { withFileTypes: true });
|
|
338
159
|
for (const entry of entries) {
|
|
339
160
|
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
340
161
|
const fullPath = path.join(baseDir, entry.name);
|
|
341
|
-
|
|
162
|
+
// Check if it has a package.json
|
|
163
|
+
try {
|
|
164
|
+
await fs.access(path.join(fullPath, 'package.json'));
|
|
165
|
+
results.push(fullPath.replace(/\\/g, '/'));
|
|
166
|
+
} catch (e) {}
|
|
342
167
|
}
|
|
343
168
|
}
|
|
344
169
|
} catch (e) {}
|
|
345
170
|
} else {
|
|
171
|
+
// Direct path
|
|
346
172
|
const fullPath = path.resolve(cwd, cleanPattern);
|
|
347
173
|
try {
|
|
348
174
|
const stat = await fs.stat(fullPath);
|
|
349
|
-
if (stat.isDirectory())
|
|
175
|
+
if (stat.isDirectory()) {
|
|
176
|
+
results.push(fullPath.replace(/\\/g, '/'));
|
|
177
|
+
}
|
|
350
178
|
} catch (e) {}
|
|
351
179
|
}
|
|
180
|
+
|
|
352
181
|
return results;
|
|
353
182
|
}
|
|
354
183
|
}
|