@emeryld/rrroutes-export 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,11 +72,13 @@ Settings:
72
72
  Behavior summary:
73
73
 
74
74
  - Resolves commit path from `from..to` (chronological).
75
+ - Builds commit-aware module path candidates (current path + extension variants + rename history).
75
76
  - Filters commits to those touching module tree/relevant config files.
76
77
  - Ensures tip commit is always included.
77
78
  - Creates a temp git worktree per selected commit.
78
79
  - For TypeScript module entries, compiles the commit snapshot to JS and imports emitted output.
79
80
  - For JavaScript module entries, imports directly from worktree.
81
+ - If module entrypoint cannot be resolved for a commit, changelog uses an empty snapshot for that commit (timeline continuity).
80
82
  - Exports snapshots, then computes adjacent diffs:
81
83
  - route events (`route_added`, `route_removed`, `schema_changed`, `cfg_changed`)
82
84
  - source-object events (`source_object_added`, `source_object_removed`, `source_schema_changed`)
@@ -85,6 +87,7 @@ Runtime progress logs:
85
87
 
86
88
  - `rrroutes-export-changelog` now prints step-by-step progress (window resolution, commit filtering, per-commit snapshot progress, diff totals, output writing, cleanup).
87
89
  - Changelog logs include compile lifecycle details: selected tsconfig, cache hit/miss, emitted entry path.
90
+ - Changelog logs include per-commit module resolution and unresolved-entry fallback messages.
88
91
  - Bundle script prints stage logs for snapshot export + changelog export.
89
92
 
90
93
  Troubleshooting historical TS imports:
@@ -92,6 +95,7 @@ Troubleshooting historical TS imports:
92
95
  - Changelog execution no longer relies on direct Node runtime loading of TS sources.
93
96
  - Historical imports like `./x.js` pointing to `x.ts` are handled by snapshot compilation.
94
97
  - If tsconfig auto-discovery fails, pass `--tsconfig`.
98
+ - If entrypoint path moved across history, changelog resolves rename history automatically.
95
99
 
96
100
  ## Recommended end-to-end script
97
101
 
@@ -32,6 +32,7 @@ type CommitSnapshot = {
32
32
  commit: ChangelogCommit;
33
33
  routesByKey: Record<string, RouteSnapshot>;
34
34
  sourceObjectsById: Record<string, SourceObjectSnapshot>;
35
+ unresolvedModule?: boolean;
35
36
  };
36
37
  type DiffOp = 'added' | 'removed' | 'changed';
37
38
  export type SchemaPathDelta = {
@@ -96,6 +97,7 @@ export type FinalizedLeavesChangelogExport = {
96
97
  from: string;
97
98
  to: string;
98
99
  selectedCommitCount: number;
100
+ unresolvedModuleCommits?: string[];
99
101
  };
100
102
  commits: ChangelogCommit[];
101
103
  byRoute: Record<string, RouteChangeEvent[]>;
@@ -123,7 +125,9 @@ declare function resolveCommitWindow(cwd: string, from?: string, to?: string): P
123
125
  to: string;
124
126
  }>;
125
127
  declare function resolveCommitPath(cwd: string, fromCommit: string, toCommit: string): Promise<string[]>;
126
- declare function filterCommits(cwd: string, commits: string[], moduleRel: string, tsconfigRel?: string): Promise<string[]>;
128
+ declare function buildModulePathCandidates(repoRoot: string, toCommit: string, requestedModuleRel: string): Promise<string[]>;
129
+ declare function resolveModulePathForCommit(repoRoot: string, commitSha: string, candidates: readonly string[]): Promise<string | undefined>;
130
+ declare function filterCommits(cwd: string, commits: string[], moduleCandidates: readonly string[], tsconfigRel?: string): Promise<string[]>;
127
131
  declare function buildSourceIdentity(section: ChangelogSchemaSection, value: Record<string, unknown>): SourceIdentity;
128
132
  declare function toRouteSnapshots(payload: FinalizedLeavesExport): Record<string, RouteSnapshot>;
129
133
  declare function toSourceObjectSnapshots(routesByKey: Record<string, RouteSnapshot>): Record<string, SourceObjectSnapshot>;
@@ -160,6 +164,8 @@ export declare const __private: {
160
164
  resolveCommitWindow: typeof resolveCommitWindow;
161
165
  resolveCommitPath: typeof resolveCommitPath;
162
166
  filterCommits: typeof filterCommits;
167
+ buildModulePathCandidates: typeof buildModulePathCandidates;
168
+ resolveModulePathForCommit: typeof resolveModulePathForCommit;
163
169
  resolveSnapshotTsconfig: typeof resolveSnapshotTsconfig;
164
170
  toEmittedModulePath: typeof toEmittedModulePath;
165
171
  buildCompileCacheKey: typeof buildCompileCacheKey;
package/dist/index.cjs CHANGED
@@ -1411,6 +1411,7 @@ var CHANGESET_CFG_FIELDS = [
1411
1411
  "feed"
1412
1412
  ];
1413
1413
  var SCHEMA_SECTIONS = ["params", "query", "body", "output"];
1414
+ var MODULE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs"];
1414
1415
  var SOURCE_KEY_BY_SECTION = {
1415
1416
  params: "paramsSchema",
1416
1417
  query: "querySchema",
@@ -1525,6 +1526,65 @@ async function resolveCommitPath(cwd, fromCommit, toCommit) {
1525
1526
  const commits = pathRaw.split("\n").map((item) => item.trim()).filter(Boolean);
1526
1527
  return [fromCommit, ...commits];
1527
1528
  }
1529
+ function modulePathStem(modulePath) {
1530
+ const normalized = normalizePath(modulePath).replace(/^\/+/, "");
1531
+ for (const ext of MODULE_EXTENSIONS) {
1532
+ if (normalized.endsWith(ext)) {
1533
+ return normalized.slice(0, -ext.length);
1534
+ }
1535
+ }
1536
+ return normalized;
1537
+ }
1538
+ function extensionVariants(pathLike) {
1539
+ const stem = modulePathStem(pathLike);
1540
+ return MODULE_EXTENSIONS.map((ext) => `${stem}${ext}`);
1541
+ }
1542
+ async function buildModulePathCandidates(repoRoot, toCommit, requestedModuleRel) {
1543
+ const requested = normalizePath(requestedModuleRel).replace(/^\/+/, "");
1544
+ const out = [];
1545
+ const seen = /* @__PURE__ */ new Set();
1546
+ const pushCandidate = (value) => {
1547
+ const normalized = normalizePath(value).replace(/^\/+/, "");
1548
+ if (!normalized) return;
1549
+ if (seen.has(normalized)) return;
1550
+ seen.add(normalized);
1551
+ out.push(normalized);
1552
+ };
1553
+ pushCandidate(requested);
1554
+ extensionVariants(requested).forEach(pushCandidate);
1555
+ const history = await runGit(repoRoot, [
1556
+ "log",
1557
+ "--follow",
1558
+ "--name-only",
1559
+ "--format=",
1560
+ toCommit,
1561
+ "--",
1562
+ requested
1563
+ ]);
1564
+ history.split("\n").map((item) => item.trim()).filter(Boolean).forEach((candidate) => {
1565
+ pushCandidate(candidate);
1566
+ extensionVariants(candidate).forEach(pushCandidate);
1567
+ });
1568
+ return out;
1569
+ }
1570
+ async function commitHasPath(repoRoot, commitSha, candidatePath) {
1571
+ try {
1572
+ await execFile("git", ["cat-file", "-e", `${commitSha}:${candidatePath}`], {
1573
+ cwd: repoRoot
1574
+ });
1575
+ return true;
1576
+ } catch {
1577
+ return false;
1578
+ }
1579
+ }
1580
+ async function resolveModulePathForCommit(repoRoot, commitSha, candidates) {
1581
+ for (const candidate of candidates) {
1582
+ if (await commitHasPath(repoRoot, commitSha, candidate)) {
1583
+ return candidate;
1584
+ }
1585
+ }
1586
+ return void 0;
1587
+ }
1528
1588
  function parentDirectories(relativePath) {
1529
1589
  const clean = normalizePath(relativePath).replace(/^\/+/, "");
1530
1590
  const dir = import_node_path4.default.posix.dirname(clean);
@@ -1536,8 +1596,11 @@ function parentDirectories(relativePath) {
1536
1596
  }
1537
1597
  return out;
1538
1598
  }
1539
- function buildRelevantConfigFiles(moduleRel, tsconfigRel) {
1540
- const dirs = new Set(parentDirectories(moduleRel));
1599
+ function buildRelevantConfigFiles(moduleCandidates, tsconfigRel) {
1600
+ const dirs = /* @__PURE__ */ new Set();
1601
+ for (const moduleRel of moduleCandidates) {
1602
+ parentDirectories(moduleRel).forEach((item) => dirs.add(item));
1603
+ }
1541
1604
  if (tsconfigRel) {
1542
1605
  parentDirectories(tsconfigRel).forEach((item) => dirs.add(item));
1543
1606
  dirs.add(import_node_path4.default.posix.dirname(normalizePath(tsconfigRel)));
@@ -1550,7 +1613,7 @@ function buildRelevantConfigFiles(moduleRel, tsconfigRel) {
1550
1613
  }
1551
1614
  return files;
1552
1615
  }
1553
- async function commitTouchedRelevantPaths(cwd, commit, moduleRel, tsconfigRel) {
1616
+ async function commitTouchedRelevantPaths(cwd, commit, moduleCandidates, tsconfigRel) {
1554
1617
  const changed = await runGit(cwd, [
1555
1618
  "show",
1556
1619
  "--pretty=format:",
@@ -1559,27 +1622,35 @@ async function commitTouchedRelevantPaths(cwd, commit, moduleRel, tsconfigRel) {
1559
1622
  commit
1560
1623
  ]);
1561
1624
  const changedFiles = changed.split("\n").map((item) => normalizePath(item.trim()).replace(/^\/+/, "")).filter(Boolean);
1562
- const moduleFile = normalizePath(moduleRel).replace(/^\/+/, "");
1563
- const moduleDir = normalizePath(import_node_path4.default.posix.dirname(moduleFile)).replace(/^\/+/, "");
1625
+ const moduleFiles = Array.from(
1626
+ new Set(moduleCandidates.map((item) => normalizePath(item).replace(/^\/+/, "")))
1627
+ );
1628
+ const moduleDirs = Array.from(
1629
+ new Set(
1630
+ moduleFiles.map((item) => normalizePath(import_node_path4.default.posix.dirname(item)).replace(/^\/+/, "")).filter((item) => item && item !== ".")
1631
+ )
1632
+ );
1564
1633
  const tsconfigFile = tsconfigRel ? normalizePath(tsconfigRel).replace(/^\/+/, "") : void 0;
1565
- const configFiles = buildRelevantConfigFiles(moduleFile, tsconfigFile);
1634
+ const configFiles = buildRelevantConfigFiles(moduleFiles, tsconfigFile);
1566
1635
  return changedFiles.some((file) => {
1567
- if (file === moduleFile) return true;
1568
- if (moduleDir && moduleDir !== "." && (file === moduleDir || file.startsWith(`${moduleDir}/`))) {
1569
- return true;
1636
+ if (moduleFiles.includes(file)) return true;
1637
+ for (const moduleDir of moduleDirs) {
1638
+ if (file === moduleDir || file.startsWith(`${moduleDir}/`)) {
1639
+ return true;
1640
+ }
1570
1641
  }
1571
1642
  if (tsconfigFile && file === tsconfigFile) return true;
1572
1643
  return configFiles.has(file);
1573
1644
  });
1574
1645
  }
1575
- async function filterCommits(cwd, commits, moduleRel, tsconfigRel) {
1646
+ async function filterCommits(cwd, commits, moduleCandidates, tsconfigRel) {
1576
1647
  if (commits.length <= 1) {
1577
1648
  return commits;
1578
1649
  }
1579
1650
  const keep = [commits[0]];
1580
1651
  for (let i = 1; i < commits.length; i += 1) {
1581
1652
  const commit = commits[i];
1582
- if (await commitTouchedRelevantPaths(cwd, commit, moduleRel, tsconfigRel)) {
1653
+ if (await commitTouchedRelevantPaths(cwd, commit, moduleCandidates, tsconfigRel)) {
1583
1654
  keep.push(commit);
1584
1655
  }
1585
1656
  }
@@ -2045,12 +2116,31 @@ ${message}`
2045
2116
  async function loadSnapshotExport(options) {
2046
2117
  return loadChangelogInput(options.entryPath, options.exportName);
2047
2118
  }
2048
- async function createSnapshot(repoRoot, moduleRel, exportName, commit, tempRoot, tsconfigRel, exportOptions, log) {
2119
+ async function createSnapshot(repoRoot, moduleCandidates, exportName, commit, tempRoot, tsconfigRel, exportOptions, log) {
2049
2120
  const worktreeDir = import_node_path4.default.join(tempRoot, `wt-${commit.sha.slice(0, 12)}`);
2050
2121
  await runGit(repoRoot, ["worktree", "add", "--detach", worktreeDir, commit.sha]);
2051
2122
  try {
2052
2123
  await ensureWorktreeDependencies(repoRoot, worktreeDir);
2053
- const modulePath = import_node_path4.default.resolve(worktreeDir, moduleRel);
2124
+ const resolvedModuleRel = await resolveModulePathForCommit(
2125
+ repoRoot,
2126
+ commit.sha,
2127
+ moduleCandidates
2128
+ );
2129
+ if (!resolvedModuleRel) {
2130
+ log?.(
2131
+ `[changelog] unresolved module for commit ${commit.sha.slice(0, 12)}; using empty snapshot`
2132
+ );
2133
+ return {
2134
+ commit,
2135
+ routesByKey: {},
2136
+ sourceObjectsById: {},
2137
+ unresolvedModule: true
2138
+ };
2139
+ }
2140
+ log?.(
2141
+ `[changelog] resolved module for commit ${commit.sha.slice(0, 12)}: ${resolvedModuleRel}`
2142
+ );
2143
+ const modulePath = import_node_path4.default.resolve(worktreeDir, resolvedModuleRel);
2054
2144
  let snapshotRuntimePath = modulePath;
2055
2145
  let resolvedTsconfigPath;
2056
2146
  if (isTypeScriptModule(modulePath)) {
@@ -2065,7 +2155,7 @@ async function createSnapshot(repoRoot, moduleRel, exportName, commit, tempRoot,
2065
2155
  );
2066
2156
  snapshotRuntimePath = await compileSnapshotEntry({
2067
2157
  worktreeDir,
2068
- moduleRel,
2158
+ moduleRel: resolvedModuleRel,
2069
2159
  modulePath,
2070
2160
  tsconfigPath: resolvedTsconfigPath,
2071
2161
  commitSha: commit.sha,
@@ -2322,12 +2412,18 @@ async function exportFinalizedLeavesChangelog(options) {
2322
2412
  log("[changelog] resolving commit window");
2323
2413
  const window = await resolveCommitWindow(repoRoot, options.from, options.to);
2324
2414
  log(`[changelog] window: ${window.from.slice(0, 12)}..${window.to.slice(0, 12)}`);
2415
+ const moduleCandidates = await buildModulePathCandidates(
2416
+ repoRoot,
2417
+ window.to,
2418
+ modulePathRelative
2419
+ );
2420
+ log(`[changelog] module candidates discovered: ${moduleCandidates.length}`);
2325
2421
  const allCommits = await resolveCommitPath(repoRoot, window.from, window.to);
2326
2422
  log(`[changelog] commits in ancestry path: ${allCommits.length}`);
2327
2423
  const selectedCommitShas = await filterCommits(
2328
2424
  repoRoot,
2329
2425
  allCommits,
2330
- modulePathRelative,
2426
+ moduleCandidates,
2331
2427
  tsconfigRelative
2332
2428
  );
2333
2429
  log(`[changelog] commits after path filter: ${selectedCommitShas.length}`);
@@ -2344,6 +2440,7 @@ async function exportFinalizedLeavesChangelog(options) {
2344
2440
  log(`[changelog] temp workspace: ${tempRoot}`);
2345
2441
  try {
2346
2442
  const snapshots = [];
2443
+ const unresolvedModuleCommits = [];
2347
2444
  for (let index = 0; index < commits.length; index += 1) {
2348
2445
  const commit = commits[index];
2349
2446
  log(
@@ -2352,7 +2449,7 @@ async function exportFinalizedLeavesChangelog(options) {
2352
2449
  snapshots.push(
2353
2450
  await createSnapshot(
2354
2451
  repoRoot,
2355
- modulePathRelative,
2452
+ moduleCandidates,
2356
2453
  exportName,
2357
2454
  commit,
2358
2455
  tempRoot,
@@ -2361,6 +2458,9 @@ async function exportFinalizedLeavesChangelog(options) {
2361
2458
  log
2362
2459
  )
2363
2460
  );
2461
+ if (snapshots[snapshots.length - 1]?.unresolvedModule) {
2462
+ unresolvedModuleCommits.push(commit.sha);
2463
+ }
2364
2464
  }
2365
2465
  const routeEvents = [];
2366
2466
  const sourceEvents = [];
@@ -2385,7 +2485,8 @@ async function exportFinalizedLeavesChangelog(options) {
2385
2485
  exportName,
2386
2486
  from: window.from,
2387
2487
  to: window.to,
2388
- selectedCommitCount: commits.length
2488
+ selectedCommitCount: commits.length,
2489
+ unresolvedModuleCommits: unresolvedModuleCommits.length > 0 ? unresolvedModuleCommits : void 0
2389
2490
  },
2390
2491
  commits,
2391
2492
  byRoute: routeRecordByKey(routeEvents),
@@ -2412,6 +2513,8 @@ var __private = {
2412
2513
  resolveCommitWindow,
2413
2514
  resolveCommitPath,
2414
2515
  filterCommits,
2516
+ buildModulePathCandidates,
2517
+ resolveModulePathForCommit,
2415
2518
  resolveSnapshotTsconfig,
2416
2519
  toEmittedModulePath,
2417
2520
  buildCompileCacheKey,