@codefast/cli 0.3.16-canary.2 → 0.4.0-canary.4

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 (41) hide show
  1. package/README.md +254 -86
  2. package/dist/arrange/analyze.mjs +2 -2
  3. package/dist/arrange/command.mjs +51 -27
  4. package/dist/arrange/domain/analyze-service.mjs +2 -2
  5. package/dist/arrange/domain/ast/collectors-cn.mjs +1 -1
  6. package/dist/arrange/domain/ast/collectors-tv.mjs +2 -2
  7. package/dist/arrange/domain/ast/helpers.mjs +2 -2
  8. package/dist/arrange/domain/ast/simplify-targets.mjs +109 -0
  9. package/dist/arrange/domain/ast/targets.mjs +25 -10
  10. package/dist/arrange/domain/grouping-service.mjs +1 -1
  11. package/dist/arrange/domain/grouping.mjs +5 -5
  12. package/dist/arrange/domain/imports.mjs +50 -2
  13. package/dist/arrange/domain/token-classifier.mjs +12 -12
  14. package/dist/arrange/output.mjs +11 -3
  15. package/dist/arrange/process-file.mjs +1 -1
  16. package/dist/arrange/scan-target.mjs +10 -1
  17. package/dist/arrange/simplify-process-file.mjs +35 -0
  18. package/dist/arrange/simplify-sync.mjs +32 -0
  19. package/dist/arrange/suggest.mjs +1 -1
  20. package/dist/arrange/sync.mjs +1 -1
  21. package/dist/arrange/workspace.mjs +1 -1
  22. package/dist/cli.mjs +1 -1
  23. package/dist/core/cli/result-handle.mjs +1 -1
  24. package/dist/core/config/schema.mjs +31 -10
  25. package/dist/core/source-text-edit.mjs +1 -1
  26. package/dist/core/workspace/resolver.mjs +3 -3
  27. package/dist/mirror/cli-result.mjs +2 -1
  28. package/dist/mirror/cli-schema.mjs +2 -1
  29. package/dist/mirror/command.mjs +12 -11
  30. package/dist/mirror/domain/exports.mjs +16 -19
  31. package/dist/mirror/output.mjs +3 -0
  32. package/dist/mirror/supplement-exports.mjs +114 -0
  33. package/dist/mirror/sync-reporter.mjs +5 -2
  34. package/dist/mirror/sync-workspace-package.mjs +35 -29
  35. package/dist/mirror/sync.mjs +2 -1
  36. package/dist/mirror/write-exports.mjs +3 -2
  37. package/dist/tag/command.mjs +6 -6
  38. package/dist/tag/output.mjs +1 -1
  39. package/dist/tag/sync.mjs +1 -1
  40. package/dist/tag/target-runner.mjs +1 -1
  41. package/package.json +10 -10
@@ -47,7 +47,7 @@ function endAfterOptionalCommaFollowingInSource(source, tokenEnd) {
47
47
  * @since 0.3.16-canary.0
48
48
  */
49
49
  function applyEditsDescending(sourceText, edits) {
50
- const sorted = [...edits].sort((editA, editB) => editB.start - editA.start);
50
+ const sorted = [...edits].toSorted((editA, editB) => editB.start - editA.start);
51
51
  let out = sourceText;
52
52
  for (const edit of sorted) out = out.slice(0, edit.start) + edit.replacement + out.slice(edit.end);
53
53
  return out;
@@ -92,13 +92,13 @@ async function readWorkspaceYaml(rootDir, fs) {
92
92
  try {
93
93
  raw = await fs.readFile(workspaceYamlPath, "utf8");
94
94
  } catch (caughtReadError) {
95
- throw new Error(`Failed to read ${wf}: ${messageFrom(caughtReadError)}`);
95
+ throw new Error(`Failed to read ${wf}: ${messageFrom(caughtReadError)}`, { cause: caughtReadError });
96
96
  }
97
97
  let doc;
98
98
  try {
99
99
  doc = parse(raw);
100
100
  } catch (caughtYamlParseError) {
101
- throw new Error(`Failed to parse ${wf}: ${messageFrom(caughtYamlParseError)}`);
101
+ throw new Error(`Failed to parse ${wf}: ${messageFrom(caughtYamlParseError)}`, { cause: caughtYamlParseError });
102
102
  }
103
103
  if (doc === null || doc === void 0) throw new Error(`${wf} must define a mapping at the document root`);
104
104
  return {
@@ -153,7 +153,7 @@ async function listWorkspacePackageDirectories(rootDirectoryPathAbsolute, fs, su
153
153
  if (!suppressGlobPermissionDiagnostics) logger.out(`⚠ Skipping workspace glob "${pattern}" (${messageFrom(caughtGlobError)})`);
154
154
  continue;
155
155
  }
156
- throw new Error(`Invalid workspace glob "${pattern}": ${messageFrom(caughtGlobError)}`);
156
+ throw new Error(`Invalid workspace glob "${pattern}": ${messageFrom(caughtGlobError)}`, { cause: caughtGlobError });
157
157
  }
158
158
  for (const matchedPath of matches) {
159
159
  const posix = toPosix(matchedPath);
@@ -9,10 +9,11 @@ function exitCodeForMirrorSyncResult(stats) {
9
9
  /**
10
10
  * @since 0.3.16-canary.0
11
11
  */
12
- function formatMirrorSyncJsonOutput(stats, elapsedSeconds) {
12
+ function formatMirrorSyncJsonOutput(stats, elapsedSeconds, write = true) {
13
13
  return JSON.stringify({
14
14
  schemaVersion: 1,
15
15
  ok: stats.packagesErrored === 0,
16
+ write,
16
17
  elapsedSeconds,
17
18
  stats
18
19
  });
@@ -6,7 +6,8 @@ import { z } from "zod";
6
6
  const mirrorSyncRunRequestSchema = z.object({
7
7
  rootDir: z.string().min(1),
8
8
  packageFilter: z.string().optional(),
9
- config: z.unknown().optional()
9
+ config: z.unknown().optional(),
10
+ write: z.boolean().optional()
10
11
  });
11
12
  //#endregion
12
13
  export { mirrorSyncRunRequestSchema };
@@ -1,14 +1,14 @@
1
- import { parseWithSchema } from "../core/schema-parse.mjs";
2
1
  import { logger } from "../core/logger.mjs";
2
+ import { readOptionalPositionalArg } from "../core/cli/positional.mjs";
3
3
  import { consumeCliAppError } from "../core/cli/result-handle.mjs";
4
4
  import { nodeFilesystem } from "../core/filesystem/node.mjs";
5
- import { readOptionalPositionalArg } from "../core/cli/positional.mjs";
5
+ import { parseWithSchema } from "../core/schema-parse.mjs";
6
6
  import { globalCliCommanderOptionsSchema } from "../core/cli/global-options.mjs";
7
- import { prepareMirrorSync } from "./prepare.mjs";
8
- import { runMirrorSync } from "./sync.mjs";
9
- import { mirrorSyncRunRequestSchema } from "./cli-schema.mjs";
10
7
  import { exitCodeForMirrorSyncResult, formatMirrorSyncJsonOutput } from "./cli-result.mjs";
8
+ import { mirrorSyncRunRequestSchema } from "./cli-schema.mjs";
11
9
  import { MirrorSyncProgressPresenter } from "./output.mjs";
10
+ import { prepareMirrorSync } from "./prepare.mjs";
11
+ import { runMirrorSync } from "./sync.mjs";
12
12
  import process from "node:process";
13
13
  import { Command } from "commander";
14
14
  //#region src/mirror/command.ts
@@ -16,8 +16,7 @@ import { Command } from "commander";
16
16
  * @since 0.3.16-canary.0
17
17
  */
18
18
  function createMirrorCommand() {
19
- const cmd = new Command("mirror").description("Keep package manifests aligned with what you ship");
20
- cmd.command("sync").description("Write package.json exports from dist/ for workspace packages").argument("[package]", "Optional package path relative to repo root (e.g. packages/ui)").option("-v, --verbose", "Print extra diagnostics", false).option("--json", "Print one JSON summary on stdout (suppresses human progress)", false).action(async (packageArg, opts, command) => {
19
+ return new Command("mirror").description("Write package.json exports from dist/ for workspace packages").argument("[package]", "Optional package path relative to repo root (e.g. packages/ui)").option("--dry-run", "Report what would change without writing package.json", false).option("-v, --verbose", "Print extra diagnostics", false).option("--json", "Print one JSON summary on stdout (suppresses human progress)", false).action(async (packageArg, opts, command) => {
21
20
  const globalOptionsOutcome = parseWithSchema(globalCliCommanderOptionsSchema, command.optsWithGlobals?.() ?? command.opts());
22
21
  if (!consumeCliAppError(globalOptionsOutcome)) return;
23
22
  const prelude = await prepareMirrorSync(nodeFilesystem, {
@@ -26,11 +25,13 @@ function createMirrorCommand() {
26
25
  globals: globalOptionsOutcome.value
27
26
  });
28
27
  if (!consumeCliAppError(prelude)) return;
28
+ const write = !opts.dryRun;
29
29
  const { rootDir, config, packageFilter } = prelude.value;
30
30
  const parsed = parseWithSchema(mirrorSyncRunRequestSchema, {
31
31
  rootDir,
32
32
  config: config.mirror ?? {},
33
- packageFilter
33
+ packageFilter,
34
+ write
34
35
  });
35
36
  if (!consumeCliAppError(parsed)) return;
36
37
  const json = !!opts.json;
@@ -40,7 +41,8 @@ function createMirrorCommand() {
40
41
  const listener = json ? void 0 : progressPresenter;
41
42
  if (!json) progressPresenter.configure({
42
43
  noColor,
43
- verbose
44
+ verbose,
45
+ dryRun: !write
44
46
  });
45
47
  const startTime = performance.now();
46
48
  const outcome = await runMirrorSync(nodeFilesystem, {
@@ -48,10 +50,9 @@ function createMirrorCommand() {
48
50
  listener
49
51
  });
50
52
  if (!consumeCliAppError(outcome)) return;
51
- if (json) logger.out(formatMirrorSyncJsonOutput(outcome.value, (performance.now() - startTime) / 1e3));
53
+ if (json) logger.out(formatMirrorSyncJsonOutput(outcome.value, (performance.now() - startTime) / 1e3, write));
52
54
  process.exitCode = exitCodeForMirrorSyncResult(outcome.value);
53
55
  });
54
- return cmd;
55
56
  }
56
57
  //#endregion
57
58
  export { createMirrorCommand };
@@ -1,10 +1,6 @@
1
1
  import { PACKAGE_JSON_EXPORT, VALID_DTS_EXTENSIONS, VALID_JS_EXTENSIONS } from "./constants.mjs";
2
2
  import * as nodePath from "node:path";
3
3
  //#region src/mirror/domain/exports.ts
4
- function resolvePackageScopedConfig(configMap, pkgMeta) {
5
- if (!configMap) return;
6
- return configMap[pkgMeta.packageName];
7
- }
8
4
  function groupDistFilesByModule(relativeDistFiles) {
9
5
  const distModulesByPath = /* @__PURE__ */ new Map();
10
6
  for (const relativeDistFile of relativeDistFiles) {
@@ -110,14 +106,11 @@ function getExportSortGroup(exportPath, pathTransform) {
110
106
  /**
111
107
  * @since 0.3.16-canary.0
112
108
  */
113
- function createPathTransform(config, pkgMeta) {
114
- const pathConfig = resolvePackageScopedConfig(config?.pathTransformations, pkgMeta);
115
- if (!pathConfig) return null;
116
- const { removePrefix } = pathConfig;
117
- if (!removePrefix) return null;
109
+ function createPathTransform(strip) {
110
+ if (!strip) return null;
118
111
  return (exportPath) => {
119
- if (!exportPath.startsWith(removePrefix)) return exportPath;
120
- const trimmedExportPath = exportPath.slice(removePrefix.length);
112
+ if (!exportPath.startsWith(strip)) return exportPath;
113
+ const trimmedExportPath = exportPath.slice(strip.length);
121
114
  if (trimmedExportPath && trimmedExportPath !== "." && !trimmedExportPath.startsWith("./")) return `./${trimmedExportPath}`;
122
115
  return trimmedExportPath;
123
116
  };
@@ -174,7 +167,7 @@ async function generateCssExports(fileSystemService, distDir, cssConfig) {
174
167
  *
175
168
  * @since 0.3.16-canary.0
176
169
  */
177
- async function generateExports(fileSystemService, distDir, pathTransform, cssConfig, customExports) {
170
+ async function generateExports(fileSystemService, distDir, pathTransform, cssConfig, extraExports, options) {
178
171
  const relativeDistFiles = await fileSystemService.listRelativeFilesRecursively(distDir);
179
172
  if (!relativeDistFiles.length) return {
180
173
  exports: { [PACKAGE_JSON_EXPORT]: PACKAGE_JSON_EXPORT },
@@ -197,23 +190,27 @@ async function generateExports(fileSystemService, distDir, pathTransform, cssCon
197
190
  let exportPath = originalExportPath;
198
191
  if (pathTransform) exportPath = pathTransform(exportPath);
199
192
  const exportEntry = {};
193
+ if (options?.source) if (typeof options.source === "string" && exportPath === ".") exportEntry.source = options.source;
194
+ else exportEntry.source = (options.resolveSourcePath ?? ((p) => `./src/${p}.ts`))(distModuleEntry.path);
200
195
  const declarationFile = distModuleEntry.files.dts;
201
- if (declarationFile) exportEntry.types = `./dist/${declarationFile}`;
202
- if (distModuleEntry.files.mjs) exportEntry.import = `./dist/${distModuleEntry.files.mjs}`;
203
- else if (distModuleEntry.files.js) exportEntry.import = `./dist/${distModuleEntry.files.js}`;
196
+ if (declarationFile && options?.types !== false) exportEntry.types = `./dist/${declarationFile}`;
197
+ if (options?.import !== false) {
198
+ if (distModuleEntry.files.mjs) exportEntry.import = `./dist/${distModuleEntry.files.mjs}`;
199
+ else if (distModuleEntry.files.js) exportEntry.import = `./dist/${distModuleEntry.files.js}`;
200
+ }
204
201
  if (distModuleEntry.files.cjs) exportEntry.require = `./dist/${distModuleEntry.files.cjs}`;
205
202
  moduleExportsBySpecifier[exportPath] = exportEntry;
206
203
  originalPathBySpecifier[exportPath] = originalExportPath;
207
204
  }
208
- let sortedSpecifiers = Object.keys(moduleExportsBySpecifier).sort((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
205
+ let sortedSpecifiers = Object.keys(moduleExportsBySpecifier).toSorted((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
209
206
  const sortedExports = {};
210
207
  for (const exportKey of sortedSpecifiers) sortedExports[exportKey] = moduleExportsBySpecifier[exportKey];
211
208
  const cssExports = await generateCssExports(fileSystemService, distDir, cssConfig ?? { enabled: true });
212
209
  Object.assign(sortedExports, cssExports);
213
- for (const [specifier, mappedPath] of Object.entries(customExports || {})) if (specifier !== "./package.json") sortedExports[specifier] = mappedPath;
210
+ for (const [specifier, mappedPath] of Object.entries(extraExports || {})) if (specifier !== "./package.json") sortedExports[specifier] = mappedPath;
214
211
  for (const cssSpecifier of Object.keys(cssExports)) if (!(cssSpecifier in originalPathBySpecifier)) originalPathBySpecifier[cssSpecifier] = cssSpecifier;
215
- for (const customSpecifier of Object.keys(customExports || {})) if (customSpecifier !== "./package.json" && !(customSpecifier in originalPathBySpecifier)) originalPathBySpecifier[customSpecifier] = customSpecifier;
216
- sortedSpecifiers = Object.keys(sortedExports).filter((exportKey) => exportKey !== PACKAGE_JSON_EXPORT).sort((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
212
+ for (const extraSpecifier of Object.keys(extraExports || {})) if (extraSpecifier !== "./package.json" && !(extraSpecifier in originalPathBySpecifier)) originalPathBySpecifier[extraSpecifier] = extraSpecifier;
213
+ sortedSpecifiers = Object.keys(sortedExports).filter((exportKey) => exportKey !== PACKAGE_JSON_EXPORT).toSorted((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
217
214
  const finalExports = {};
218
215
  for (const exportKey of sortedSpecifiers) finalExports[exportKey] = sortedExports[exportKey];
219
216
  finalExports[PACKAGE_JSON_EXPORT] = PACKAGE_JSON_EXPORT;
@@ -8,12 +8,15 @@ const cliLogger = logger;
8
8
  var MirrorSyncProgressPresenter = class {
9
9
  reporter = new MirrorSyncReporter();
10
10
  verbose = false;
11
+ dryRun = false;
11
12
  configure(options) {
12
13
  this.verbose = options.verbose;
14
+ this.dryRun = options.dryRun;
13
15
  this.reporter.configureMirrorColors(options.noColor);
14
16
  }
15
17
  onBanner() {
16
18
  this.reporter.mirrorBanner(cliLogger);
19
+ if (this.dryRun) this.reporter.mirrorDryRunNotice(cliLogger);
17
20
  }
18
21
  onProcessingMode(mode) {
19
22
  this.reporter.mirrorProcessingMode(cliLogger, mode);
@@ -0,0 +1,114 @@
1
+ import { DIST_DIR } from "./domain/constants.mjs";
2
+ import { writePackageJsonExportsAtomic } from "./write-exports.mjs";
3
+ import path from "node:path";
4
+ //#region src/mirror/supplement-exports.ts
5
+ const DTS_EXTENSIONS = [
6
+ ".d.mts",
7
+ ".d.ts",
8
+ ".d.cts"
9
+ ];
10
+ const JS_EXTENSIONS = [".mjs", ".js"];
11
+ /**
12
+ * Infer the dist module path (e.g. `"index"`, `"components/button"`) from an existing
13
+ * conditional export entry. Prefers `import` / `require` values because their extensions
14
+ * are simpler than `.d.mts` etc.
15
+ */
16
+ function inferModulePath(specifier, entry) {
17
+ for (const key of ["import", "require"]) {
18
+ const value = entry[key];
19
+ if (typeof value === "string" && value.startsWith("./dist/")) {
20
+ const relative = value.slice(7);
21
+ for (const ext of JS_EXTENSIONS) if (relative.endsWith(ext)) return relative.slice(0, -ext.length);
22
+ }
23
+ }
24
+ const typesValue = entry.types;
25
+ if (typeof typesValue === "string" && typesValue.startsWith("./dist/")) {
26
+ const relative = typesValue.slice(7);
27
+ for (const ext of DTS_EXTENSIONS) if (relative.endsWith(ext)) return relative.slice(0, -ext.length);
28
+ }
29
+ if (specifier === ".") return "index";
30
+ if (specifier.startsWith("./")) return specifier.slice(2);
31
+ return specifier;
32
+ }
33
+ function findDtsSpecifier(fs, distDir, modulePath) {
34
+ for (const ext of DTS_EXTENSIONS) if (fs.existsSync(path.join(distDir, `${modulePath}${ext}`))) return `./dist/${modulePath}${ext}`;
35
+ return null;
36
+ }
37
+ function findImportSpecifier(fs, distDir, modulePath) {
38
+ for (const ext of JS_EXTENSIONS) if (fs.existsSync(path.join(distDir, `${modulePath}${ext}`))) return `./dist/${modulePath}${ext}`;
39
+ return null;
40
+ }
41
+ /**
42
+ * Rebuild an export entry in canonical key order, adding any missing conditions.
43
+ * Key order: source → types → import → require → (remaining original keys).
44
+ */
45
+ function buildSupplementedEntry(specifier, existing, modulePath, distDir, fs, options) {
46
+ const result = {};
47
+ const knownKeys = new Set([
48
+ "source",
49
+ "types",
50
+ "import",
51
+ "require"
52
+ ]);
53
+ if ("source" in existing) result.source = existing.source;
54
+ else if (options.source) if (typeof options.source === "string" && specifier === ".") result.source = options.source;
55
+ else result.source = options.resolveSourcePath(modulePath);
56
+ if ("types" in existing) result.types = existing.types;
57
+ else if (options.types) {
58
+ const found = findDtsSpecifier(fs, distDir, modulePath);
59
+ if (found) result.types = found;
60
+ }
61
+ if ("import" in existing) result.import = existing.import;
62
+ else if (options.import) {
63
+ const found = findImportSpecifier(fs, distDir, modulePath);
64
+ if (found) result.import = found;
65
+ }
66
+ if ("require" in existing) result.require = existing.require;
67
+ for (const [key, value] of Object.entries(existing)) if (!knownKeys.has(key) && !(key in result)) result[key] = value;
68
+ return result;
69
+ }
70
+ /**
71
+ * @since 0.3.16-canary.0
72
+ */
73
+ async function supplementExportsInPackageJson(fs, packageJsonPath, packageDir, options, write = true) {
74
+ const raw = await fs.readFile(packageJsonPath, "utf8");
75
+ const existingExports = JSON.parse(raw).exports;
76
+ if (!existingExports || typeof existingExports !== "object" || Array.isArray(existingExports)) return { supplementedSpecifiers: [] };
77
+ const distDir = path.join(packageDir, DIST_DIR);
78
+ const supplementedSpecifiers = [];
79
+ const supplementedExports = {};
80
+ const originalPathBySpecifier = {};
81
+ for (const [specifier, entry] of Object.entries(existingExports)) {
82
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
83
+ supplementedExports[specifier] = entry;
84
+ originalPathBySpecifier[specifier] = specifier;
85
+ continue;
86
+ }
87
+ const entryRecord = entry;
88
+ const supplemented = buildSupplementedEntry(specifier, entryRecord, inferModulePath(specifier, entryRecord), distDir, fs, options);
89
+ if (JSON.stringify(supplemented) !== JSON.stringify(entryRecord)) supplementedSpecifiers.push(specifier);
90
+ supplementedExports[specifier] = supplemented;
91
+ originalPathBySpecifier[specifier] = specifier;
92
+ }
93
+ await writePackageJsonExportsAtomic(fs, packageJsonPath, {
94
+ generatedExports: supplementedExports,
95
+ managedExportSpecifiers: Object.keys(supplementedExports),
96
+ originalPathBySpecifier
97
+ }, write);
98
+ return { supplementedSpecifiers };
99
+ }
100
+ /**
101
+ * Build a `resolveSourcePath` closure that checks the filesystem for `.tsx` before
102
+ * falling back to `.ts`. Mirrors the logic used in normal (non-custom) mode.
103
+ *
104
+ * @since 0.3.16-canary.0
105
+ */
106
+ function buildSourcePathResolver(fs, packageDir) {
107
+ const srcDir = path.join(packageDir, "src");
108
+ return (modulePath) => {
109
+ const tsxPath = path.join(srcDir, `${modulePath}.tsx`);
110
+ return fs.existsSync(tsxPath) ? `./src/${modulePath}.tsx` : `./src/${modulePath}.ts`;
111
+ };
112
+ }
113
+ //#endregion
114
+ export { buildSourcePathResolver, supplementExportsInPackageJson };
@@ -14,7 +14,7 @@ const ANSI = {
14
14
  brightCyan: "\x1B[96m"
15
15
  };
16
16
  /**
17
- * Console implementation: ANSI styling and layout for mirror sync.
17
+ * Console implementation: ANSI styling and layout for the mirror command.
18
18
  *
19
19
  * @since 0.3.16-canary.0
20
20
  */
@@ -27,6 +27,9 @@ var MirrorSyncReporter = class {
27
27
  logger.out(`\n${this.paint("📦 Mirror — package exports", ANSI.bold + ANSI.cyan)}`);
28
28
  logger.out(`${this.paint("═".repeat(60), ANSI.dim)}\n`);
29
29
  }
30
+ mirrorDryRunNotice(logger) {
31
+ logger.out(`${this.paint("Dry run — no files will be written.", ANSI.yellow)}\n`);
32
+ }
30
33
  mirrorProcessingMode(logger, mode) {
31
34
  if (mode.kind === "single") {
32
35
  logger.out(`${this.paint("Processing single package...", ANSI.dim)}\n`);
@@ -65,7 +68,7 @@ var MirrorSyncReporter = class {
65
68
  const breakdown = [];
66
69
  if (generatedDistAssetCounts.jsCount > 0) breakdown.push(this.paint(`${generatedDistAssetCounts.jsCount} modules`, ANSI.green));
67
70
  if (generatedDistAssetCounts.cssCount > 0) breakdown.push(this.paint(`${generatedDistAssetCounts.cssCount} CSS`, ANSI.magenta));
68
- if (pkgStats.customExports > 0) breakdown.push(this.paint(`${pkgStats.customExports} custom`, ANSI.yellow));
71
+ if (pkgStats.extraExports > 0) breakdown.push(this.paint(`${pkgStats.extraExports} custom`, ANSI.yellow));
69
72
  const totalExportsText = this.paint(`${pkgStats.totalExports} exports`, ANSI.brightCyan);
70
73
  if (breakdown.length === 0) logger.out(` ${this.paint("└─", ANSI.dim)} ${totalExportsText}`);
71
74
  else logger.out(` ${this.paint("└─", ANSI.dim)} ${breakdown.join(" + ")} = ${totalExportsText}`);
@@ -1,15 +1,16 @@
1
1
  import { messageFrom } from "../core/errors.mjs";
2
+ import { createMirrorDistFilesystem } from "./dist-filesystem-impl.mjs";
2
3
  import { DIST_DIR, PACKAGE_JSON } from "./domain/constants.mjs";
3
4
  import { createPathTransform, generateExports } from "./domain/exports.mjs";
4
5
  import { resolvePackageDisplayName } from "./domain/package-display-name.mjs";
5
6
  import { writePackageJsonExportsAtomic } from "./write-exports.mjs";
6
- import { createMirrorDistFilesystem } from "./dist-filesystem-impl.mjs";
7
+ import { buildSourcePathResolver, supplementExportsInPackageJson } from "./supplement-exports.mjs";
7
8
  import path from "node:path";
8
9
  //#region src/mirror/sync-workspace-package.ts
9
10
  /**
10
11
  * @since 0.3.16-canary.0
11
12
  */
12
- async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, config) {
13
+ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, config, write = true) {
13
14
  const pathJoin = path.join;
14
15
  const pathResolve = path.resolve;
15
16
  const pathBasename = path.basename;
@@ -23,7 +24,7 @@ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, confi
23
24
  path: packageDir,
24
25
  jsModules: 0,
25
26
  cssExports: 0,
26
- customExports: 0,
27
+ extraExports: 0,
27
28
  totalExports: 0,
28
29
  hasTransform: false,
29
30
  cssConfigStatus: "",
@@ -49,8 +50,8 @@ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, confi
49
50
  pkgStats.name = folderBasename;
50
51
  packageJsonParseError = caughtError;
51
52
  }
52
- const packageMeta = { packageName: pkgStats.name };
53
- if (isPackageSkipped(config.skipPackages, packageMeta)) {
53
+ const pkgConfig = config[pkgStats.name];
54
+ if (pkgConfig === false) {
54
55
  pkgStats.skipped = true;
55
56
  pkgStats.skipReason = "configured to skip";
56
57
  return pkgStats;
@@ -70,34 +71,39 @@ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, confi
70
71
  return pkgStats;
71
72
  }
72
73
  try {
73
- const pathTransform = createPathTransform(config, packageMeta);
74
- pkgStats.hasTransform = !!pathTransform;
75
- const cssConfig = resolvePackageScopedConfig(config.cssExports, packageMeta);
76
- if (cssConfig === false) pkgStats.cssConfigStatus = "disabled";
77
- else if (cssConfig !== void 0) pkgStats.cssConfigStatus = "configured";
78
- const customExports = resolvePackageScopedConfig(config.customExports, packageMeta) || {};
79
- const generatedExports = await generateExports(distFilesystem, distDir, pathTransform, cssConfig, customExports);
80
- const { prunedKeys } = await writePackageJsonExportsAtomic(fs, packageJsonPath, {
81
- generatedExports: generatedExports.exports,
82
- managedExportSpecifiers: Object.keys(generatedExports.exports),
83
- originalPathBySpecifier: generatedExports.originalPathBySpecifier
84
- });
85
- pkgStats.jsModules = generatedExports.jsCount;
86
- pkgStats.cssExports = generatedExports.cssCount;
87
- pkgStats.customExports = Object.keys(customExports).length;
88
- pkgStats.totalExports = Object.keys(generatedExports.exports).length;
89
- pkgStats.prunedExportKeys = prunedKeys;
74
+ const resolveSourcePath = buildSourcePathResolver(fs, packageDir);
75
+ const exportOptions = {
76
+ source: pkgConfig?.source ?? true,
77
+ types: pkgConfig?.types ?? true,
78
+ import: pkgConfig?.import ?? true,
79
+ resolveSourcePath
80
+ };
81
+ if (pkgConfig?.preserve) {
82
+ const { supplementedSpecifiers } = await supplementExportsInPackageJson(fs, packageJsonPath, packageDir, exportOptions, write);
83
+ pkgStats.totalExports = supplementedSpecifiers.length;
84
+ } else {
85
+ const pathTransform = createPathTransform(pkgConfig?.strip);
86
+ pkgStats.hasTransform = !!pathTransform;
87
+ const cssConfig = pkgConfig?.css;
88
+ if (cssConfig === false) pkgStats.cssConfigStatus = "disabled";
89
+ else if (cssConfig !== void 0) pkgStats.cssConfigStatus = "configured";
90
+ const extraExports = pkgConfig?.exports ?? {};
91
+ const generatedExports = await generateExports(distFilesystem, distDir, pathTransform, cssConfig, extraExports, exportOptions);
92
+ const { prunedKeys } = await writePackageJsonExportsAtomic(fs, packageJsonPath, {
93
+ generatedExports: generatedExports.exports,
94
+ managedExportSpecifiers: Object.keys(generatedExports.exports),
95
+ originalPathBySpecifier: generatedExports.originalPathBySpecifier
96
+ }, write);
97
+ pkgStats.jsModules = generatedExports.jsCount;
98
+ pkgStats.cssExports = generatedExports.cssCount;
99
+ pkgStats.extraExports = Object.keys(extraExports).length;
100
+ pkgStats.totalExports = Object.keys(generatedExports.exports).length;
101
+ pkgStats.prunedExportKeys = prunedKeys;
102
+ }
90
103
  } catch (caughtError) {
91
104
  pkgStats.error = messageFrom(caughtError);
92
105
  }
93
106
  return pkgStats;
94
107
  }
95
- function resolvePackageScopedConfig(configMap, packageMeta) {
96
- if (!configMap) return;
97
- return configMap[packageMeta.packageName];
98
- }
99
- function isPackageSkipped(skipPackagesList, packageMeta) {
100
- return !!skipPackagesList?.includes(packageMeta.packageName);
101
- }
102
108
  //#endregion
103
109
  export { syncExportsForWorkspacePackage };
@@ -12,6 +12,7 @@ import path from "node:path";
12
12
  async function runMirrorSync(fs, input) {
13
13
  const config = input.config ?? {};
14
14
  const { listener } = input;
15
+ const write = input.write ?? true;
15
16
  listener?.onBanner();
16
17
  const startTime = performance.now();
17
18
  try {
@@ -47,7 +48,7 @@ async function runMirrorSync(fs, input) {
47
48
  }
48
49
  let ordinal = 1;
49
50
  for (const pkgPath of targetPackages) {
50
- const pkgStats = await syncExportsForWorkspacePackage(fs, input.rootDir, pkgPath, config);
51
+ const pkgStats = await syncExportsForWorkspacePackage(fs, input.rootDir, pkgPath, config, write);
51
52
  stats.packageDetails.push(pkgStats);
52
53
  listener?.onPackageComplete(pkgStats, ordinal, targetPackages.length);
53
54
  ordinal++;
@@ -8,7 +8,7 @@ import { MirrorError, MirrorErrorCode } from "./domain/errors.mjs";
8
8
  *
9
9
  * @since 0.3.16-canary.0
10
10
  */
11
- async function writePackageJsonExportsAtomic(fs, packageJsonPath, mergeInput) {
11
+ async function writePackageJsonExportsAtomic(fs, packageJsonPath, mergeInput, write = true) {
12
12
  function containsDistTarget(value) {
13
13
  if (typeof value === "string") return value.startsWith("./dist/");
14
14
  if (Array.isArray(value)) return value.some((entryValue) => containsDistTarget(entryValue));
@@ -76,7 +76,8 @@ async function writePackageJsonExportsAtomic(fs, packageJsonPath, mergeInput) {
76
76
  } else packageJson.files = ["dist"];
77
77
  const sortedExportMap = {};
78
78
  const hasCatchAllWildcard = "./*" in mergedExportMap;
79
- for (const exportSpecifier of Object.keys(mergedExportMap).sort((leftSpecifier, rightSpecifier) => compareExportSpecifiers(leftSpecifier, rightSpecifier, originalPathBySpecifier, hasCatchAllWildcard))) sortedExportMap[exportSpecifier] = mergedExportMap[exportSpecifier];
79
+ for (const exportSpecifier of Object.keys(mergedExportMap).toSorted((leftSpecifier, rightSpecifier) => compareExportSpecifiers(leftSpecifier, rightSpecifier, originalPathBySpecifier, hasCatchAllWildcard))) sortedExportMap[exportSpecifier] = mergedExportMap[exportSpecifier];
80
+ if (!write) return { prunedKeys };
80
81
  packageJson.exports = sortedExportMap;
81
82
  const serialized = JSON.stringify(packageJson, null, 2) + "\n";
82
83
  const tmpPath = `${packageJsonPath}.tmp`;
@@ -1,13 +1,13 @@
1
- import { parseWithSchema } from "../core/schema-parse.mjs";
2
1
  import { logger } from "../core/logger.mjs";
2
+ import { readOptionalPositionalArg } from "../core/cli/positional.mjs";
3
3
  import { consumeCliAppError } from "../core/cli/result-handle.mjs";
4
4
  import { nodeFilesystem } from "../core/filesystem/node.mjs";
5
- import { readOptionalPositionalArg } from "../core/cli/positional.mjs";
6
- import { prepareTagSync } from "./prepare.mjs";
7
- import { runTagSync } from "./sync.mjs";
8
- import { tagSyncRunRequestSchema } from "./cli-schema.mjs";
5
+ import { parseWithSchema } from "../core/schema-parse.mjs";
9
6
  import { exitCodeForTagSyncResult } from "./cli-result.mjs";
7
+ import { tagSyncRunRequestSchema } from "./cli-schema.mjs";
10
8
  import { TagSyncProgressPresenter, presentTagSyncResult } from "./output.mjs";
9
+ import { prepareTagSync } from "./prepare.mjs";
10
+ import { runTagSync } from "./sync.mjs";
11
11
  import process from "node:process";
12
12
  import { Command } from "commander";
13
13
  //#region src/tag/command.ts
@@ -15,7 +15,7 @@ import { Command } from "commander";
15
15
  * @since 0.3.16-canary.0
16
16
  */
17
17
  function createTagCommand() {
18
- return new Command("tag").description("Add @since <version> JSDoc tags to exported declarations").alias("annotate").argument("[target]", "Directory or file to annotate (default: auto-discover workspace packages)").option("--dry-run", "Show summary without writing files", false).option("--json", "Print one JSON summary on stdout (suppresses human progress)", false).action(async (target, opts) => {
18
+ return new Command("tag").description("Add @since <version> JSDoc tags to exported declarations").argument("[target]", "Directory or file to tag (default: auto-discover workspace packages)").option("--dry-run", "Show summary without writing files", false).option("--json", "Print one JSON summary on stdout (suppresses human progress)", false).action(async (target, opts) => {
19
19
  const prelude = await prepareTagSync(nodeFilesystem, {
20
20
  currentWorkingDirectory: process.cwd(),
21
21
  rawTarget: readOptionalPositionalArg(target)
@@ -1,5 +1,5 @@
1
- import "../core/exit-codes.mjs";
2
1
  import { logger } from "../core/logger.mjs";
2
+ import "../core/exit-codes.mjs";
3
3
  import { exitCodeForTagSyncResult } from "./cli-result.mjs";
4
4
  //#region src/tag/output.ts
5
5
  /**
package/dist/tag/sync.mjs CHANGED
@@ -20,7 +20,7 @@ async function runTagSync(fs, input) {
20
20
  const modifiedFiles = allFileResults.filter((entry) => entry.changed).map((entry) => entry.filePath);
21
21
  const hookError = input.write && modifiedFiles.length > 0 ? await runTagOnAfterWriteHook(tagConfig?.onAfterWrite, modifiedFiles) : null;
22
22
  const versionsSet = extractDistinctVersions(targetExecutionResults);
23
- const distinctVersions = [...versionsSet].sort((left, right) => left.localeCompare(right));
23
+ const distinctVersions = [...versionsSet].toSorted((left, right) => left.localeCompare(right));
24
24
  return ok({
25
25
  mode: input.write ? "applied" : "dry-run",
26
26
  selectedTargets,
@@ -1,6 +1,6 @@
1
1
  import { walkTsxFiles } from "../core/workspace/typescript-walk.mjs";
2
- import { resolveNearestPackageVersion } from "./version-resolver.mjs";
3
2
  import { TagSinceWriter } from "./since-writer.mjs";
3
+ import { resolveNearestPackageVersion } from "./version-resolver.mjs";
4
4
  import path from "node:path";
5
5
  //#region src/tag/target-runner.ts
6
6
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codefast/cli",
3
- "version": "0.3.16-canary.2",
3
+ "version": "0.4.0-canary.4",
4
4
  "description": "Developer CLI for the Codefast monorepo (arrange, mirror, tag)",
5
5
  "keywords": [
6
6
  "cli",
@@ -51,7 +51,7 @@
51
51
  "access": "public"
52
52
  },
53
53
  "dependencies": {
54
- "commander": "^14.0.3",
54
+ "commander": "^15.0.0",
55
55
  "jiti": "^2.7.0",
56
56
  "picomatch": "^4.0.4",
57
57
  "typescript": "^6.0.3",
@@ -59,17 +59,17 @@
59
59
  "zod": "^4.4.3"
60
60
  },
61
61
  "devDependencies": {
62
- "@types/node": "^25.7.0",
62
+ "@types/node": "^25.9.2",
63
63
  "@types/picomatch": "^4.0.3",
64
- "@typescript/native-preview": "7.0.0-dev.20260514.1",
65
- "@vitest/coverage-v8": "^4.1.6",
66
- "tsdown": "^0.22.0",
67
- "vite": "^8.0.13",
68
- "vitest": "^4.1.6",
69
- "@codefast/typescript-config": "0.3.16-canary.2"
64
+ "@typescript/native-preview": "7.0.0-dev.20260604.1",
65
+ "@vitest/coverage-v8": "^4.1.8",
66
+ "tsdown": "^0.22.2",
67
+ "vite": "^8.0.16",
68
+ "vitest": "^4.1.8",
69
+ "@codefast/typescript-config": "0.4.0-canary.4"
70
70
  },
71
71
  "engines": {
72
- "node": ">=22.0.0"
72
+ "node": ">=24.0.0"
73
73
  },
74
74
  "scripts": {
75
75
  "build": "tsdown",