pkg-scaffold 2.2.0 → 2.4.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.
@@ -0,0 +1,171 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { glob } from 'fs/promises'; // Native sub-directory crawling
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
+ }