@emeryld/rrroutes-export 1.0.8 → 1.0.9

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/dist/index.mjs CHANGED
@@ -1340,9 +1340,11 @@ async function runExportFinalizedLeavesCli(argv) {
1340
1340
  import fs2 from "fs/promises";
1341
1341
  import os from "os";
1342
1342
  import path4 from "path";
1343
+ import crypto from "crypto";
1343
1344
  import { execFile as execFileCallback, spawn as spawn2 } from "child_process";
1344
1345
  import { promisify } from "util";
1345
1346
  import { pathToFileURL as pathToFileURL2 } from "url";
1347
+ import ts2 from "typescript";
1346
1348
  var execFile = promisify(execFileCallback);
1347
1349
  var CHANGESET_CFG_FIELDS = [
1348
1350
  "summary",
@@ -1825,96 +1827,213 @@ async function pathExists(filePath) {
1825
1827
  return false;
1826
1828
  }
1827
1829
  }
1828
- async function createMissingJsImportShims(worktreeDir) {
1829
- const shimWrites = [];
1830
- const filesToScan = [];
1831
- const skipNames = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build"]);
1830
+ function isTypeScriptModule(modulePath) {
1831
+ const ext = path4.extname(modulePath).toLowerCase();
1832
+ return ext === ".ts" || ext === ".tsx" || ext === ".mts" || ext === ".cts";
1833
+ }
1834
+ function isJavaScriptModule(modulePath) {
1835
+ const ext = path4.extname(modulePath).toLowerCase();
1836
+ return ext === ".js" || ext === ".mjs" || ext === ".cjs";
1837
+ }
1838
+ async function resolveSnapshotTsconfig(worktreeDir, modulePath, tsconfigOverride) {
1839
+ if (tsconfigOverride) {
1840
+ const resolved = path4.resolve(worktreeDir, tsconfigOverride);
1841
+ if (!await pathExists(resolved)) {
1842
+ throw new Error(`tsconfig not found: ${resolved}`);
1843
+ }
1844
+ return resolved;
1845
+ }
1846
+ let current = path4.dirname(modulePath);
1847
+ const normalizedRoot = normalizePath(path4.resolve(worktreeDir));
1848
+ while (true) {
1849
+ const candidate = path4.join(current, "tsconfig.json");
1850
+ if (await pathExists(candidate)) {
1851
+ return candidate;
1852
+ }
1853
+ const parent = path4.dirname(current);
1854
+ if (parent === current) break;
1855
+ if (!normalizePath(parent).startsWith(normalizedRoot)) break;
1856
+ current = parent;
1857
+ }
1858
+ throw new Error(
1859
+ `Unable to resolve tsconfig for TypeScript module: ${modulePath}. Pass --tsconfig to override.`
1860
+ );
1861
+ }
1862
+ function toEmittedModulePath(moduleRel) {
1863
+ const normalized = normalizePath(moduleRel);
1864
+ if (normalized.endsWith(".mts")) return normalized.slice(0, -4) + ".mjs";
1865
+ if (normalized.endsWith(".cts")) return normalized.slice(0, -4) + ".cjs";
1866
+ if (normalized.endsWith(".tsx")) return normalized.slice(0, -4) + ".js";
1867
+ if (normalized.endsWith(".ts")) return normalized.slice(0, -3) + ".js";
1868
+ return normalized;
1869
+ }
1870
+ function buildCompileCacheKey(commitSha, moduleRel, tsconfigPath, tsconfigStat) {
1871
+ return crypto.createHash("sha1").update(commitSha).update("\n").update(normalizePath(moduleRel)).update("\n").update(normalizePath(tsconfigPath)).update("\n").update(String(tsconfigStat.mtimeMs)).update("\n").update(String(tsconfigStat.size)).digest("hex");
1872
+ }
1873
+ async function collectFilesRecursive(rootDir) {
1874
+ const files = [];
1832
1875
  const walk = async (dir) => {
1833
1876
  const entries = await fs2.readdir(dir, { withFileTypes: true });
1834
1877
  for (const entry of entries) {
1878
+ const target = path4.join(dir, entry.name);
1835
1879
  if (entry.isDirectory()) {
1836
- if (skipNames.has(entry.name)) continue;
1837
- await walk(path4.join(dir, entry.name));
1880
+ await walk(target);
1838
1881
  continue;
1839
1882
  }
1840
- if (!entry.isFile()) continue;
1841
- if (!/\\.(ts|tsx|mts)$/.test(entry.name)) continue;
1842
- filesToScan.push(path4.join(dir, entry.name));
1843
- }
1844
- };
1845
- await walk(worktreeDir);
1846
- const importRegexes = [
1847
- /\\bfrom\\s+['\"](\\.[^'\"]+\\.js)['\"]/g,
1848
- /\\bimport\\(\\s*['\"](\\.[^'\"]+\\.js)['\"]\\s*\\)/g,
1849
- /\\brequire\\(\\s*['\"](\\.[^'\"]+\\.js)['\"]\\s*\\)/g
1850
- ];
1851
- const seen = /* @__PURE__ */ new Set();
1852
- for (const filePath of filesToScan) {
1853
- const content = await fs2.readFile(filePath, "utf8");
1854
- for (const regex of importRegexes) {
1855
- regex.lastIndex = 0;
1856
- let match = regex.exec(content);
1857
- while (match) {
1858
- const specifier = match[1];
1859
- const resolvedJs = path4.resolve(path4.dirname(filePath), specifier);
1860
- if (seen.has(resolvedJs)) {
1861
- match = regex.exec(content);
1862
- continue;
1863
- }
1864
- seen.add(resolvedJs);
1865
- if (await pathExists(resolvedJs)) {
1866
- match = regex.exec(content);
1867
- continue;
1868
- }
1869
- const tsCandidates = [
1870
- resolvedJs.replace(/\\.js$/, ".ts"),
1871
- resolvedJs.replace(/\\.js$/, ".mts"),
1872
- resolvedJs.replace(/\\.js$/, ".tsx")
1873
- ];
1874
- let targetTs;
1875
- for (const candidate of tsCandidates) {
1876
- if (await pathExists(candidate)) {
1877
- targetTs = candidate;
1878
- break;
1879
- }
1880
- }
1881
- if (targetTs) {
1882
- const relImport = normalizePath(path4.relative(path4.dirname(resolvedJs), targetTs));
1883
- const importPath = relImport.startsWith(".") ? relImport : `./${relImport}`;
1884
- const shim = `export * from '${importPath}'
1885
- import * as __all from '${importPath}'
1886
- export default ('default' in __all ? __all.default : __all)
1887
- `;
1888
- await fs2.mkdir(path4.dirname(resolvedJs), { recursive: true });
1889
- await fs2.writeFile(resolvedJs, shim, "utf8");
1890
- shimWrites.push(resolvedJs);
1891
- }
1892
- match = regex.exec(content);
1883
+ if (entry.isFile()) {
1884
+ files.push(target);
1893
1885
  }
1894
1886
  }
1887
+ };
1888
+ if (await pathExists(rootDir)) {
1889
+ await walk(rootDir);
1895
1890
  }
1896
- return shimWrites;
1891
+ return files;
1892
+ }
1893
+ function suffixMatchScore(candidate, wanted) {
1894
+ const left = normalizePath(candidate).split("/");
1895
+ const right = normalizePath(wanted).split("/");
1896
+ let score = 0;
1897
+ while (score < left.length && score < right.length) {
1898
+ const a = left[left.length - 1 - score];
1899
+ const b = right[right.length - 1 - score];
1900
+ if (a !== b) break;
1901
+ score += 1;
1902
+ }
1903
+ return score;
1904
+ }
1905
+ async function resolveEmittedEntryPath(emitRoot, moduleRel) {
1906
+ const expected = path4.resolve(emitRoot, toEmittedModulePath(moduleRel));
1907
+ if (await pathExists(expected)) {
1908
+ return expected;
1909
+ }
1910
+ const wantedRel = toEmittedModulePath(moduleRel);
1911
+ const wantedBase = path4.posix.basename(normalizePath(wantedRel));
1912
+ const files = await collectFilesRecursive(emitRoot);
1913
+ const candidates = files.filter(
1914
+ (file) => path4.posix.basename(normalizePath(file)) === wantedBase
1915
+ );
1916
+ if (candidates.length === 0) {
1917
+ return void 0;
1918
+ }
1919
+ candidates.sort(
1920
+ (a, b) => suffixMatchScore(b, wantedRel) - suffixMatchScore(a, wantedRel)
1921
+ );
1922
+ return candidates[0];
1923
+ }
1924
+ async function compileSnapshotEntry(options) {
1925
+ const log = options.log ?? (() => void 0);
1926
+ const tsconfigStat = await fs2.stat(options.tsconfigPath);
1927
+ const cacheKey = buildCompileCacheKey(
1928
+ options.commitSha,
1929
+ options.moduleRel,
1930
+ options.tsconfigPath,
1931
+ { mtimeMs: tsconfigStat.mtimeMs, size: tsconfigStat.size }
1932
+ );
1933
+ const emitRoot = path4.join(options.cacheRoot, cacheKey, "dist");
1934
+ let emittedEntry = path4.resolve(emitRoot, toEmittedModulePath(options.moduleRel));
1935
+ if (!options.noCache && await pathExists(emittedEntry)) {
1936
+ log(`[changelog] compile cache hit: ${options.commitSha.slice(0, 12)}`);
1937
+ return emittedEntry;
1938
+ }
1939
+ log(
1940
+ `[changelog] compile cache ${options.noCache ? "disabled" : "miss"}: ${options.commitSha.slice(0, 12)}`
1941
+ );
1942
+ await fs2.rm(path4.join(options.cacheRoot, cacheKey), { recursive: true, force: true }).catch(
1943
+ () => void 0
1944
+ );
1945
+ await fs2.mkdir(emitRoot, { recursive: true });
1946
+ const readResult = ts2.readConfigFile(options.tsconfigPath, ts2.sys.readFile);
1947
+ if (readResult.error) {
1948
+ throw new Error(ts2.flattenDiagnosticMessageText(readResult.error.messageText, "\n"));
1949
+ }
1950
+ const parsed = ts2.parseJsonConfigFileContent(
1951
+ readResult.config,
1952
+ ts2.sys,
1953
+ path4.dirname(options.tsconfigPath)
1954
+ );
1955
+ const compilerOptions = {
1956
+ ...parsed.options,
1957
+ noEmit: false,
1958
+ declaration: false,
1959
+ declarationMap: false,
1960
+ sourceMap: false,
1961
+ inlineSourceMap: false,
1962
+ composite: false,
1963
+ incremental: false,
1964
+ tsBuildInfoFile: void 0,
1965
+ outDir: emitRoot
1966
+ };
1967
+ const program = ts2.createProgram([options.modulePath], compilerOptions);
1968
+ const emitResult = program.emit();
1969
+ const diagnostics = ts2.getPreEmitDiagnostics(program).concat(emitResult.diagnostics).filter((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error);
1970
+ if (diagnostics.length > 0) {
1971
+ const host = {
1972
+ getCanonicalFileName: (fileName) => fileName,
1973
+ getCurrentDirectory: () => options.worktreeDir,
1974
+ getNewLine: () => "\n"
1975
+ };
1976
+ const message = ts2.formatDiagnosticsWithColorAndContext(diagnostics.slice(0, 20), host);
1977
+ throw new Error(
1978
+ `TypeScript snapshot compile failed for ${options.moduleRel} at ${options.commitSha.slice(0, 12)}:
1979
+ ${message}`
1980
+ );
1981
+ }
1982
+ emittedEntry = await resolveEmittedEntryPath(emitRoot, options.moduleRel) ?? emittedEntry;
1983
+ if (!await pathExists(emittedEntry)) {
1984
+ throw new Error(
1985
+ `Compiled snapshot entry was not emitted: ${emittedEntry}. Check module/tsconfig compatibility.`
1986
+ );
1987
+ }
1988
+ log(`[changelog] compiled snapshot entry: ${emittedEntry}`);
1989
+ return emittedEntry;
1990
+ }
1991
+ async function loadSnapshotExport(options) {
1992
+ return loadChangelogInput(options.entryPath, options.exportName);
1897
1993
  }
1898
1994
  async function createSnapshot(repoRoot, moduleRel, exportName, commit, tempRoot, tsconfigRel, exportOptions, log) {
1899
1995
  const worktreeDir = path4.join(tempRoot, `wt-${commit.sha.slice(0, 12)}`);
1900
1996
  await runGit(repoRoot, ["worktree", "add", "--detach", worktreeDir, commit.sha]);
1901
1997
  try {
1902
1998
  await ensureWorktreeDependencies(repoRoot, worktreeDir);
1903
- const shimmedFiles = await createMissingJsImportShims(worktreeDir);
1904
- if (shimmedFiles.length > 0) {
1905
- log?.(
1906
- `[changelog] created ${shimmedFiles.length} missing .js import shim(s) in ${commit.sha.slice(0, 12)}`
1999
+ const modulePath = path4.resolve(worktreeDir, moduleRel);
2000
+ let snapshotRuntimePath = modulePath;
2001
+ let resolvedTsconfigPath;
2002
+ if (isTypeScriptModule(modulePath)) {
2003
+ resolvedTsconfigPath = await resolveSnapshotTsconfig(
2004
+ worktreeDir,
2005
+ modulePath,
2006
+ tsconfigRel
2007
+ );
2008
+ log?.(`[changelog] using tsconfig: ${resolvedTsconfigPath}`);
2009
+ const compileCacheRoot = path4.resolve(
2010
+ exportOptions?.cacheDir ?? path4.join(os.tmpdir(), "rrroutes-export-changelog-cache")
2011
+ );
2012
+ snapshotRuntimePath = await compileSnapshotEntry({
2013
+ worktreeDir,
2014
+ moduleRel,
2015
+ modulePath,
2016
+ tsconfigPath: resolvedTsconfigPath,
2017
+ commitSha: commit.sha,
2018
+ cacheRoot: compileCacheRoot,
2019
+ noCache: exportOptions?.noCache,
2020
+ log
2021
+ });
2022
+ } else if (!isJavaScriptModule(modulePath)) {
2023
+ throw new Error(
2024
+ `Unsupported module extension for changelog snapshots: ${modulePath}`
1907
2025
  );
1908
2026
  }
1909
- const modulePath = path4.resolve(worktreeDir, moduleRel);
1910
- const tsconfigPath = tsconfigRel ? path4.resolve(worktreeDir, tsconfigRel) : void 0;
1911
- const input = await loadChangelogInput(modulePath, exportName);
2027
+ const input = await loadSnapshotExport({
2028
+ entryPath: snapshotRuntimePath,
2029
+ exportName
2030
+ });
1912
2031
  const payload = await exportFinalizedLeaves(input, {
1913
2032
  includeSource: true,
1914
2033
  sourceModulePath: modulePath,
1915
2034
  sourceExportName: exportName,
1916
- tsconfigPath,
1917
- ...exportOptions
2035
+ tsconfigPath: resolvedTsconfigPath ?? (tsconfigRel ? path4.resolve(worktreeDir, tsconfigRel) : void 0),
2036
+ handlers: exportOptions?.handlers
1918
2037
  });
1919
2038
  const routesByKey = toRouteSnapshots(payload);
1920
2039
  const sourceObjectsById = toSourceObjectSnapshots(routesByKey);
@@ -2239,6 +2358,11 @@ var __private = {
2239
2358
  resolveCommitWindow,
2240
2359
  resolveCommitPath,
2241
2360
  filterCommits,
2361
+ resolveSnapshotTsconfig,
2362
+ toEmittedModulePath,
2363
+ buildCompileCacheKey,
2364
+ compileSnapshotEntry,
2365
+ loadSnapshotExport,
2242
2366
  toRouteSnapshots,
2243
2367
  toSourceObjectSnapshots,
2244
2368
  diffSnapshots,
@@ -2249,6 +2373,7 @@ var __private = {
2249
2373
  import path5 from "path";
2250
2374
  function parseFinalizedLeavesChangelogCliArgs(argv) {
2251
2375
  const args = /* @__PURE__ */ new Map();
2376
+ let noCache = false;
2252
2377
  for (let i = 0; i < argv.length; i += 1) {
2253
2378
  const key = argv[i];
2254
2379
  if (key === "--") continue;
@@ -2256,6 +2381,10 @@ function parseFinalizedLeavesChangelogCliArgs(argv) {
2256
2381
  if (key === "--with-source") {
2257
2382
  continue;
2258
2383
  }
2384
+ if (key === "--no-cache") {
2385
+ noCache = true;
2386
+ continue;
2387
+ }
2259
2388
  const value = argv[i + 1];
2260
2389
  if (!value || value.startsWith("--")) {
2261
2390
  throw new Error(`Missing value for ${key}`);
@@ -2274,7 +2403,9 @@ function parseFinalizedLeavesChangelogCliArgs(argv) {
2274
2403
  htmlFile: args.get("--html") ?? void 0,
2275
2404
  tsconfigPath: args.get("--tsconfig") ?? void 0,
2276
2405
  from: args.get("--from") ?? void 0,
2277
- to: args.get("--to") ?? void 0
2406
+ to: args.get("--to") ?? void 0,
2407
+ cacheDir: args.get("--cache-dir") ?? void 0,
2408
+ noCache
2278
2409
  };
2279
2410
  }
2280
2411
  async function runExportFinalizedLeavesChangelogCli(argv) {
@@ -2292,6 +2423,8 @@ async function runExportFinalizedLeavesChangelogCli(argv) {
2292
2423
  from: args.from,
2293
2424
  to: args.to,
2294
2425
  includeSource: true,
2426
+ cacheDir: args.cacheDir,
2427
+ noCache: args.noCache,
2295
2428
  log
2296
2429
  };
2297
2430
  log("[changelog-cli] starting changelog export");