pkg-scaffold 2.3.0 → 3.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.
- package/.scaffold-ignore +22 -0
- package/README.md +94 -107
- package/bin/cli.js +94 -0
- package/index.js +0 -0
- package/package.json +18 -6
- package/src/EngineContext.js +287 -0
- package/src/ast/ASTAnalyzer.js +325 -0
- package/src/ast/BarrelParser.js +189 -0
- package/src/ast/MagicDetector.js +155 -0
- package/src/healing/GitSandbox.js +160 -0
- package/src/healing/SelfHealer.js +150 -0
- package/src/index.js +384 -0
- package/src/performance/GraphCache.js +84 -0
- package/src/performance/SupplyChainGuard.js +106 -0
- package/src/performance/WorkerPool.js +92 -0
- package/src/performance/WorkerTaskRunner.js +66 -0
- package/src/refractor/ImpactAnalyzer.js +92 -0
- package/src/refractor/SourceRewriter.js +86 -0
- package/src/refractor/TransactionManager.js +131 -0
- package/src/refractor/TypeIntegrity.js +75 -0
- package/src/resolution/DepencyResolver.js +131 -0
- package/src/resolution/PathMapper.js +115 -0
- package/src/resolution/WorkSpaceGraph.js +171 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Ambient Type Declaration (`.d.ts`) Alignment Supervisor
|
|
7
|
+
* Updates declaration mapping entries to keep compiler checks stable.
|
|
8
|
+
*/
|
|
9
|
+
export class TypeIntegrity {
|
|
10
|
+
constructor(context) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Re-evaluates ambient files to verify type alignment after optimization changes.
|
|
16
|
+
* @param {string} sourceFilePath - Absolute filename target context location
|
|
17
|
+
* @param {string} prunedSymbolName - Target variable token removed from active source graph
|
|
18
|
+
*/
|
|
19
|
+
async synchronizeDeclarationFile(sourceFilePath, prunedSymbolName) {
|
|
20
|
+
const fileDirectory = path.dirname(sourceFilePath);
|
|
21
|
+
const baselineName = path.basename(sourceFilePath, path.extname(sourceFilePath));
|
|
22
|
+
|
|
23
|
+
// Map standard ambient types build output combinations
|
|
24
|
+
const declarationTargets = [
|
|
25
|
+
path.join(fileDirectory, `${baselineName}.d.ts`),
|
|
26
|
+
path.join(this.context.cwd, 'dist', `${baselineName}.d.ts`),
|
|
27
|
+
path.join(this.context.cwd, 'types', `${baselineName}.d.ts`)
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
for (const dtsPath of declarationTargets) {
|
|
31
|
+
try {
|
|
32
|
+
await fs.access(dtsPath);
|
|
33
|
+
const code = await fs.readFile(dtsPath, 'utf8');
|
|
34
|
+
|
|
35
|
+
const sourceFile = ts.createSourceFile(
|
|
36
|
+
dtsPath,
|
|
37
|
+
code,
|
|
38
|
+
ts.ScriptTarget.Latest,
|
|
39
|
+
true,
|
|
40
|
+
ts.ScriptKind.TS
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const replacementIntervals = [];
|
|
44
|
+
this.inspectDtsNodes(sourceFile, prunedSymbolName, replacementIntervals);
|
|
45
|
+
|
|
46
|
+
if (replacementIntervals.length > 0) {
|
|
47
|
+
let updatedDtsText = code;
|
|
48
|
+
// Apply substring text deletions from back to front to keep offset indexes accurate
|
|
49
|
+
replacementIntervals.sort((a, b) => b.start - a.start);
|
|
50
|
+
|
|
51
|
+
for (const interval of replacementIntervals) {
|
|
52
|
+
updatedDtsText = updatedDtsText.slice(0, interval.start) + updatedDtsText.slice(interval.end);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await fs.writeFile(dtsPath, updatedDtsText, 'utf8');
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Declaration file variation target absent; proceed to fallback check routes
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
inspectDtsNodes(node, matchSymbol, intervals) {
|
|
64
|
+
if (!node) return;
|
|
65
|
+
|
|
66
|
+
// Match type mutations inside ambient structural parameters
|
|
67
|
+
if (ts.isExportSpecifier(node) && node.name.text === matchSymbol) {
|
|
68
|
+
intervals.push({ start: node.getStart(), end: node.getEnd() });
|
|
69
|
+
} else if ((ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node)) && node.name && node.name.text === matchSymbol) {
|
|
70
|
+
intervals.push({ start: node.getStart(), end: node.getEnd() });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ts.forEachChild(node, child => this.inspectDtsNodes(child, matchSymbol, intervals));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import resolve from 'enhanced-resolve';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Industrial-Strength Module Resolution Supervisor
|
|
6
|
+
* Integrates path mapping and workspace topologies using enhanced-resolve.
|
|
7
|
+
*/
|
|
8
|
+
export class DependencyResolver {
|
|
9
|
+
constructor(context, pathMapper, workspaceGraph) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
this.pathMapper = pathMapper;
|
|
12
|
+
this.workspaceGraph = workspaceGraph;
|
|
13
|
+
|
|
14
|
+
// Instantiate production-grade enhanced-resolve workspace parameters
|
|
15
|
+
this.nativeResolver = resolve.create.sync({
|
|
16
|
+
conditionNames: ['import', 'module', 'require', 'node', 'types'],
|
|
17
|
+
extensions: ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json', '.vue'],
|
|
18
|
+
mainFields: ['module', 'main', 'types'],
|
|
19
|
+
exportsFields: ['exports'],
|
|
20
|
+
symlinks: true
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolves a raw import string from a source file into an absolute file path.
|
|
26
|
+
* @param {string} containingFile - Absolute path of the file containing the import declaration
|
|
27
|
+
* @param {string} importSpecifier - Raw import target string (e.g., '../components/Button' or '@utils/math')
|
|
28
|
+
* @returns {string|null} Resolved absolute file path location on disk, or null if external/third-party node_module
|
|
29
|
+
*/
|
|
30
|
+
resolveModulePath(containingFile, importSpecifier) {
|
|
31
|
+
// Challenge #16: Ignore built-in Node.js modules (fs, path, etc.)
|
|
32
|
+
if (importSpecifier.startsWith('node:') || [
|
|
33
|
+
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants',
|
|
34
|
+
'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'fs/promises', 'http', 'http2',
|
|
35
|
+
'https', 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks', 'process',
|
|
36
|
+
'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder',
|
|
37
|
+
'timers', 'tls', 'trace_events', 'tty', 'url', 'util', 'v8', 'vm', 'worker_threads', 'zlib'
|
|
38
|
+
].includes(importSpecifier)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const containingDir = path.dirname(containingFile);
|
|
43
|
+
|
|
44
|
+
// Rule A: Intercept and resolve local monorepo workspace cross-links
|
|
45
|
+
if (this.workspaceGraph.isLocalWorkspaceSpecifier(importSpecifier)) {
|
|
46
|
+
const match = this.workspaceGraph.getWorkspacePackageMatch(importSpecifier);
|
|
47
|
+
if (match) {
|
|
48
|
+
if (importSpecifier === match.packageName) {
|
|
49
|
+
// Point directly to the target package's configured index entry file
|
|
50
|
+
return match.entryPoints[0] || null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle deep sub-path monorepo target imports
|
|
54
|
+
const subPathOffset = importSpecifier.slice(match.packageName.length + 1);
|
|
55
|
+
try {
|
|
56
|
+
return this.nativeResolver(match.rootDirectory, `./${subPathOffset}`);
|
|
57
|
+
} catch {
|
|
58
|
+
// Fall back to scanning the package root directly if the sub-path lookup fails
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Rule B: Intercept and expand path mapping aliases (@/*)
|
|
64
|
+
const aliasedCandidates = this.pathMapper.resolveCandidatePaths(importSpecifier);
|
|
65
|
+
if (aliasedCandidates.length > 0) {
|
|
66
|
+
for (const candidate of aliasedCandidates) {
|
|
67
|
+
try {
|
|
68
|
+
const resolvedPath = this.nativeResolver(containingDir, candidate);
|
|
69
|
+
if (this.isAbsoluteInternalPath(resolvedPath)) {
|
|
70
|
+
return resolvedPath;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// Candidate target path absent; try the next fallback pattern entry
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Rule C: Standard file system lookups for standard files or package assets
|
|
79
|
+
try {
|
|
80
|
+
const resolvedPath = this.nativeResolver(containingDir, importSpecifier);
|
|
81
|
+
if (this.isAbsoluteInternalPath(resolvedPath)) {
|
|
82
|
+
return resolvedPath;
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (this.context.verbose) {
|
|
86
|
+
// Output trace logs for unresolvable dependencies during deep code investigations
|
|
87
|
+
console.debug(`[Resolution Trace Skip] Specifier unresolvable from context: ${importSpecifier} inside ${containingFile}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null; // Target is an external node_module dependency or an unresolvable asset
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Ensures our tracking focus stays locked onto internal codebase components, bypassing third-party node_modules.
|
|
96
|
+
*/
|
|
97
|
+
isAbsoluteInternalPath(resolvedPath) {
|
|
98
|
+
if (!resolvedPath) return false;
|
|
99
|
+
const normalized = resolvedPath.replace(/\\/g, '/');
|
|
100
|
+
|
|
101
|
+
// Ignore external node_modules blocks, but preserve local monorepo packages that live inside symlinked node_modules
|
|
102
|
+
if (normalized.includes('/node_modules/')) {
|
|
103
|
+
for (const [name, meta] of this.workspaceGraph.packageManifests.entries()) {
|
|
104
|
+
if (normalized.startsWith(meta.rootDirectory.replace(/\\/g, '/'))) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return path.isAbsolute(resolvedPath);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Challenge #17 Intent Detection. Evaluates if an export serves as a consumer contract distribution node.
|
|
116
|
+
*/
|
|
117
|
+
determineIntentProfile(filePath, declaredExportsManifest) {
|
|
118
|
+
const fileName = path.basename(filePath);
|
|
119
|
+
const isPublicContractFile = /(^index|^public\-api|^entry)\.(ts|js|tsx|jsx)$/i.test(fileName);
|
|
120
|
+
|
|
121
|
+
// If the file is a primary bundle entry point, flag its exports as protected public contracts
|
|
122
|
+
if (isPublicContractFile) {
|
|
123
|
+
for (const [symbolKey, metadata] of declaredExportsManifest.entries()) {
|
|
124
|
+
metadata.isLibraryContract = true;
|
|
125
|
+
}
|
|
126
|
+
return 'LIBRARY_CONSUMPTION_TARGET';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return 'INTERNAL_CODEBASE_ELEMENT';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
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
|
+
const jsonCleanText = rawText.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1');
|
|
29
|
+
const tsconfig = JSON.parse(jsonCleanText);
|
|
30
|
+
|
|
31
|
+
if (!tsconfig.compilerOptions) return;
|
|
32
|
+
|
|
33
|
+
const opts = tsconfig.compilerOptions;
|
|
34
|
+
if (opts.baseUrl) {
|
|
35
|
+
this.baseUrl = opts.baseUrl;
|
|
36
|
+
this.absoluteBaseUrl = path.resolve(this.context.cwd, this.baseUrl);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (opts.paths) {
|
|
40
|
+
for (const [aliasPattern, targetArrays] of Object.entries(opts.paths)) {
|
|
41
|
+
this.registerPatternRule(aliasPattern, targetArrays);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (this.context.verbose) {
|
|
46
|
+
console.warn(`⚠️ [PathMapper Override] Proceeding without custom path configurations. Source: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Registers structural lookup parameters for alias strings.
|
|
53
|
+
*/
|
|
54
|
+
registerPatternRule(pattern, targets) {
|
|
55
|
+
const wildcardIndex = pattern.indexOf('*');
|
|
56
|
+
|
|
57
|
+
if (wildcardIndex === -1) {
|
|
58
|
+
this.mappings.push({
|
|
59
|
+
isExact: true,
|
|
60
|
+
pattern,
|
|
61
|
+
targets: targets.map(t => path.normalize(t))
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
67
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
68
|
+
|
|
69
|
+
this.mappings.push({
|
|
70
|
+
isExact: false,
|
|
71
|
+
prefix,
|
|
72
|
+
suffix,
|
|
73
|
+
targets: targets.map(t => path.normalize(t))
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolves a raw import specifier against mapped path patterns.
|
|
79
|
+
* @param {string} specifier - Raw text from import declaration (e.g., '@ui/button')
|
|
80
|
+
* @returns {Array<string>} Candidates of absolute filesystem paths to try resolving
|
|
81
|
+
*/
|
|
82
|
+
resolveCandidatePaths(specifier) {
|
|
83
|
+
const matchingCandidates = [];
|
|
84
|
+
|
|
85
|
+
for (const rule of this.mappings) {
|
|
86
|
+
if (rule.isExact) {
|
|
87
|
+
if (specifier === rule.pattern) {
|
|
88
|
+
rule.targets.forEach(target => {
|
|
89
|
+
matchingCandidates.push(path.resolve(this.absoluteBaseUrl, target));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// Evaluate wildcard pattern matches
|
|
94
|
+
if (specifier.startsWith(rule.prefix) && specifier.endsWith(rule.suffix)) {
|
|
95
|
+
const extractedWildcardContent = specifier.slice(
|
|
96
|
+
rule.prefix.length,
|
|
97
|
+
specifier.length - rule.suffix.length
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
rule.targets.forEach(targetTemplate => {
|
|
101
|
+
const interpolatedTarget = targetTemplate.replace('*', extractedWildcardContent);
|
|
102
|
+
matchingCandidates.push(path.resolve(this.absoluteBaseUrl, interpolatedTarget));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Fall back to direct lookup relative to the base URL
|
|
109
|
+
if (!specifier.startsWith('.') && !path.isAbsolute(specifier)) {
|
|
110
|
+
matchingCandidates.push(path.resolve(this.absoluteBaseUrl, specifier));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return matchingCandidates;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
// Native sub-directory crawling removed as it's not in fs/promises in older node, but we use readdir anyway.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Monorepo Cross-Linking Topology Manager
|
|
7
|
+
* Maps sub-package structural boundaries across pnpm, Yarn, or npm workspaces.
|
|
8
|
+
*/
|
|
9
|
+
export class WorkspaceGraph {
|
|
10
|
+
constructor(context) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
this.packageManifests = new Map(); // Package Name -> { manifestPath, rootDirectory, entryPoints[] }
|
|
13
|
+
this.workspacePackageNames = new Set();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checks the environment layout to discover and map local workspace packages.
|
|
18
|
+
*/
|
|
19
|
+
async initializeWorkspaceMesh() {
|
|
20
|
+
const rootPackageJsonPath = path.join(this.context.cwd, 'package.json');
|
|
21
|
+
const pnpmWorkspacePath = path.join(this.context.cwd, 'pnpm-workspace.yaml');
|
|
22
|
+
|
|
23
|
+
let workspaceGlobs = [];
|
|
24
|
+
|
|
25
|
+
// Protocol A: Check for pnpm workspace configurations
|
|
26
|
+
try {
|
|
27
|
+
const yaml = await fs.readFile(pnpmWorkspacePath, 'utf8');
|
|
28
|
+
const lines = yaml.split('\n');
|
|
29
|
+
let insidePackagesBlock = false;
|
|
30
|
+
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
if (trimmed.startsWith('packages:')) {
|
|
34
|
+
insidePackagesBlock = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (insidePackagesBlock && trimmed.startsWith('-')) {
|
|
38
|
+
const pattern = trimmed.replace(/^-|['"]/g, '').trim();
|
|
39
|
+
workspaceGlobs.push(pattern);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// pnpm structure absent; check package.json workspace array paths instead
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Protocol B: Check for Yarn/npm workspaces array inside the root package.json
|
|
47
|
+
if (workspaceGlobs.length === 0) {
|
|
48
|
+
try {
|
|
49
|
+
const pkgText = await fs.readFile(rootPackageJsonPath, 'utf8');
|
|
50
|
+
const pkg = JSON.parse(pkgText);
|
|
51
|
+
if (pkg.workspaces) {
|
|
52
|
+
workspaceGlobs = Array.isArray(pkg.workspaces) ? pkg.workspaces : (pkg.workspaces.packages || []);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
return; // Workspace mesh maps skipped for single-package targets
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Crawl target glob configurations to locate workspace packages
|
|
60
|
+
for (const pattern of workspaceGlobs) {
|
|
61
|
+
await this.locatePackagesViaPattern(pattern);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async locatePackagesViaPattern(globPattern) {
|
|
66
|
+
// Normalizes wildcards down into base query directories
|
|
67
|
+
const standardizedPattern = globPattern.replace(/\\/g, '/');
|
|
68
|
+
const baseDir = standardizedPattern.split('/*')[0];
|
|
69
|
+
const absoluteSearchPath = path.resolve(this.context.cwd, baseDir);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const contents = await fs.readdir(absoluteSearchPath, { withFileTypes: true });
|
|
73
|
+
|
|
74
|
+
for (const entity of contents) {
|
|
75
|
+
if (!entity.isDirectory()) continue;
|
|
76
|
+
|
|
77
|
+
const subPackageDir = path.join(absoluteSearchPath, entity.name);
|
|
78
|
+
const manifestFile = path.join(subPackageDir, 'package.json');
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const data = await fs.readFile(manifestFile, 'utf8');
|
|
82
|
+
const pkg = JSON.parse(data);
|
|
83
|
+
|
|
84
|
+
if (pkg.name) {
|
|
85
|
+
const entryPoints = this.calculatePackageExportsEntries(pkg, subPackageDir);
|
|
86
|
+
|
|
87
|
+
this.packageManifests.set(pkg.name, {
|
|
88
|
+
packageName: pkg.name,
|
|
89
|
+
rootDirectory: subPackageDir,
|
|
90
|
+
manifestPath: manifestFile,
|
|
91
|
+
entryPoints
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.workspacePackageNames.add(pkg.name);
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// package.json parsing failed; ignore invalid directory roots
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Unreadable target directories; pass tracking
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Tracks package entry points by evaluating standard fields and main/exports configurations.
|
|
107
|
+
*/
|
|
108
|
+
calculatePackageExportsEntries(pkg, pkgDir) {
|
|
109
|
+
const entries = new Set();
|
|
110
|
+
|
|
111
|
+
// Trace traditional entry fields
|
|
112
|
+
if (pkg.main) entries.add(path.resolve(pkgDir, pkg.main));
|
|
113
|
+
if (pkg.module) entries.add(path.resolve(pkgDir, pkg.module));
|
|
114
|
+
if (pkg.browser) entries.add(path.resolve(pkgDir, pkg.browser));
|
|
115
|
+
if (pkg.types) entries.add(path.resolve(pkgDir, pkg.types));
|
|
116
|
+
|
|
117
|
+
// Handle deep nested conditional exports matrices block parameters
|
|
118
|
+
if (pkg.exports) {
|
|
119
|
+
this.recursivelyUnwindExports(pkg.exports, pkgDir, entries);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Default file index fallback configurations
|
|
123
|
+
if (entries.size === 0) {
|
|
124
|
+
entries.add(path.resolve(pkgDir, 'index.js'));
|
|
125
|
+
entries.add(path.resolve(pkgDir, 'index.ts'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return Array.from(entries);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
recursivelyUnwindExports(exportsValue, pkgDir, collected) {
|
|
132
|
+
if (typeof exportsValue === 'string') {
|
|
133
|
+
if (exportsValue.startsWith('.')) {
|
|
134
|
+
collected.add(path.resolve(pkgDir, exportsValue));
|
|
135
|
+
}
|
|
136
|
+
} else if (typeof exportsValue === 'object' && exportsValue !== null) {
|
|
137
|
+
for (const val of Object.values(exportsValue)) {
|
|
138
|
+
this.recursivelyUnwindExports(val, pkgDir, collected);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Checks if an import specifier matches a package registered in our workspace mesh.
|
|
145
|
+
*/
|
|
146
|
+
isLocalWorkspaceSpecifier(specifier) {
|
|
147
|
+
if (this.workspacePackageNames.has(specifier)) return true;
|
|
148
|
+
|
|
149
|
+
// Catch sub-path imports from monorepos (e.g., '@workspace/shared/utils')
|
|
150
|
+
for (const registeredPkgName of this.workspacePackageNames) {
|
|
151
|
+
if (specifier.startsWith(`${registeredPkgName}/`)) return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Maps a workspace package specifier to its local absolute package root location on disk.
|
|
158
|
+
*/
|
|
159
|
+
getWorkspacePackageMatch(specifier) {
|
|
160
|
+
if (this.packageManifests.has(specifier)) {
|
|
161
|
+
return this.packageManifests.get(specifier);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const [name, metadata] of this.packageManifests.entries()) {
|
|
165
|
+
if (specifier.startsWith(`${name}/`)) {
|
|
166
|
+
return metadata;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ESNext"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"rootDir": "./",
|
|
13
|
+
"allowJs": true,
|
|
14
|
+
"checkJs": false,
|
|
15
|
+
"declaration": true,
|
|
16
|
+
"emitDeclarationOnly": false
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"bin/**/*",
|
|
20
|
+
"src/**/*"
|
|
21
|
+
],
|
|
22
|
+
"exclude": [
|
|
23
|
+
"node_modules",
|
|
24
|
+
".scaffold-cache"
|
|
25
|
+
]
|
|
26
|
+
}
|