entkapp 5.4.0 → 5.5.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/package.json +1 -1
- package/src/EngineContext.js +0 -6
- package/src/ast/ASTAnalyzer.js +6 -94
- package/src/ast/MagicDetector.js +43 -23
- package/src/ast/OxcAnalyzer.js +27 -190
- package/src/index.js +4 -87
- package/src/performance/GraphCache.js +0 -27
- package/src/performance/WorkerPool.js +4 -22
- package/src/performance/WorkerTaskRunner.js +0 -26
- package/src/plugins/BasePlugin.js +27 -16
- package/src/plugins/PluginRegistry.js +0 -44
- package/src/plugins/ecosystems/UltimateBundle.js +0 -259
- package/src/resolution/ConfigLoader.js +26 -190
- package/src/resolution/GraphAnalyzer.js +1 -65
- package/src/resolution/PathMapper.js +0 -81
- package/src/resolution/TSConfigLoader.js +1 -3
- package/src/resolution/WorkSpaceGraph.js +89 -260
|
@@ -111,31 +111,6 @@ export class PluginRegistry {
|
|
|
111
111
|
return this.plugins.get(name);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/**
|
|
115
|
-
* Version 5.4.0: Collect entry points from all plugins for a given file.
|
|
116
|
-
* @param {string} content - The file content
|
|
117
|
-
* @param {string} filePath - The absolute path to the file
|
|
118
|
-
* @returns {Array<string>} List of detected entry points
|
|
119
|
-
*/
|
|
120
|
-
detectEntryPointsFromContent(content, filePath) {
|
|
121
|
-
const entryPoints = [];
|
|
122
|
-
for (const plugin of this.plugins.values()) {
|
|
123
|
-
if (typeof plugin.detectEntryPoints === 'function') {
|
|
124
|
-
try {
|
|
125
|
-
const detected = plugin.detectEntryPoints(content, filePath);
|
|
126
|
-
if (Array.isArray(detected)) {
|
|
127
|
-
entryPoints.push(...detected);
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
if (this.context?.verbose) {
|
|
131
|
-
console.warn(`[PluginRegistry] Entry detection failed for plugin "${plugin.name}" on ${filePath}:`, e.message);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return entryPoints;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
114
|
async getActivePlugins(baseDir) {
|
|
140
115
|
const active = [];
|
|
141
116
|
for (const plugin of this.plugins.values()) {
|
|
@@ -146,25 +121,6 @@ export class PluginRegistry {
|
|
|
146
121
|
return active;
|
|
147
122
|
}
|
|
148
123
|
|
|
149
|
-
/**
|
|
150
|
-
* Version 5.3.0: Run content analysis on a node using all active plugins.
|
|
151
|
-
* @param {GraphNode} node - The node to analyze
|
|
152
|
-
* @param {string} filePath - The absolute path to the file
|
|
153
|
-
*/
|
|
154
|
-
async runPluginAnalysis(node, filePath) {
|
|
155
|
-
for (const plugin of this.plugins.values()) {
|
|
156
|
-
if (typeof plugin.analyze === 'function') {
|
|
157
|
-
try {
|
|
158
|
-
await plugin.analyze(node, filePath);
|
|
159
|
-
} catch (e) {
|
|
160
|
-
if (this.context?.verbose) {
|
|
161
|
-
console.warn(`[PluginRegistry] Analysis failed for plugin "${plugin.name}" on ${filePath}:`, e.message);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
124
|
/**
|
|
169
125
|
* Version 5.0.0: Run dependency diagnostics across all registered plugins.
|
|
170
126
|
* Returns a consolidated list of missing/misplaced dependencies and orphaned configs.
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import fs from 'fs/promises';
|
|
10
10
|
import { BasePlugin } from '../BasePlugin.js';
|
|
11
|
-
import { FrameworkConfigParser } from '../../resolution/FrameworkConfigParser.js';
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
export class GraphQLPlugin extends BasePlugin {
|
|
@@ -212,27 +211,6 @@ export class NuxtPlugin extends BasePlugin {
|
|
|
212
211
|
}
|
|
213
212
|
return false;
|
|
214
213
|
}
|
|
215
|
-
detectEntryPoints(content, filePath) {
|
|
216
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
217
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
218
|
-
const { entries } = parser.parse(content, filePath);
|
|
219
|
-
return Array.from(entries);
|
|
220
|
-
}
|
|
221
|
-
return [];
|
|
222
|
-
}
|
|
223
|
-
async analyze(node, filePath) {
|
|
224
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
225
|
-
node.isEntry = true;
|
|
226
|
-
if (node.rawCode) {
|
|
227
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
228
|
-
const { aliases, entries } = parser.parse(node.rawCode, filePath);
|
|
229
|
-
if (aliases.size > 0) node.frameworkAliases = aliases;
|
|
230
|
-
if (entries.size > 0) node.frameworkEntries = entries;
|
|
231
|
-
if (node.rawCode.includes('modules:')) node.hasNuxtModules = true;
|
|
232
|
-
if (node.rawCode.includes('components:')) node.hasNuxtComponentsConfig = true;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
214
|
}
|
|
237
215
|
|
|
238
216
|
export class RemixPlugin extends BasePlugin {
|
|
@@ -255,21 +233,6 @@ export class RemixPlugin extends BasePlugin {
|
|
|
255
233
|
}
|
|
256
234
|
return false;
|
|
257
235
|
}
|
|
258
|
-
detectEntryPoints(content, filePath) {
|
|
259
|
-
if (filePath.endsWith('remix.config.js') || filePath.includes('vite.config.')) {
|
|
260
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
261
|
-
const { entries } = parser.parse(content, filePath);
|
|
262
|
-
return Array.from(entries);
|
|
263
|
-
}
|
|
264
|
-
return [];
|
|
265
|
-
}
|
|
266
|
-
async analyze(node, filePath) {
|
|
267
|
-
if (filePath.endsWith('remix.config.js')) {
|
|
268
|
-
node.isEntry = true;
|
|
269
|
-
if (node.rawCode?.includes('ignoredRouteFiles:')) node.hasRemixIgnoredRoutes = true;
|
|
270
|
-
if (node.rawCode?.includes('serverBuildPath:')) node.hasRemixServerBuildPath = true;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
236
|
}
|
|
274
237
|
|
|
275
238
|
export class SvelteKitPlugin extends BasePlugin {
|
|
@@ -288,29 +251,6 @@ export class SvelteKitPlugin extends BasePlugin {
|
|
|
288
251
|
async isActive(baseDir) {
|
|
289
252
|
try { await fs.access(path.join(baseDir, 'svelte.config.js')); return true; } catch { return false; }
|
|
290
253
|
}
|
|
291
|
-
detectEntryPoints(content, filePath) {
|
|
292
|
-
if (filePath.includes('svelte.config.')) {
|
|
293
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
294
|
-
const { entries } = parser.parse(content, filePath);
|
|
295
|
-
return Array.from(entries);
|
|
296
|
-
}
|
|
297
|
-
return [];
|
|
298
|
-
}
|
|
299
|
-
async analyze(node, filePath) {
|
|
300
|
-
if (filePath.includes('svelte.config.')) {
|
|
301
|
-
node.isEntry = true;
|
|
302
|
-
if (node.rawCode) {
|
|
303
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
304
|
-
const { aliases, entries } = parser.parse(node.rawCode, filePath);
|
|
305
|
-
if (aliases.size > 0) {
|
|
306
|
-
node.frameworkAliases = aliases;
|
|
307
|
-
node.hasSvelteAliases = true;
|
|
308
|
-
}
|
|
309
|
-
if (entries.size > 0) node.frameworkEntries = entries;
|
|
310
|
-
if (node.rawCode.includes('adapter:')) node.hasSvelteAdapter = true;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
254
|
}
|
|
315
255
|
|
|
316
256
|
export class AstroPlugin extends BasePlugin {
|
|
@@ -325,39 +265,12 @@ export class AstroPlugin extends BasePlugin {
|
|
|
325
265
|
}
|
|
326
266
|
return false;
|
|
327
267
|
}
|
|
328
|
-
detectEntryPoints(content, filePath) {
|
|
329
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
330
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
331
|
-
const { entries } = parser.parse(content, filePath);
|
|
332
|
-
return Array.from(entries);
|
|
333
|
-
}
|
|
334
|
-
return [];
|
|
335
|
-
}
|
|
336
|
-
async analyze(node, filePath) {
|
|
337
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
338
|
-
node.isEntry = true;
|
|
339
|
-
if (node.rawCode) {
|
|
340
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
341
|
-
const { aliases, entries } = parser.parse(node.rawCode, filePath);
|
|
342
|
-
if (aliases.size > 0) node.frameworkAliases = aliases;
|
|
343
|
-
if (entries.size > 0) node.frameworkEntries = entries;
|
|
344
|
-
if (node.rawCode.includes('integrations:')) node.hasAstroIntegrations = true;
|
|
345
|
-
if (node.rawCode.includes('output:')) node.hasAstroOutputConfig = true;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
268
|
}
|
|
350
269
|
|
|
351
270
|
export class VitepressPlugin extends BasePlugin {
|
|
352
271
|
get name() { return 'vitepress'; }
|
|
353
272
|
getConfigFiles() { return ['package.json']; }
|
|
354
273
|
getRequiredPackages() { return [{ name: 'vitepress', dev: true }]; }
|
|
355
|
-
|
|
356
|
-
getRoutePatterns() {
|
|
357
|
-
// Protection: Every file inside a .vitepress folder is treated as an entry point
|
|
358
|
-
return [/\/\.vitepress\//];
|
|
359
|
-
}
|
|
360
|
-
|
|
361
274
|
async isActive(baseDir) {
|
|
362
275
|
try {
|
|
363
276
|
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
@@ -369,62 +282,6 @@ export class VitepressPlugin extends BasePlugin {
|
|
|
369
282
|
return hasDep;
|
|
370
283
|
} catch { return false; }
|
|
371
284
|
}
|
|
372
|
-
|
|
373
|
-
async runDependencyDiagnostics(baseDir) {
|
|
374
|
-
const diagnostics = await super.runDependencyDiagnostics(baseDir);
|
|
375
|
-
|
|
376
|
-
let pkg = null;
|
|
377
|
-
try {
|
|
378
|
-
const raw = await fs.readFile(path.join(baseDir, 'package.json'), 'utf8');
|
|
379
|
-
pkg = JSON.parse(raw);
|
|
380
|
-
} catch { return diagnostics; }
|
|
381
|
-
|
|
382
|
-
const scripts = pkg.scripts || {};
|
|
383
|
-
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
384
|
-
const hasVitepressDep = 'vitepress' in allDeps;
|
|
385
|
-
|
|
386
|
-
// Check if any script uses vitepress
|
|
387
|
-
const hasVitepressCommand = Object.values(scripts).some(s => s.includes('vitepress'));
|
|
388
|
-
|
|
389
|
-
// Logic for .vitepress folder existence
|
|
390
|
-
let hasVitepressFolder = false;
|
|
391
|
-
const possibleDirs = ['.vitepress', 'docs/.vitepress', 'docs/docs/.vitepress'];
|
|
392
|
-
for (const d of possibleDirs) {
|
|
393
|
-
try {
|
|
394
|
-
await fs.access(path.join(baseDir, d));
|
|
395
|
-
hasVitepressFolder = true;
|
|
396
|
-
break;
|
|
397
|
-
} catch {}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Scenario 1: build cmd/dependency exists but no folder -> unused dependency
|
|
401
|
-
if ((hasVitepressCommand || hasVitepressDep) && !hasVitepressFolder) {
|
|
402
|
-
diagnostics.push({
|
|
403
|
-
plugin: this.name,
|
|
404
|
-
severity: 'warning',
|
|
405
|
-
message: `Plugin "${this.name}": "vitepress" is defined in package.json or scripts, but no ".vitepress" folder was found. This might be an unused dependency.`
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Scenario 2: folder exists but no dependency -> missing dependency
|
|
410
|
-
if (hasVitepressFolder && !hasVitepressDep) {
|
|
411
|
-
diagnostics.push({
|
|
412
|
-
plugin: this.name,
|
|
413
|
-
severity: 'error',
|
|
414
|
-
message: `Plugin "${this.name}": ".vitepress" folder exists, but "vitepress" package is not installed in package.json.`
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return diagnostics;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
async analyze(node, filePath) {
|
|
422
|
-
// If it's a config file inside .vitepress, mark it
|
|
423
|
-
if (filePath.includes('/.vitepress/config.') || filePath.includes('/.vitepress/theme/')) {
|
|
424
|
-
node.isEntry = true;
|
|
425
|
-
node.isVitepressInternal = true;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
285
|
}
|
|
429
286
|
|
|
430
287
|
export class GatsbyPlugin extends BasePlugin {
|
|
@@ -464,14 +321,6 @@ export class NextJsPlugin extends BasePlugin {
|
|
|
464
321
|
}
|
|
465
322
|
return false;
|
|
466
323
|
}
|
|
467
|
-
async analyze(node, filePath) {
|
|
468
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
469
|
-
node.isEntry = true;
|
|
470
|
-
if (node.rawCode?.includes('rewrites')) node.hasNextRewrites = true;
|
|
471
|
-
if (node.rawCode?.includes('redirects')) node.hasNextRedirects = true;
|
|
472
|
-
if (node.rawCode?.includes('images:')) node.hasNextImageConfig = true;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
324
|
}
|
|
476
325
|
|
|
477
326
|
export class ReactPlugin extends BasePlugin {
|
|
@@ -594,42 +443,6 @@ export class VitePlugin extends BasePlugin {
|
|
|
594
443
|
}
|
|
595
444
|
return false;
|
|
596
445
|
}
|
|
597
|
-
detectEntryPoints(content, filePath) {
|
|
598
|
-
if (filePath.includes('vite.config.')) {
|
|
599
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
600
|
-
const { entries } = parser.parse(content, filePath);
|
|
601
|
-
return Array.from(entries);
|
|
602
|
-
}
|
|
603
|
-
return [];
|
|
604
|
-
}
|
|
605
|
-
async analyze(node, filePath) {
|
|
606
|
-
if (filePath.includes('vite.config.')) {
|
|
607
|
-
// Mark config as an entry so its own dependencies are tracked
|
|
608
|
-
node.isEntry = true;
|
|
609
|
-
|
|
610
|
-
// UPGRADE: Use FrameworkConfigParser for deep structural extraction
|
|
611
|
-
if (node.rawCode) {
|
|
612
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
613
|
-
const { aliases, entries } = parser.parse(node.rawCode, filePath);
|
|
614
|
-
|
|
615
|
-
if (aliases.size > 0) {
|
|
616
|
-
node.frameworkAliases = aliases;
|
|
617
|
-
node.viteAliasesDetected = true;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (entries.size > 0) {
|
|
621
|
-
node.frameworkEntries = entries;
|
|
622
|
-
node.hasCustomViteInputs = true;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Detect Plugins (Keep existing simple check)
|
|
626
|
-
const pluginsPattern = /plugins\s*:\s*\[([\s\S]*?)\]/g;
|
|
627
|
-
if (pluginsPattern.test(node.rawCode)) {
|
|
628
|
-
node.hasVitePlugins = true;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
446
|
}
|
|
634
447
|
|
|
635
448
|
export class EsbuildPlugin extends BasePlugin {
|
|
@@ -666,30 +479,6 @@ export class WebpackPlugin extends BasePlugin {
|
|
|
666
479
|
}
|
|
667
480
|
return false;
|
|
668
481
|
}
|
|
669
|
-
detectEntryPoints(content, filePath) {
|
|
670
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
671
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
672
|
-
const { entries } = parser.parse(content, filePath);
|
|
673
|
-
return Array.from(entries);
|
|
674
|
-
}
|
|
675
|
-
return [];
|
|
676
|
-
}
|
|
677
|
-
detectEntryPoints(content, filePath) {
|
|
678
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
679
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
680
|
-
const { entries } = parser.parse(content, filePath);
|
|
681
|
-
return Array.from(entries);
|
|
682
|
-
}
|
|
683
|
-
return [];
|
|
684
|
-
}
|
|
685
|
-
async analyze(node, filePath) {
|
|
686
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
687
|
-
node.isEntry = true;
|
|
688
|
-
if (node.rawCode?.includes('entry:')) node.hasWebpackEntries = true;
|
|
689
|
-
if (node.rawCode?.includes('resolve:')) node.hasWebpackResolve = true;
|
|
690
|
-
if (node.rawCode?.includes('plugins:')) node.hasWebpackPlugins = true;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
482
|
}
|
|
694
483
|
|
|
695
484
|
export class ParcelPlugin extends BasePlugin {
|
|
@@ -732,14 +521,6 @@ export class TailwindPlugin extends BasePlugin {
|
|
|
732
521
|
}
|
|
733
522
|
return false;
|
|
734
523
|
}
|
|
735
|
-
async analyze(node, filePath) {
|
|
736
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
737
|
-
node.isEntry = true;
|
|
738
|
-
if (node.rawCode?.includes('content:')) node.hasTailwindContent = true;
|
|
739
|
-
if (node.rawCode?.includes('theme:')) node.hasTailwindTheme = true;
|
|
740
|
-
if (node.rawCode?.includes('plugins:')) node.hasTailwindPlugins = true;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
524
|
}
|
|
744
525
|
|
|
745
526
|
export class PostcssPlugin extends BasePlugin {
|
|
@@ -884,13 +665,6 @@ export class JestPlugin extends BasePlugin {
|
|
|
884
665
|
async isActive(baseDir) {
|
|
885
666
|
try { await fs.access(path.join(baseDir, 'jest.config.js')); return true; } catch { return false; }
|
|
886
667
|
}
|
|
887
|
-
async analyze(node, filePath) {
|
|
888
|
-
if (filePath.endsWith('jest.config.js')) {
|
|
889
|
-
node.isEntry = true;
|
|
890
|
-
if (node.rawCode?.includes('testEnvironment:')) node.hasJestTestEnv = true;
|
|
891
|
-
if (node.rawCode?.includes('setupFiles:')) node.hasJestSetupFiles = true;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
668
|
}
|
|
895
669
|
|
|
896
670
|
export class VitestPlugin extends BasePlugin {
|
|
@@ -903,39 +677,6 @@ export class VitestPlugin extends BasePlugin {
|
|
|
903
677
|
}
|
|
904
678
|
return false;
|
|
905
679
|
}
|
|
906
|
-
detectEntryPoints(content, filePath) {
|
|
907
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
908
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
909
|
-
const { entries } = parser.parse(content, filePath);
|
|
910
|
-
return Array.from(entries);
|
|
911
|
-
}
|
|
912
|
-
return [];
|
|
913
|
-
}
|
|
914
|
-
detectEntryPoints(content, filePath) {
|
|
915
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
916
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
917
|
-
const { entries } = parser.parse(content, filePath);
|
|
918
|
-
return Array.from(entries);
|
|
919
|
-
}
|
|
920
|
-
return [];
|
|
921
|
-
}
|
|
922
|
-
async analyze(node, filePath) {
|
|
923
|
-
if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
|
|
924
|
-
node.isEntry = true;
|
|
925
|
-
if (node.rawCode) {
|
|
926
|
-
const parser = new FrameworkConfigParser(this.context);
|
|
927
|
-
const { aliases, entries } = parser.parse(node.rawCode, filePath);
|
|
928
|
-
if (aliases.size > 0) {
|
|
929
|
-
node.frameworkAliases = aliases;
|
|
930
|
-
node.hasWebpackAliases = true;
|
|
931
|
-
}
|
|
932
|
-
if (entries.size > 0) {
|
|
933
|
-
node.frameworkEntries = entries;
|
|
934
|
-
node.hasWebpackEntries = true;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
680
|
}
|
|
940
681
|
|
|
941
682
|
export class PlaywrightPlugin extends BasePlugin {
|
|
@@ -3,239 +3,73 @@ import path from 'path';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* ConfigLoader
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Loads and merges entkapp configuration from multiple sources:
|
|
7
|
+
* - entkapp/config.json (project config)
|
|
8
|
+
* - entkapp.config.js / entkapp.config.mjs (JS config)
|
|
9
|
+
* - package.json "entkapp" key
|
|
10
|
+
* - CLI flags (passed as overrides)
|
|
9
11
|
*/
|
|
10
12
|
export class ConfigLoader {
|
|
11
|
-
constructor(cwd) {
|
|
12
|
-
this.cwd = cwd;
|
|
13
|
+
constructor(cwd) {
|
|
14
|
+
this.cwd = cwd;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
async loadConfig(overrides = {}) {
|
|
16
18
|
let config = this._defaultConfig();
|
|
17
19
|
|
|
18
|
-
// 1. entkapp/config.json
|
|
20
|
+
// 1. Try entkapp/config.json
|
|
19
21
|
const jsonConfigPath = path.join(this.cwd, 'entkapp', 'config.json');
|
|
20
22
|
try {
|
|
21
23
|
const raw = await fs.readFile(jsonConfigPath, 'utf8');
|
|
24
|
+
// Strip comments from JSON (JSONC support)
|
|
22
25
|
const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
23
|
-
|
|
26
|
+
const parsed = JSON.parse(stripped);
|
|
27
|
+
config = this._merge(config, parsed);
|
|
24
28
|
} catch (e) {}
|
|
25
29
|
|
|
26
|
-
// 2. entkapp.config.
|
|
27
|
-
for (const configFile of ['entkapp.config.
|
|
30
|
+
// 2. Try entkapp.config.js / entkapp.config.mjs
|
|
31
|
+
for (const configFile of ['entkapp.config.mjs', 'entkapp.config.js', 'entkapp.config.cjs']) {
|
|
28
32
|
const jsConfigPath = path.join(this.cwd, configFile);
|
|
29
33
|
try {
|
|
30
34
|
const mod = await import(jsConfigPath);
|
|
31
35
|
const jsConfig = mod.default || mod;
|
|
32
|
-
if (typeof jsConfig === 'object'
|
|
36
|
+
if (typeof jsConfig === 'object') {
|
|
33
37
|
config = this._merge(config, jsConfig);
|
|
34
38
|
break;
|
|
35
39
|
}
|
|
36
40
|
} catch (e) {}
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
// 3. package.json "entkapp" key
|
|
43
|
+
// 3. Try package.json "entkapp" key
|
|
44
|
+
const pkgPath = path.join(this.cwd, 'package.json');
|
|
40
45
|
try {
|
|
41
|
-
const pkg = JSON.parse(await fs.readFile(
|
|
46
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
42
47
|
if (pkg.entkapp && typeof pkg.entkapp === 'object') {
|
|
43
48
|
config = this._merge(config, pkg.entkapp);
|
|
44
49
|
}
|
|
45
50
|
} catch (e) {}
|
|
46
51
|
|
|
47
|
-
// 4.
|
|
48
|
-
const workspaceData = await this.loadWorkspaceConfigs();
|
|
49
|
-
if (workspaceData.packages.length > 0) {
|
|
50
|
-
config.workspace = true;
|
|
51
|
-
config.workspacePackages = workspaceData.packages;
|
|
52
|
-
config.workspaceTsConfigs = workspaceData.tsConfigs;
|
|
53
|
-
config.workspaceConfigFiles = workspaceData.configFiles;
|
|
54
|
-
config.monorepoConfigs = workspaceData.monorepoConfigs;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 5. CLI overrides
|
|
52
|
+
// 4. Apply CLI overrides
|
|
58
53
|
config = this._merge(config, overrides);
|
|
59
54
|
|
|
60
|
-
// Final sanity check: ensure arrays exist
|
|
61
|
-
if (!Array.isArray(config.entryPoints)) config.entryPoints = [];
|
|
62
|
-
if (!Array.isArray(config.workspacePackages)) config.workspacePackages = [];
|
|
63
|
-
if (!Array.isArray(config.workspaceTsConfigs)) config.workspaceTsConfigs = [];
|
|
64
|
-
if (!Array.isArray(config.workspaceConfigFiles)) config.workspaceConfigFiles = [];
|
|
65
|
-
|
|
66
55
|
return config;
|
|
67
56
|
}
|
|
68
57
|
|
|
69
|
-
async loadWorkspaceConfigs() {
|
|
70
|
-
const result = {
|
|
71
|
-
packages: [],
|
|
72
|
-
tsConfigs: [],
|
|
73
|
-
configFiles: [],
|
|
74
|
-
monorepoConfigs: {}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// Load global configs first
|
|
78
|
-
const globalConfigs = ['turbo.json', 'nx.json', 'lerna.json'];
|
|
79
|
-
for (const file of globalConfigs) {
|
|
80
|
-
try {
|
|
81
|
-
const content = JSON.parse(await fs.readFile(path.join(this.cwd, file), 'utf8'));
|
|
82
|
-
result.monorepoConfigs[file] = content;
|
|
83
|
-
} catch (e) {}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const patterns = await this._resolveWorkspacePatterns();
|
|
87
|
-
if (patterns.length === 0) return result;
|
|
88
|
-
|
|
89
|
-
for (const pattern of patterns) {
|
|
90
|
-
const dirs = await this._expandGlob(pattern);
|
|
91
|
-
for (const dir of dirs) {
|
|
92
|
-
await this._readPackageDir(dir, result);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return result;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async _resolveWorkspacePatterns() {
|
|
100
|
-
const pnpmPatterns = await this._parseWorkspaceYaml('pnpm-workspace.yaml');
|
|
101
|
-
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
const pkg = JSON.parse(await fs.readFile(path.join(this.cwd, 'package.json'), 'utf8'));
|
|
105
|
-
if (pkg.workspaces) {
|
|
106
|
-
return Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages || [];
|
|
107
|
-
}
|
|
108
|
-
} catch (e) {}
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const lerna = JSON.parse(await fs.readFile(path.join(this.cwd, 'lerna.json'), 'utf8'));
|
|
112
|
-
if (lerna.packages) return lerna.packages;
|
|
113
|
-
} catch (e) {}
|
|
114
|
-
|
|
115
|
-
return this._parseWorkspaceYaml('workspace.yaml');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async _parseWorkspaceYaml(fileName) {
|
|
119
|
-
const filePath = path.join(this.cwd, fileName);
|
|
120
|
-
try {
|
|
121
|
-
const raw = await fs.readFile(filePath, 'utf8');
|
|
122
|
-
const patterns = [];
|
|
123
|
-
const lines = raw.split('\n');
|
|
124
|
-
let inPackages = false;
|
|
125
|
-
for (const line of lines) {
|
|
126
|
-
const trimmed = line.trim();
|
|
127
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
128
|
-
if (/^packages\s*:/.test(trimmed)) {
|
|
129
|
-
inPackages = true;
|
|
130
|
-
const inlineMatch = trimmed.match(/^packages\s*:\s*\[([^\]]*)\]/);
|
|
131
|
-
if (inlineMatch) {
|
|
132
|
-
inPackages = false;
|
|
133
|
-
for (const item of inlineMatch[1].split(',')) {
|
|
134
|
-
const val = item.trim().replace(/^['"]|['"]$/g, '');
|
|
135
|
-
if (val) patterns.push(val);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (inPackages) {
|
|
141
|
-
if (line.match(/^[a-zA-Z0-9_-]/) && trimmed.endsWith(':')) {
|
|
142
|
-
inPackages = false;
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
if (trimmed.startsWith('-')) {
|
|
146
|
-
const val = trimmed.substring(1).trim().replace(/^['"]|['"]$/g, '');
|
|
147
|
-
if (val) patterns.push(val);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return patterns;
|
|
152
|
-
} catch (e) {
|
|
153
|
-
return [];
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async _readPackageDir(dir, result) {
|
|
158
|
-
const normalizedDir = dir.replace(/\\/g, '/');
|
|
159
|
-
let packageName;
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
const pkg = JSON.parse(await fs.readFile(path.join(dir, 'package.json'), 'utf8'));
|
|
163
|
-
packageName = pkg.name;
|
|
164
|
-
result.packages.push({
|
|
165
|
-
dir: normalizedDir,
|
|
166
|
-
name: packageName,
|
|
167
|
-
entkappConfig: (pkg.entkapp && typeof pkg.entkapp === 'object') ? pkg.entkapp : {}
|
|
168
|
-
});
|
|
169
|
-
} catch (e) {
|
|
170
|
-
// If no package.json, skip as requested in WorkSpaceGraph logic
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// tsconfig.json
|
|
175
|
-
try {
|
|
176
|
-
const tsconfigRaw = await fs.readFile(path.join(dir, 'tsconfig.json'), 'utf8');
|
|
177
|
-
const stripped = tsconfigRaw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
178
|
-
result.tsConfigs.push({ dir: normalizedDir, name: packageName, tsconfig: JSON.parse(stripped) });
|
|
179
|
-
} catch (e) {}
|
|
180
|
-
|
|
181
|
-
// *.config.ts / .js
|
|
182
|
-
try {
|
|
183
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
184
|
-
for (const entry of entries) {
|
|
185
|
-
if (!entry.isFile() || !/\.config\.(ts|js|mjs|cjs)$/.test(entry.name)) continue;
|
|
186
|
-
const filePath = path.join(dir, entry.name).replace(/\\/g, '/');
|
|
187
|
-
try {
|
|
188
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
189
|
-
result.configFiles.push({ dir: normalizedDir, name: packageName, fileName: entry.name, filePath, content });
|
|
190
|
-
} catch (e) {}
|
|
191
|
-
}
|
|
192
|
-
} catch (e) {}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async _expandGlob(pattern) {
|
|
196
|
-
const results = [];
|
|
197
|
-
const cleanPattern = pattern.replace(/\/$/, '');
|
|
198
|
-
if (cleanPattern.includes('*')) {
|
|
199
|
-
const parts = cleanPattern.split('/');
|
|
200
|
-
const wildcardIndex = parts.findIndex(p => p.includes('*'));
|
|
201
|
-
const baseDir = path.join(this.cwd, ...parts.slice(0, wildcardIndex));
|
|
202
|
-
try {
|
|
203
|
-
const entries = await fs.readdir(baseDir, { withFileTypes: true });
|
|
204
|
-
for (const entry of entries) {
|
|
205
|
-
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
206
|
-
const fullPath = path.join(baseDir, entry.name);
|
|
207
|
-
try {
|
|
208
|
-
await fs.access(path.join(fullPath, 'package.json'));
|
|
209
|
-
results.push(fullPath.replace(/\\/g, '/'));
|
|
210
|
-
} catch (e) {}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} catch (e) {}
|
|
214
|
-
} else {
|
|
215
|
-
const fullPath = path.resolve(this.cwd, cleanPattern);
|
|
216
|
-
try {
|
|
217
|
-
const stat = await fs.stat(fullPath);
|
|
218
|
-
if (stat.isDirectory()) results.push(fullPath.replace(/\\/g, '/'));
|
|
219
|
-
} catch (e) {}
|
|
220
|
-
}
|
|
221
|
-
return results;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
58
|
_defaultConfig() {
|
|
225
59
|
return {
|
|
226
60
|
interface: 'CLI',
|
|
227
61
|
useBuiltinPlugins: true,
|
|
228
62
|
useCustomPlugins: true,
|
|
229
|
-
options: {
|
|
63
|
+
options: {
|
|
64
|
+
verbose: false,
|
|
65
|
+
fastMode: true,
|
|
66
|
+
selfHealing: true
|
|
67
|
+
},
|
|
230
68
|
enabledPlugins: [],
|
|
231
69
|
ignoreDependencies: ['entkapp', '@types/*'],
|
|
232
70
|
exclude: ['node_modules', '.git', 'dist', 'build', 'coverage'],
|
|
233
71
|
entryPoints: [],
|
|
234
|
-
workspace: false
|
|
235
|
-
workspacePackages: [],
|
|
236
|
-
workspaceTsConfigs: [],
|
|
237
|
-
workspaceConfigFiles: [],
|
|
238
|
-
monorepoConfigs: {}
|
|
72
|
+
workspace: false
|
|
239
73
|
};
|
|
240
74
|
}
|
|
241
75
|
|
|
@@ -245,6 +79,8 @@ export class ConfigLoader {
|
|
|
245
79
|
for (const key of Object.keys(override)) {
|
|
246
80
|
if (key === 'options' && typeof override[key] === 'object') {
|
|
247
81
|
result.options = { ...(base.options || {}), ...override[key] };
|
|
82
|
+
} else if (Array.isArray(override[key])) {
|
|
83
|
+
result[key] = override[key];
|
|
248
84
|
} else {
|
|
249
85
|
result[key] = override[key];
|
|
250
86
|
}
|