@codefast/cli 0.3.12 → 0.3.13-canary.1

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.
Files changed (159) hide show
  1. package/README.md +206 -51
  2. package/dist/{bin.js → bin.mjs} +4 -1
  3. package/dist/commands/arrange.mjs +107 -0
  4. package/dist/commands/mirror.mjs +66 -0
  5. package/dist/commands/tag.mjs +58 -0
  6. package/dist/lib/arrange/application/analyze.mjs +92 -0
  7. package/dist/lib/arrange/application/group-file.mjs +113 -0
  8. package/dist/lib/arrange/application/run-target.mjs +65 -0
  9. package/dist/lib/arrange/domain/ast/ast-helpers.mjs +115 -0
  10. package/dist/lib/arrange/domain/ast/collectors-cn.mjs +61 -0
  11. package/dist/lib/arrange/domain/ast/collectors-jsx.mjs +20 -0
  12. package/dist/lib/arrange/domain/ast/collectors-tv.mjs +170 -0
  13. package/dist/lib/arrange/domain/ast/targets.mjs +106 -0
  14. package/dist/lib/arrange/domain/constants.mjs +169 -0
  15. package/dist/lib/arrange/domain/errors.mjs +19 -0
  16. package/dist/lib/arrange/domain/grouping.mjs +235 -0
  17. package/dist/lib/arrange/domain/imports.mjs +54 -0
  18. package/dist/lib/arrange/domain/tokenizer.mjs +216 -0
  19. package/dist/lib/arrange/index.mjs +15 -0
  20. package/dist/lib/arrange/infra/walk.mjs +33 -0
  21. package/dist/lib/arrange/presentation/formatters.mjs +52 -0
  22. package/dist/lib/arrange/presentation/report.mjs +24 -0
  23. package/dist/lib/config/domain/schema.mjs +25 -0
  24. package/dist/lib/config/index.mjs +3 -0
  25. package/dist/lib/config/infra/loader.mjs +78 -0
  26. package/dist/lib/infra/caught-unknown-message.mjs +20 -0
  27. package/dist/lib/infra/config-reporter.mjs +10 -0
  28. package/dist/lib/infra/node-io.mjs +30 -0
  29. package/dist/lib/infra/workspace/repo-root.mjs +22 -0
  30. package/dist/lib/mirror/application/engine.mjs +248 -0
  31. package/dist/lib/mirror/application/sync.mjs +162 -0
  32. package/dist/lib/mirror/domain/constants.mjs +12 -0
  33. package/dist/lib/mirror/domain/errors.mjs +15 -0
  34. package/dist/lib/mirror/domain/types.mjs +1 -0
  35. package/dist/lib/mirror/index.mjs +2 -0
  36. package/dist/lib/mirror/infra/dirent-list.mjs +14 -0
  37. package/dist/lib/mirror/infra/package-filter.mjs +26 -0
  38. package/dist/lib/mirror/infra/path-normalizer.mjs +7 -0
  39. package/dist/lib/mirror/infra/update-pkg.mjs +105 -0
  40. package/dist/lib/mirror/infra/workspace-packages.mjs +159 -0
  41. package/dist/lib/mirror/presentation/reporter.mjs +111 -0
  42. package/dist/lib/tag/application/engine.mjs +287 -0
  43. package/dist/lib/tag/domain/types.mjs +1 -0
  44. package/dist/lib/tag/index.mjs +4 -0
  45. package/dist/lib/tag/infra/target-resolver.mjs +170 -0
  46. package/dist/lib/tag/presentation/colors.mjs +12 -0
  47. package/dist/lib/tag/presentation/tag-presenter.mjs +63 -0
  48. package/dist/program.mjs +34 -0
  49. package/package.json +11 -11
  50. package/dist/bin.d.ts +0 -3
  51. package/dist/bin.d.ts.map +0 -1
  52. package/dist/commands/arrange.d.ts +0 -3
  53. package/dist/commands/arrange.d.ts.map +0 -1
  54. package/dist/commands/arrange.js +0 -114
  55. package/dist/commands/mirror.d.ts +0 -4
  56. package/dist/commands/mirror.d.ts.map +0 -1
  57. package/dist/commands/mirror.js +0 -63
  58. package/dist/lib/arrange/analyze.d.ts +0 -4
  59. package/dist/lib/arrange/analyze.d.ts.map +0 -1
  60. package/dist/lib/arrange/analyze.js +0 -105
  61. package/dist/lib/arrange/ast/collectors-cn.d.ts +0 -10
  62. package/dist/lib/arrange/ast/collectors-cn.d.ts.map +0 -1
  63. package/dist/lib/arrange/ast/collectors-cn.js +0 -84
  64. package/dist/lib/arrange/ast/collectors-jsx.d.ts +0 -4
  65. package/dist/lib/arrange/ast/collectors-jsx.d.ts.map +0 -1
  66. package/dist/lib/arrange/ast/collectors-jsx.js +0 -18
  67. package/dist/lib/arrange/ast/collectors-tv.d.ts +0 -13
  68. package/dist/lib/arrange/ast/collectors-tv.d.ts.map +0 -1
  69. package/dist/lib/arrange/ast/collectors-tv.js +0 -273
  70. package/dist/lib/arrange/ast/targets.d.ts +0 -8
  71. package/dist/lib/arrange/ast/targets.d.ts.map +0 -1
  72. package/dist/lib/arrange/ast/targets.js +0 -143
  73. package/dist/lib/arrange/ast/utils.d.ts +0 -36
  74. package/dist/lib/arrange/ast/utils.d.ts.map +0 -1
  75. package/dist/lib/arrange/ast/utils.js +0 -138
  76. package/dist/lib/arrange/constants.d.ts +0 -68
  77. package/dist/lib/arrange/constants.d.ts.map +0 -1
  78. package/dist/lib/arrange/constants.js +0 -179
  79. package/dist/lib/arrange/errors.d.ts +0 -14
  80. package/dist/lib/arrange/errors.d.ts.map +0 -1
  81. package/dist/lib/arrange/errors.js +0 -16
  82. package/dist/lib/arrange/formatters.d.ts +0 -16
  83. package/dist/lib/arrange/formatters.d.ts.map +0 -1
  84. package/dist/lib/arrange/formatters.js +0 -57
  85. package/dist/lib/arrange/group-file.d.ts +0 -4
  86. package/dist/lib/arrange/group-file.d.ts.map +0 -1
  87. package/dist/lib/arrange/group-file.js +0 -115
  88. package/dist/lib/arrange/grouping.d.ts +0 -41
  89. package/dist/lib/arrange/grouping.d.ts.map +0 -1
  90. package/dist/lib/arrange/grouping.js +0 -280
  91. package/dist/lib/arrange/imports.d.ts +0 -6
  92. package/dist/lib/arrange/imports.d.ts.map +0 -1
  93. package/dist/lib/arrange/imports.js +0 -74
  94. package/dist/lib/arrange/report.d.ts +0 -4
  95. package/dist/lib/arrange/report.d.ts.map +0 -1
  96. package/dist/lib/arrange/report.js +0 -37
  97. package/dist/lib/arrange/run-target.d.ts +0 -4
  98. package/dist/lib/arrange/run-target.d.ts.map +0 -1
  99. package/dist/lib/arrange/run-target.js +0 -28
  100. package/dist/lib/arrange/tokenizer.d.ts +0 -37
  101. package/dist/lib/arrange/tokenizer.d.ts.map +0 -1
  102. package/dist/lib/arrange/tokenizer.js +0 -390
  103. package/dist/lib/arrange/types.d.ts +0 -104
  104. package/dist/lib/arrange/types.d.ts.map +0 -1
  105. package/dist/lib/arrange/types.js +0 -7
  106. package/dist/lib/arrange/walk.d.ts +0 -4
  107. package/dist/lib/arrange/walk.d.ts.map +0 -1
  108. package/dist/lib/arrange/walk.js +0 -34
  109. package/dist/lib/arrange.d.ts +0 -41
  110. package/dist/lib/arrange.d.ts.map +0 -1
  111. package/dist/lib/arrange.js +0 -38
  112. package/dist/lib/infra/fs-contract.d.ts +0 -28
  113. package/dist/lib/infra/fs-contract.d.ts.map +0 -1
  114. package/dist/lib/infra/node-io.d.ts +0 -4
  115. package/dist/lib/infra/node-io.d.ts.map +0 -1
  116. package/dist/lib/infra/node-io.js +0 -27
  117. package/dist/lib/mirror/config.d.ts +0 -7
  118. package/dist/lib/mirror/config.d.ts.map +0 -1
  119. package/dist/lib/mirror/config.js +0 -146
  120. package/dist/lib/mirror/constants.d.ts +0 -10
  121. package/dist/lib/mirror/constants.d.ts.map +0 -1
  122. package/dist/lib/mirror/constants.js +0 -13
  123. package/dist/lib/mirror/engine.d.ts +0 -12
  124. package/dist/lib/mirror/engine.d.ts.map +0 -1
  125. package/dist/lib/mirror/engine.js +0 -248
  126. package/dist/lib/mirror/errors.d.ts +0 -10
  127. package/dist/lib/mirror/errors.d.ts.map +0 -1
  128. package/dist/lib/mirror/errors.js +0 -12
  129. package/dist/lib/mirror/package-filter.d.ts +0 -8
  130. package/dist/lib/mirror/package-filter.d.ts.map +0 -1
  131. package/dist/lib/mirror/package-filter.js +0 -33
  132. package/dist/lib/mirror/reporter.d.ts +0 -24
  133. package/dist/lib/mirror/reporter.d.ts.map +0 -1
  134. package/dist/lib/mirror/reporter.js +0 -126
  135. package/dist/lib/mirror/sync.d.ts +0 -4
  136. package/dist/lib/mirror/sync.d.ts.map +0 -1
  137. package/dist/lib/mirror/sync.js +0 -135
  138. package/dist/lib/mirror/types.d.ts +0 -81
  139. package/dist/lib/mirror/types.d.ts.map +0 -1
  140. package/dist/lib/mirror/update-pkg.d.ts +0 -13
  141. package/dist/lib/mirror/update-pkg.d.ts.map +0 -1
  142. package/dist/lib/mirror/update-pkg.js +0 -39
  143. package/dist/lib/mirror/workspace-packages.d.ts +0 -26
  144. package/dist/lib/mirror/workspace-packages.d.ts.map +0 -1
  145. package/dist/lib/mirror/workspace-packages.js +0 -160
  146. package/dist/lib/mirror.d.ts +0 -7
  147. package/dist/lib/mirror.d.ts.map +0 -1
  148. package/dist/lib/mirror.js +0 -5
  149. package/dist/lib/repo-root.d.ts +0 -7
  150. package/dist/lib/repo-root.d.ts.map +0 -1
  151. package/dist/lib/repo-root.js +0 -23
  152. package/dist/lib/shared/utils.d.ts +0 -9
  153. package/dist/lib/shared/utils.d.ts.map +0 -1
  154. package/dist/lib/shared/utils.js +0 -12
  155. package/dist/program.d.ts +0 -4
  156. package/dist/program.d.ts.map +0 -1
  157. package/dist/program.js +0 -38
  158. /package/dist/lib/{infra/fs-contract.js → arrange/domain/types.mjs} +0 -0
  159. /package/dist/lib/{mirror/types.js → infra/fs-contract.mjs} +0 -0
@@ -0,0 +1,248 @@
1
+ import { DTS_EXTENSION, PACKAGE_JSON_EXPORT, VALID_JS_EXTENSIONS } from "../domain/constants.mjs";
2
+ import { isDirentList } from "../infra/dirent-list.mjs";
3
+ import { normalizePath } from "../infra/path-normalizer.mjs";
4
+ import path from "node:path";
5
+ //#region src/lib/mirror/application/engine.ts
6
+ function resolvePackageScopedConfig(configMap, pkgMeta) {
7
+ if (!configMap) return;
8
+ return configMap[pkgMeta.packageName];
9
+ }
10
+ async function scanDirectoryFiles(fs, dir, baseDir = dir) {
11
+ try {
12
+ const raw = await fs.readdir(dir, {
13
+ recursive: true,
14
+ withFileTypes: true
15
+ });
16
+ if (!isDirentList(raw)) return [];
17
+ return raw.filter((dirent) => dirent.isFile()).map((dirent) => {
18
+ const fullPath = path.join(dirent.parentPath, dirent.name);
19
+ return normalizePath(path.relative(baseDir, fullPath));
20
+ });
21
+ } catch (caughtError) {
22
+ if (caughtError && typeof caughtError === "object" && "code" in caughtError) {
23
+ if (caughtError.code === "ENOENT" || caughtError.code === "EACCES") return [];
24
+ }
25
+ throw caughtError;
26
+ }
27
+ }
28
+ async function isDirectoryCssOnly(fs, distDir, dirPath) {
29
+ try {
30
+ const raw = await fs.readdir(path.join(distDir, dirPath), { withFileTypes: true });
31
+ if (!isDirentList(raw)) return false;
32
+ if (raw.length === 0) return true;
33
+ return raw.every((dirent) => dirent.isFile() && dirent.name.endsWith(".css"));
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ function groupFilesByModule(files) {
39
+ const modules = /* @__PURE__ */ new Map();
40
+ for (const file of files) {
41
+ let ext;
42
+ let modulePath;
43
+ if (file.endsWith(".d.ts")) {
44
+ ext = DTS_EXTENSION;
45
+ modulePath = file.slice(0, -DTS_EXTENSION.length);
46
+ } else {
47
+ ext = path.extname(file);
48
+ if (!VALID_JS_EXTENSIONS.has(ext)) continue;
49
+ modulePath = file.slice(0, -ext.length);
50
+ }
51
+ if (!path.basename(modulePath)) continue;
52
+ let distModule = modules.get(modulePath);
53
+ if (!distModule) {
54
+ distModule = {
55
+ path: modulePath,
56
+ files: {
57
+ js: null,
58
+ mjs: null,
59
+ cjs: null,
60
+ dts: null
61
+ }
62
+ };
63
+ modules.set(modulePath, distModule);
64
+ }
65
+ if (ext === ".js") distModule.files.js = file;
66
+ else if (ext === ".mjs") distModule.files.mjs = file;
67
+ else if (ext === ".cjs") distModule.files.cjs = file;
68
+ else if (ext === ".d.ts") distModule.files.dts = file;
69
+ }
70
+ return modules;
71
+ }
72
+ function toExportPath(distPath) {
73
+ if (distPath === "index") return ".";
74
+ if (distPath.endsWith("/index")) return `./${distPath.slice(0, -6)}`;
75
+ return `./${distPath}`;
76
+ }
77
+ const GROUP_ORDER = {
78
+ components: 100,
79
+ hooks: 200,
80
+ primitives: 300,
81
+ core: 400,
82
+ loaders: 500,
83
+ utils: 600,
84
+ adapters: 700,
85
+ script: 750,
86
+ css: 900
87
+ };
88
+ function getExportGroup(exportPath, pathTransform) {
89
+ if (exportPath === ".") return [
90
+ ".",
91
+ "",
92
+ 0,
93
+ 0
94
+ ];
95
+ const cleanPath = exportPath.startsWith("./") ? exportPath.slice(2) : exportPath;
96
+ if (cleanPath.endsWith("/*")) {
97
+ const dirName = cleanPath.slice(0, -2);
98
+ return [
99
+ dirName,
100
+ "",
101
+ dirName === "css" ? 900 : 100,
102
+ 0
103
+ ];
104
+ }
105
+ const parts = cleanPath.split("/");
106
+ if (parts.length === 1) {
107
+ const name = parts[0];
108
+ if (name in GROUP_ORDER) return [
109
+ name,
110
+ "",
111
+ GROUP_ORDER[name] ?? 0,
112
+ 0
113
+ ];
114
+ if (pathTransform) return [
115
+ "components",
116
+ name,
117
+ 100,
118
+ 1
119
+ ];
120
+ return [
121
+ "",
122
+ name,
123
+ 800,
124
+ 1
125
+ ];
126
+ }
127
+ const group = parts[0] ?? "";
128
+ return [
129
+ group,
130
+ parts.slice(1).join("/"),
131
+ GROUP_ORDER[group] ?? 700,
132
+ 1
133
+ ];
134
+ }
135
+ function createPathTransform(config, pkgMeta) {
136
+ const pathConfig = resolvePackageScopedConfig(config?.pathTransformations, pkgMeta);
137
+ if (!pathConfig) return null;
138
+ const { removePrefix } = pathConfig;
139
+ if (!removePrefix) return null;
140
+ return (exportPath) => {
141
+ if (!exportPath.startsWith(removePrefix)) return exportPath;
142
+ const result = exportPath.slice(removePrefix.length);
143
+ if (result && result !== "." && !result.startsWith("./")) return `./${result}`;
144
+ return result;
145
+ };
146
+ }
147
+ function getSortTuple(exportPath, pathTransform) {
148
+ const [group, subpath, order, isIndex] = getExportGroup(exportPath, pathTransform);
149
+ return [
150
+ order,
151
+ group,
152
+ isIndex,
153
+ subpath
154
+ ];
155
+ }
156
+ function compareTuples(left, right) {
157
+ if (left[0] !== right[0]) return left[0] - right[0];
158
+ if (left[1] !== right[1]) return left[1] < right[1] ? -1 : 1;
159
+ if (left[2] !== right[2]) return left[2] - right[2];
160
+ if (left[3] !== right[3]) return left[3] < right[3] ? -1 : 1;
161
+ return 0;
162
+ }
163
+ async function generateCssExports(fs, distDir, cssConfig) {
164
+ if (cssConfig === false) return {};
165
+ if (cssConfig === true) cssConfig = { enabled: true };
166
+ if (!cssConfig || cssConfig.enabled === false) return {};
167
+ const cssFiles = (await scanDirectoryFiles(fs, distDir)).filter((relativeDistPath) => relativeDistPath.endsWith(".css"));
168
+ if (!cssFiles.length) return {};
169
+ const cssExports = { ...cssConfig.customExports };
170
+ const cssByDir = /* @__PURE__ */ new Map();
171
+ const rootCss = [];
172
+ for (const file of cssFiles) {
173
+ const dirName = normalizePath(path.dirname(file));
174
+ if (dirName === ".") rootCss.push(file);
175
+ else {
176
+ const arr = cssByDir.get(dirName) || [];
177
+ arr.push(file);
178
+ cssByDir.set(dirName, arr);
179
+ }
180
+ }
181
+ for (const file of rootCss) {
182
+ const exportPath = `./${file}`;
183
+ if (!(exportPath in cssExports)) cssExports[exportPath] = `./dist/${file}`;
184
+ }
185
+ for (const [dirName, dirFiles] of cssByDir.entries()) if (await isDirectoryCssOnly(fs, distDir, dirName) && !cssConfig.forceExportFiles) {
186
+ const wildcardExport = `./${dirName}/*`;
187
+ if (!(wildcardExport in cssExports)) cssExports[wildcardExport] = `./dist/${dirName}/*`;
188
+ } else for (const file of dirFiles) {
189
+ const exportPath = `./${file}`;
190
+ if (!(exportPath in cssExports)) cssExports[exportPath] = `./dist/${file}`;
191
+ }
192
+ return cssExports;
193
+ }
194
+ /**
195
+ * Compute `package.json#exports` from a built `dist/` tree. No logging; no writes.
196
+ */
197
+ async function generateExports(fs, distDir, pathTransform, cssConfig, customExports) {
198
+ const files = await scanDirectoryFiles(fs, distDir);
199
+ if (!files.length) return {
200
+ exports: { [PACKAGE_JSON_EXPORT]: PACKAGE_JSON_EXPORT },
201
+ originalPathBySpecifier: { [PACKAGE_JSON_EXPORT]: PACKAGE_JSON_EXPORT },
202
+ jsCount: 0,
203
+ cssCount: 0
204
+ };
205
+ const modules = groupFilesByModule(files);
206
+ const validModules = Array.from(modules.values()).filter((moduleEntry) => (moduleEntry.files.js || moduleEntry.files.mjs) && moduleEntry.files.dts);
207
+ if (!validModules.length) return {
208
+ exports: { [PACKAGE_JSON_EXPORT]: PACKAGE_JSON_EXPORT },
209
+ originalPathBySpecifier: { [PACKAGE_JSON_EXPORT]: PACKAGE_JSON_EXPORT },
210
+ jsCount: 0,
211
+ cssCount: 0
212
+ };
213
+ const exportsObj = {};
214
+ const originalPathBySpecifier = {};
215
+ for (const distModuleEntry of validModules) {
216
+ const originalExportPath = toExportPath(distModuleEntry.path);
217
+ let exportPath = originalExportPath;
218
+ if (pathTransform) exportPath = pathTransform(exportPath);
219
+ const distPath = `./dist/${distModuleEntry.path}`;
220
+ const entry = { types: `${distPath}.d.ts` };
221
+ if (distModuleEntry.files.mjs) entry.import = `${distPath}.mjs`;
222
+ else if (distModuleEntry.files.js) entry.import = `${distPath}.js`;
223
+ if (distModuleEntry.files.cjs) entry.require = `${distPath}.cjs`;
224
+ exportsObj[exportPath] = entry;
225
+ originalPathBySpecifier[exportPath] = originalExportPath;
226
+ }
227
+ let sortedKeys = Object.keys(exportsObj).sort((pathA, pathB) => compareTuples(getSortTuple(pathA, pathTransform), getSortTuple(pathB, pathTransform)));
228
+ const sortedExports = {};
229
+ for (const exportKey of sortedKeys) sortedExports[exportKey] = exportsObj[exportKey];
230
+ const cssExports = await generateCssExports(fs, distDir, cssConfig ?? { enabled: true });
231
+ Object.assign(sortedExports, cssExports);
232
+ for (const [specifier, mappedPath] of Object.entries(customExports || {})) if (specifier !== "./package.json") sortedExports[specifier] = mappedPath;
233
+ for (const cssSpecifier of Object.keys(cssExports)) if (!(cssSpecifier in originalPathBySpecifier)) originalPathBySpecifier[cssSpecifier] = cssSpecifier;
234
+ for (const customSpecifier of Object.keys(customExports || {})) if (customSpecifier !== "./package.json" && !(customSpecifier in originalPathBySpecifier)) originalPathBySpecifier[customSpecifier] = customSpecifier;
235
+ sortedKeys = Object.keys(sortedExports).filter((exportKey) => exportKey !== PACKAGE_JSON_EXPORT).sort((pathA, pathB) => compareTuples(getSortTuple(pathA, pathTransform), getSortTuple(pathB, pathTransform)));
236
+ const finalExports = {};
237
+ for (const exportKey of sortedKeys) finalExports[exportKey] = sortedExports[exportKey];
238
+ finalExports[PACKAGE_JSON_EXPORT] = PACKAGE_JSON_EXPORT;
239
+ originalPathBySpecifier[PACKAGE_JSON_EXPORT] = PACKAGE_JSON_EXPORT;
240
+ return {
241
+ exports: finalExports,
242
+ originalPathBySpecifier,
243
+ jsCount: Object.keys(exportsObj).length,
244
+ cssCount: Object.keys(cssExports).length
245
+ };
246
+ }
247
+ //#endregion
248
+ export { createPathTransform, generateExports };
@@ -0,0 +1,162 @@
1
+ import { messageFromCaughtUnknown } from "../../infra/caught-unknown-message.mjs";
2
+ import { DIST_DIR, PACKAGE_JSON } from "../domain/constants.mjs";
3
+ import { createPathTransform, generateExports } from "./engine.mjs";
4
+ import { resolvePackageFilterUnderRoot } from "../infra/package-filter.mjs";
5
+ import { configureMirrorColors, logPackageError, logPackageSuccess, logPrunedStaleExport, logSkippedWorkspacePackage, mirrorBanner, mirrorFatalError, mirrorNoPackages, mirrorProcessingMode, mirrorSummary, mirrorSummarySeparator } from "../presentation/reporter.mjs";
6
+ import { resolvePackageDisplayName, writePackageJsonExportsAtomic } from "../infra/update-pkg.mjs";
7
+ import { findWorkspacePackageRelPaths } from "../infra/workspace-packages.mjs";
8
+ import path from "node:path";
9
+ //#region src/lib/mirror/application/sync.ts
10
+ function resolvePackageScopedConfig(configMap, pkgMeta) {
11
+ if (!configMap) return;
12
+ return configMap[pkgMeta.packageName];
13
+ }
14
+ function isPackageSkipped(skipPackagesArray, pkgMeta) {
15
+ if (!skipPackagesArray) return false;
16
+ return skipPackagesArray.includes(pkgMeta.packageName);
17
+ }
18
+ async function syncExportsForWorkspacePackage(fs, logger, rootDir, packagePathStr, index, total, config, verbose, stats) {
19
+ const packageDir = path.resolve(rootDir, packagePathStr);
20
+ const distDir = path.join(packageDir, DIST_DIR);
21
+ const packageJsonPath = path.join(packageDir, PACKAGE_JSON);
22
+ const folderBasename = path.basename(packageDir);
23
+ const pkgStats = {
24
+ name: folderBasename,
25
+ path: packageDir,
26
+ jsModules: 0,
27
+ cssExports: 0,
28
+ customExports: 0,
29
+ totalExports: 0,
30
+ hasTransform: false,
31
+ cssConfigStatus: "",
32
+ skipped: false,
33
+ skipReason: "",
34
+ error: null
35
+ };
36
+ if (!fs.existsSync(packageJsonPath)) {
37
+ pkgStats.skipped = true;
38
+ pkgStats.skipReason = "package.json not found";
39
+ stats.packagesSkipped++;
40
+ logSkippedWorkspacePackage(logger, index, total, folderBasename, pkgStats.skipReason);
41
+ return pkgStats;
42
+ }
43
+ let packageJsonParseError;
44
+ try {
45
+ const pkgContent = await fs.readFile(packageJsonPath, "utf8");
46
+ const raw = JSON.parse(pkgContent);
47
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new SyntaxError("package.json root must be a JSON object");
48
+ pkgStats.name = resolvePackageDisplayName(raw, folderBasename);
49
+ } catch (caughtError) {
50
+ pkgStats.name = folderBasename;
51
+ packageJsonParseError = caughtError;
52
+ }
53
+ const pkgMeta = { packageName: pkgStats.name };
54
+ if (isPackageSkipped(config.skipPackages, pkgMeta)) {
55
+ pkgStats.skipped = true;
56
+ pkgStats.skipReason = "configured to skip";
57
+ stats.packagesSkipped++;
58
+ logSkippedWorkspacePackage(logger, index, total, pkgStats.name, pkgStats.skipReason);
59
+ return pkgStats;
60
+ }
61
+ if (packageJsonParseError !== void 0) {
62
+ if (!fs.existsSync(distDir)) {
63
+ pkgStats.skipped = true;
64
+ pkgStats.skipReason = "dist/ not found";
65
+ stats.packagesSkipped++;
66
+ logSkippedWorkspacePackage(logger, index, total, pkgStats.name, pkgStats.skipReason);
67
+ return pkgStats;
68
+ }
69
+ pkgStats.error = messageFromCaughtUnknown(packageJsonParseError);
70
+ stats.packagesErrored++;
71
+ logPackageError(logger, index, total, pkgStats.name, packageJsonParseError, verbose);
72
+ return pkgStats;
73
+ }
74
+ if (!fs.existsSync(distDir)) {
75
+ pkgStats.skipped = true;
76
+ pkgStats.skipReason = "dist/ not found";
77
+ stats.packagesSkipped++;
78
+ logSkippedWorkspacePackage(logger, index, total, pkgStats.name, pkgStats.skipReason);
79
+ return pkgStats;
80
+ }
81
+ try {
82
+ const pathTransform = createPathTransform(config, pkgMeta);
83
+ pkgStats.hasTransform = !!pathTransform;
84
+ const cssConfig = resolvePackageScopedConfig(config.cssExports, pkgMeta);
85
+ if (cssConfig === false) pkgStats.cssConfigStatus = "disabled";
86
+ else if (cssConfig !== void 0) pkgStats.cssConfigStatus = "configured";
87
+ const customExports = resolvePackageScopedConfig(config.customExports, pkgMeta) || {};
88
+ const generatedExports = await generateExports(fs, distDir, pathTransform, cssConfig, customExports);
89
+ const { prunedKeys } = await writePackageJsonExportsAtomic(fs, packageJsonPath, {
90
+ generatedExports: generatedExports.exports,
91
+ managedExportSpecifiers: Object.keys(generatedExports.exports),
92
+ originalPathBySpecifier: generatedExports.originalPathBySpecifier
93
+ });
94
+ for (const exportSpecifier of prunedKeys) logPrunedStaleExport(logger, exportSpecifier);
95
+ pkgStats.jsModules = generatedExports.jsCount;
96
+ pkgStats.cssExports = generatedExports.cssCount;
97
+ pkgStats.customExports = Object.keys(customExports).length;
98
+ pkgStats.totalExports = Object.keys(generatedExports.exports).length;
99
+ stats.packagesProcessed++;
100
+ stats.totalExports += pkgStats.totalExports;
101
+ stats.totalJsModules += pkgStats.jsModules;
102
+ stats.totalCssExports += pkgStats.cssExports;
103
+ logPackageSuccess(logger, index, total, pkgStats, generatedExports, verbose);
104
+ } catch (caughtError) {
105
+ pkgStats.error = messageFromCaughtUnknown(caughtError);
106
+ stats.packagesErrored++;
107
+ logPackageError(logger, index, total, pkgStats.name, caughtError, verbose);
108
+ }
109
+ return pkgStats;
110
+ }
111
+ /** @returns Process exit code (0 or 1). */
112
+ async function runMirrorSync(opts) {
113
+ const { fs, logger } = opts;
114
+ const config = opts.config ?? {};
115
+ configureMirrorColors(!!opts.noColor);
116
+ mirrorBanner(logger);
117
+ const startTime = performance.now();
118
+ const verbose = !!opts.verbose;
119
+ try {
120
+ const stats = {
121
+ packagesFound: 0,
122
+ packagesProcessed: 0,
123
+ packagesSkipped: 0,
124
+ packagesErrored: 0,
125
+ totalExports: 0,
126
+ totalJsModules: 0,
127
+ totalCssExports: 0,
128
+ packageDetails: []
129
+ };
130
+ let targetPackages = [];
131
+ if (opts.packageFilter) {
132
+ targetPackages = [resolvePackageFilterUnderRoot(opts.rootDir, opts.packageFilter)];
133
+ mirrorProcessingMode(logger, { kind: "single" });
134
+ } else {
135
+ const { relPaths, multiSource } = await findWorkspacePackageRelPaths(opts.rootDir, fs, logger);
136
+ targetPackages = relPaths;
137
+ mirrorProcessingMode(logger, {
138
+ kind: "multi",
139
+ source: multiSource
140
+ });
141
+ }
142
+ stats.packagesFound = targetPackages.length;
143
+ if (targetPackages.length === 0) {
144
+ mirrorNoPackages(logger);
145
+ return 0;
146
+ }
147
+ let nextPackageOrdinal = 1;
148
+ for (const pkgPath of targetPackages) {
149
+ const pkgStats = await syncExportsForWorkspacePackage(fs, logger, opts.rootDir, pkgPath, nextPackageOrdinal++, targetPackages.length, config, verbose, stats);
150
+ stats.packageDetails.push(pkgStats);
151
+ }
152
+ const elapsed = (performance.now() - startTime) / 1e3;
153
+ mirrorSummarySeparator(logger);
154
+ mirrorSummary(logger, stats, elapsed);
155
+ return stats.packagesErrored > 0 ? 1 : 0;
156
+ } catch (caughtError) {
157
+ mirrorFatalError(logger, caughtError);
158
+ return 1;
159
+ }
160
+ }
161
+ //#endregion
162
+ export { runMirrorSync };
@@ -0,0 +1,12 @@
1
+ //#region src/lib/mirror/domain/constants.ts
2
+ const DIST_DIR = "dist";
3
+ const PACKAGE_JSON = "package.json";
4
+ const VALID_JS_EXTENSIONS = new Set([
5
+ ".js",
6
+ ".mjs",
7
+ ".cjs"
8
+ ]);
9
+ const DTS_EXTENSION = ".d.ts";
10
+ const PACKAGE_JSON_EXPORT = "./package.json";
11
+ //#endregion
12
+ export { DIST_DIR, DTS_EXTENSION, PACKAGE_JSON, PACKAGE_JSON_EXPORT, VALID_JS_EXTENSIONS };
@@ -0,0 +1,15 @@
1
+ //#region src/lib/mirror/domain/errors.ts
2
+ const MirrorErrorCode = {
3
+ FATAL: "FATAL",
4
+ PACKAGE_WRITE: "PACKAGE_WRITE"
5
+ };
6
+ var MirrorError = class extends Error {
7
+ code;
8
+ constructor(code, message, options) {
9
+ super(message, options);
10
+ this.name = "MirrorError";
11
+ this.code = code;
12
+ }
13
+ };
14
+ //#endregion
15
+ export { MirrorError, MirrorErrorCode };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { runMirrorSync } from "./application/sync.mjs";
2
+ export { runMirrorSync };
@@ -0,0 +1,14 @@
1
+ //#region src/lib/mirror/infra/dirent-list.ts
2
+ /**
3
+ * Narrow `fs.promises.readdir` overload result to `Dirent[]` when `withFileTypes: true`.
4
+ * Accepts both historical checks (`isFile` / `isDirectory`) in one guard.
5
+ * Returning `true` for an empty array is intentional: callers treat empty lists
6
+ * the same regardless of the element type, so `[]` is safely accepted as `Dirent[]`.
7
+ */
8
+ function isDirentList(x) {
9
+ if (x.length === 0) return true;
10
+ const first = x[0];
11
+ return typeof first === "object" && first !== null && "isFile" in first && "isDirectory" in first;
12
+ }
13
+ //#endregion
14
+ export { isDirentList };
@@ -0,0 +1,26 @@
1
+ import { normalizePath } from "./path-normalizer.mjs";
2
+ import { realpathSync } from "node:fs";
3
+ import path from "node:path";
4
+ //#region src/lib/mirror/infra/package-filter.ts
5
+ function tryRealpath(entryPath) {
6
+ try {
7
+ return realpathSync.native(entryPath);
8
+ } catch {
9
+ return path.resolve(entryPath);
10
+ }
11
+ }
12
+ /**
13
+ * Resolve `packageFilter` to a POSIX-ish path relative to `rootDir`, without using
14
+ * `process.cwd()`. Use for programmatic `runMirrorSync` calls.
15
+ *
16
+ * @throws If the resolved path is not a strict subdirectory of `rootDir`.
17
+ */
18
+ function resolvePackageFilterUnderRoot(rootDir, packageFilter) {
19
+ const rootReal = tryRealpath(path.resolve(rootDir));
20
+ const resolved = path.isAbsolute(packageFilter) ? tryRealpath(path.resolve(packageFilter)) : tryRealpath(path.join(rootReal, packageFilter));
21
+ const normalized = normalizePath(path.relative(rootReal, resolved));
22
+ if (normalized.startsWith("..") || path.isAbsolute(normalized) || normalized === "" || normalized === ".") throw new Error(`Package path must be a subdirectory under monorepo root: ${rootDir}`);
23
+ return normalized;
24
+ }
25
+ //#endregion
26
+ export { resolvePackageFilterUnderRoot };
@@ -0,0 +1,7 @@
1
+ import path from "node:path";
2
+ //#region src/lib/mirror/infra/path-normalizer.ts
3
+ function normalizePath(pathString) {
4
+ return pathString.split(path.sep).join("/").replace(/\\/g, "/");
5
+ }
6
+ //#endregion
7
+ export { normalizePath };
@@ -0,0 +1,105 @@
1
+ import { messageFromCaughtUnknown } from "../../infra/caught-unknown-message.mjs";
2
+ import { PACKAGE_JSON_EXPORT } from "../domain/constants.mjs";
3
+ import { MirrorError, MirrorErrorCode } from "../domain/errors.mjs";
4
+ //#region src/lib/mirror/infra/update-pkg.ts
5
+ function containsDistTarget(value) {
6
+ if (typeof value === "string") return value.startsWith("./dist/");
7
+ if (Array.isArray(value)) return value.some((entryValue) => containsDistTarget(entryValue));
8
+ if (value && typeof value === "object") return Object.values(value).some((entryValue) => containsDistTarget(entryValue));
9
+ return false;
10
+ }
11
+ /**
12
+ * Keep `./package.json` at the very end for aesthetics by default.
13
+ * If a catch-all `./*` exists, move `./package.json` before wildcards so it remains reachable.
14
+ */
15
+ function exportSpecifierRank(exportSpecifier, hasCatchAllWildcard) {
16
+ if (exportSpecifier === ".") return 0;
17
+ if (exportSpecifier === "./package.json") return hasCatchAllWildcard ? 2 : 4;
18
+ if (exportSpecifier.includes("*")) return 3;
19
+ return 1;
20
+ }
21
+ function compareExportSpecifiers(leftSpecifier, rightSpecifier, originalPathBySpecifier, hasCatchAllWildcard) {
22
+ const leftRank = exportSpecifierRank(leftSpecifier, hasCatchAllWildcard);
23
+ const rightRank = exportSpecifierRank(rightSpecifier, hasCatchAllWildcard);
24
+ if (leftRank !== rightRank) return leftRank - rightRank;
25
+ const leftOriginalPath = originalPathBySpecifier[leftSpecifier] ?? leftSpecifier;
26
+ const rightOriginalPath = originalPathBySpecifier[rightSpecifier] ?? rightSpecifier;
27
+ const originalPathComparison = leftOriginalPath.localeCompare(rightOriginalPath);
28
+ if (originalPathComparison !== 0) return originalPathComparison;
29
+ return leftSpecifier.localeCompare(rightSpecifier);
30
+ }
31
+ /** Shared rule: show `package.json#name` when it is a non-empty string; else folder basename. */
32
+ function resolvePackageDisplayName(packageJson, folderBasename) {
33
+ const declaredName = packageJson.name;
34
+ return typeof declaredName === "string" && declaredName.length > 0 ? declaredName : folderBasename;
35
+ }
36
+ async function readPackageJsonDisplayName(fs, packageJsonPath, folderBasename) {
37
+ if (!fs.existsSync(packageJsonPath)) return folderBasename;
38
+ try {
39
+ const raw = await fs.readFile(packageJsonPath, "utf8");
40
+ return resolvePackageDisplayName(JSON.parse(raw), folderBasename);
41
+ } catch {
42
+ return folderBasename;
43
+ }
44
+ }
45
+ /**
46
+ * Writes `exports` into `package.json` via a temp file + rename (atomic on same volume).
47
+ * Preserves a trailing newline after JSON.
48
+ */
49
+ async function writePackageJsonExportsAtomic(fs, packageJsonPath, mergeInput) {
50
+ const raw = await fs.readFile(packageJsonPath, "utf8");
51
+ const packageJson = JSON.parse(raw);
52
+ const existingExports = packageJson.exports;
53
+ const existingExportMap = existingExports && typeof existingExports === "object" && !Array.isArray(existingExports) ? existingExports : {};
54
+ const managedSpecifiers = new Set(mergeInput.managedExportSpecifiers);
55
+ const originalPathBySpecifier = { ...mergeInput.originalPathBySpecifier };
56
+ const mergedExportMap = {};
57
+ const prunedKeys = [];
58
+ for (const [exportSpecifier, exportValue] of Object.entries(existingExportMap)) {
59
+ if (exportSpecifier === "./package.json") {
60
+ mergedExportMap[exportSpecifier] = exportValue;
61
+ if (!(exportSpecifier in originalPathBySpecifier)) originalPathBySpecifier[exportSpecifier] = exportSpecifier;
62
+ continue;
63
+ }
64
+ if (!managedSpecifiers.has(exportSpecifier)) if (containsDistTarget(exportValue)) prunedKeys.push(exportSpecifier);
65
+ else {
66
+ mergedExportMap[exportSpecifier] = exportValue;
67
+ if (!(exportSpecifier in originalPathBySpecifier)) originalPathBySpecifier[exportSpecifier] = exportSpecifier;
68
+ }
69
+ }
70
+ for (const [exportSpecifier, exportValue] of Object.entries(mergeInput.generatedExports)) mergedExportMap[exportSpecifier] = exportValue;
71
+ if (!("./package.json" in mergedExportMap)) mergedExportMap[PACKAGE_JSON_EXPORT] = PACKAGE_JSON_EXPORT;
72
+ originalPathBySpecifier[PACKAGE_JSON_EXPORT] = PACKAGE_JSON_EXPORT;
73
+ const rootExport = mergedExportMap["."];
74
+ if (rootExport && typeof rootExport === "object" && !Array.isArray(rootExport)) {
75
+ const rootEntry = rootExport;
76
+ const requirePath = typeof rootEntry.require === "string" ? rootEntry.require : void 0;
77
+ const importPath = typeof rootEntry.import === "string" ? rootEntry.import : void 0;
78
+ const typesPath = typeof rootEntry.types === "string" ? rootEntry.types : void 0;
79
+ if (requirePath || importPath) packageJson.main = requirePath ?? importPath;
80
+ if (importPath && importPath.endsWith(".mjs")) packageJson.module = importPath;
81
+ if (typesPath) packageJson.types = typesPath;
82
+ }
83
+ const filesField = packageJson.files;
84
+ if (Array.isArray(filesField)) {
85
+ const normalizedFiles = filesField.filter((entry) => typeof entry === "string");
86
+ if (!normalizedFiles.includes("dist")) normalizedFiles.push("dist");
87
+ packageJson.files = normalizedFiles;
88
+ } else packageJson.files = ["dist"];
89
+ const sortedExportMap = {};
90
+ const hasCatchAllWildcard = "./*" in mergedExportMap;
91
+ for (const exportSpecifier of Object.keys(mergedExportMap).sort((leftSpecifier, rightSpecifier) => compareExportSpecifiers(leftSpecifier, rightSpecifier, originalPathBySpecifier, hasCatchAllWildcard))) sortedExportMap[exportSpecifier] = mergedExportMap[exportSpecifier];
92
+ packageJson.exports = sortedExportMap;
93
+ const serialized = JSON.stringify(packageJson, null, 2) + "\n";
94
+ const tmpPath = `${packageJsonPath}.tmp`;
95
+ await fs.writeFile(tmpPath, serialized, "utf8");
96
+ try {
97
+ await fs.rename(tmpPath, packageJsonPath);
98
+ } catch (caughtRenameError) {
99
+ await fs.unlink(tmpPath).catch(() => {});
100
+ throw new MirrorError(MirrorErrorCode.PACKAGE_WRITE, `Failed to atomically update ${packageJsonPath}: ${messageFromCaughtUnknown(caughtRenameError)}`, caughtRenameError instanceof Error ? { cause: caughtRenameError } : void 0);
101
+ }
102
+ return { prunedKeys };
103
+ }
104
+ //#endregion
105
+ export { readPackageJsonDisplayName, resolvePackageDisplayName, writePackageJsonExportsAtomic };