@emeryld/rrroutes-export 1.0.4 → 1.0.6
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 +138 -3
- package/bin/rrroutes-export-changelog.mjs +16 -0
- package/dist/defaultViewerTemplate.d.ts +1 -1
- package/dist/exportFinalizedLeaves.changelog.cli.d.ts +15 -0
- package/dist/exportFinalizedLeaves.changelog.d.ts +146 -0
- package/dist/index.cjs +909 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +904 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -4
- package/tools/finalized-leaves-viewer.html +129 -1
package/dist/index.mjs
CHANGED
|
@@ -296,46 +296,46 @@ function normalizeType(schema) {
|
|
|
296
296
|
return "unknown";
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
|
-
function isNonEmptyPath(
|
|
300
|
-
return typeof
|
|
299
|
+
function isNonEmptyPath(path6) {
|
|
300
|
+
return typeof path6 === "string" && path6.length > 0;
|
|
301
301
|
}
|
|
302
|
-
function setNode(out,
|
|
303
|
-
if (!isNonEmptyPath(
|
|
304
|
-
out[
|
|
302
|
+
function setNode(out, path6, schema, inherited) {
|
|
303
|
+
if (!isNonEmptyPath(path6)) return;
|
|
304
|
+
out[path6] = {
|
|
305
305
|
type: normalizeType(schema),
|
|
306
306
|
nullable: inherited.nullable || Boolean(schema.nullable),
|
|
307
307
|
optional: inherited.optional || Boolean(schema.optional),
|
|
308
308
|
literal: schema.kind === "literal" ? schema.literal : void 0
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
|
-
function flattenInto(out, schema,
|
|
312
|
-
if (!schema || !isNonEmptyPath(
|
|
311
|
+
function flattenInto(out, schema, path6, inherited) {
|
|
312
|
+
if (!schema || !isNonEmptyPath(path6)) return;
|
|
313
313
|
const nextInherited = {
|
|
314
314
|
optional: inherited.optional || Boolean(schema.optional),
|
|
315
315
|
nullable: inherited.nullable || Boolean(schema.nullable)
|
|
316
316
|
};
|
|
317
317
|
if (schema.kind === "union" && Array.isArray(schema.union) && schema.union.length > 0) {
|
|
318
318
|
schema.union.forEach((option, index) => {
|
|
319
|
-
const optionPath = `${
|
|
319
|
+
const optionPath = `${path6}-${index + 1}`;
|
|
320
320
|
flattenInto(out, option, optionPath, inherited);
|
|
321
321
|
});
|
|
322
322
|
return;
|
|
323
323
|
}
|
|
324
|
-
setNode(out,
|
|
324
|
+
setNode(out, path6, schema, inherited);
|
|
325
325
|
if (schema.kind === "object" && schema.properties) {
|
|
326
326
|
for (const [key, child] of Object.entries(schema.properties)) {
|
|
327
|
-
const childPath = `${
|
|
327
|
+
const childPath = `${path6}.${key}`;
|
|
328
328
|
flattenInto(out, child, childPath, nextInherited);
|
|
329
329
|
}
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
332
|
if (schema.kind === "array" && schema.element) {
|
|
333
|
-
flattenInto(out, schema.element, `${
|
|
333
|
+
flattenInto(out, schema.element, `${path6}[]`, nextInherited);
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
|
-
function flattenSerializableSchema(schema,
|
|
336
|
+
function flattenSerializableSchema(schema, path6) {
|
|
337
337
|
const out = {};
|
|
338
|
-
flattenInto(out, schema,
|
|
338
|
+
flattenInto(out, schema, path6, { optional: false, nullable: false });
|
|
339
339
|
return out;
|
|
340
340
|
}
|
|
341
341
|
function isSerializedLeaf(value) {
|
|
@@ -408,7 +408,27 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
|
|
|
408
408
|
const resultsEl = document.getElementById('results')
|
|
409
409
|
const payload = window.__FINALIZED_LEAVES_PAYLOAD
|
|
410
410
|
|
|
411
|
-
if (
|
|
411
|
+
if (payload && payload.kind === 'finalized-leaves-changelog') {
|
|
412
|
+
statusEl.textContent =
|
|
413
|
+
'Loaded changelog payload with ' + (payload.commits?.length || 0) + ' commits.'
|
|
414
|
+
|
|
415
|
+
const wrapSection = (title, value) => {
|
|
416
|
+
const card = document.createElement('details')
|
|
417
|
+
const summary = document.createElement('summary')
|
|
418
|
+
summary.textContent = title
|
|
419
|
+
const pre = document.createElement('pre')
|
|
420
|
+
pre.textContent = JSON.stringify(value, null, 2)
|
|
421
|
+
card.appendChild(summary)
|
|
422
|
+
card.appendChild(pre)
|
|
423
|
+
return card
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
resultsEl.appendChild(wrapSection('Commits', payload.commits || []))
|
|
427
|
+
resultsEl.appendChild(wrapSection('By Route', payload.byRoute || {}))
|
|
428
|
+
resultsEl.appendChild(
|
|
429
|
+
wrapSection('By Source Object', payload.bySourceObject || {}),
|
|
430
|
+
)
|
|
431
|
+
} else if (!payload || !Array.isArray(payload.leaves)) {
|
|
412
432
|
statusEl.textContent = 'No baked payload found in this HTML file.'
|
|
413
433
|
} else {
|
|
414
434
|
statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'
|
|
@@ -1315,22 +1335,892 @@ async function runExportFinalizedLeavesCli(argv) {
|
|
|
1315
1335
|
outFile: path3.resolve(args.outFile)
|
|
1316
1336
|
};
|
|
1317
1337
|
}
|
|
1338
|
+
|
|
1339
|
+
// src/exportFinalizedLeaves.changelog.ts
|
|
1340
|
+
import fs2 from "fs/promises";
|
|
1341
|
+
import os from "os";
|
|
1342
|
+
import path4 from "path";
|
|
1343
|
+
import { execFile as execFileCallback, spawn as spawn2 } from "child_process";
|
|
1344
|
+
import { promisify } from "util";
|
|
1345
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1346
|
+
var execFile = promisify(execFileCallback);
|
|
1347
|
+
var CHANGESET_CFG_FIELDS = [
|
|
1348
|
+
"summary",
|
|
1349
|
+
"description",
|
|
1350
|
+
"tags",
|
|
1351
|
+
"deprecated",
|
|
1352
|
+
"stability",
|
|
1353
|
+
"docsGroup",
|
|
1354
|
+
"docsHidden",
|
|
1355
|
+
"feed"
|
|
1356
|
+
];
|
|
1357
|
+
var SCHEMA_SECTIONS = ["params", "query", "body", "output"];
|
|
1358
|
+
var SOURCE_KEY_BY_SECTION = {
|
|
1359
|
+
params: "paramsSchema",
|
|
1360
|
+
query: "querySchema",
|
|
1361
|
+
body: "bodySchema",
|
|
1362
|
+
output: "outputSchema"
|
|
1363
|
+
};
|
|
1364
|
+
function normalizePath(p) {
|
|
1365
|
+
return p.replace(/\\/g, "/");
|
|
1366
|
+
}
|
|
1367
|
+
function stableStringify(value) {
|
|
1368
|
+
return JSON.stringify(value, (_key, v) => {
|
|
1369
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
1370
|
+
const sorted = Object.entries(v).sort(([a], [b]) => a.localeCompare(b)).reduce((acc, [k, inner]) => {
|
|
1371
|
+
acc[k] = inner;
|
|
1372
|
+
return acc;
|
|
1373
|
+
}, {});
|
|
1374
|
+
return sorted;
|
|
1375
|
+
}
|
|
1376
|
+
return v;
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
function sanitizeCfgValue(value) {
|
|
1380
|
+
if (Array.isArray(value)) {
|
|
1381
|
+
return value.slice().sort((a, b) => String(a).localeCompare(String(b)));
|
|
1382
|
+
}
|
|
1383
|
+
return value;
|
|
1384
|
+
}
|
|
1385
|
+
function toCfgSubset(cfg) {
|
|
1386
|
+
const out = {};
|
|
1387
|
+
for (const field of CHANGESET_CFG_FIELDS) {
|
|
1388
|
+
const value = sanitizeCfgValue(cfg[field]);
|
|
1389
|
+
if (value !== void 0) {
|
|
1390
|
+
out[field] = value;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return out;
|
|
1394
|
+
}
|
|
1395
|
+
function entrySignature(entry) {
|
|
1396
|
+
return stableStringify({
|
|
1397
|
+
type: entry.type,
|
|
1398
|
+
nullable: Boolean(entry.nullable),
|
|
1399
|
+
optional: Boolean(entry.optional),
|
|
1400
|
+
literal: entry.literal
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
function semverToTuple(tag) {
|
|
1404
|
+
const match = tag.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
1405
|
+
if (!match) return null;
|
|
1406
|
+
return {
|
|
1407
|
+
major: Number(match[1]),
|
|
1408
|
+
minor: Number(match[2]),
|
|
1409
|
+
patch: Number(match[3]),
|
|
1410
|
+
prerelease: match[4] ?? ""
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
function compareSemverTags(a, b) {
|
|
1414
|
+
const av = semverToTuple(a);
|
|
1415
|
+
const bv = semverToTuple(b);
|
|
1416
|
+
if (!av && !bv) return 0;
|
|
1417
|
+
if (!av) return -1;
|
|
1418
|
+
if (!bv) return 1;
|
|
1419
|
+
if (av.major !== bv.major) return av.major - bv.major;
|
|
1420
|
+
if (av.minor !== bv.minor) return av.minor - bv.minor;
|
|
1421
|
+
if (av.patch !== bv.patch) return av.patch - bv.patch;
|
|
1422
|
+
if (!av.prerelease && bv.prerelease) return 1;
|
|
1423
|
+
if (av.prerelease && !bv.prerelease) return -1;
|
|
1424
|
+
return av.prerelease.localeCompare(bv.prerelease);
|
|
1425
|
+
}
|
|
1426
|
+
async function runGit(cwd, args) {
|
|
1427
|
+
const { stdout } = await execFile("git", args, { cwd });
|
|
1428
|
+
return stdout.trim();
|
|
1429
|
+
}
|
|
1430
|
+
async function resolveCommit(cwd, rev) {
|
|
1431
|
+
return runGit(cwd, ["rev-parse", "--verify", `${rev}^{commit}`]);
|
|
1432
|
+
}
|
|
1433
|
+
async function findLastReachableSemverTag(cwd, to) {
|
|
1434
|
+
const tagsRaw = await runGit(cwd, ["tag", "--merged", to]);
|
|
1435
|
+
const tags = tagsRaw.split("\n").map((item) => item.trim()).filter(Boolean).filter((tag) => semverToTuple(tag) !== null);
|
|
1436
|
+
if (tags.length === 0) {
|
|
1437
|
+
return void 0;
|
|
1438
|
+
}
|
|
1439
|
+
tags.sort(compareSemverTags);
|
|
1440
|
+
return tags[tags.length - 1];
|
|
1441
|
+
}
|
|
1442
|
+
async function findDefaultFromCommit(cwd, toCommit) {
|
|
1443
|
+
const tag = await findLastReachableSemverTag(cwd, toCommit);
|
|
1444
|
+
if (tag) {
|
|
1445
|
+
return resolveCommit(cwd, tag);
|
|
1446
|
+
}
|
|
1447
|
+
const root = await runGit(cwd, ["rev-list", "--max-parents=0", toCommit]);
|
|
1448
|
+
const rootCommit = root.split("\n").map((item) => item.trim()).filter(Boolean)[0];
|
|
1449
|
+
if (!rootCommit) {
|
|
1450
|
+
throw new Error("Failed to resolve a baseline commit for changelog generation.");
|
|
1451
|
+
}
|
|
1452
|
+
return rootCommit;
|
|
1453
|
+
}
|
|
1454
|
+
async function resolveCommitWindow(cwd, from, to) {
|
|
1455
|
+
const toCommit = await resolveCommit(cwd, to ?? "HEAD");
|
|
1456
|
+
const fromCommit = from ? await resolveCommit(cwd, from) : await findDefaultFromCommit(cwd, toCommit);
|
|
1457
|
+
return { from: fromCommit, to: toCommit };
|
|
1458
|
+
}
|
|
1459
|
+
async function resolveCommitPath(cwd, fromCommit, toCommit) {
|
|
1460
|
+
if (fromCommit === toCommit) {
|
|
1461
|
+
return [toCommit];
|
|
1462
|
+
}
|
|
1463
|
+
const pathRaw = await runGit(cwd, [
|
|
1464
|
+
"rev-list",
|
|
1465
|
+
"--reverse",
|
|
1466
|
+
"--ancestry-path",
|
|
1467
|
+
`${fromCommit}..${toCommit}`
|
|
1468
|
+
]);
|
|
1469
|
+
const commits = pathRaw.split("\n").map((item) => item.trim()).filter(Boolean);
|
|
1470
|
+
return [fromCommit, ...commits];
|
|
1471
|
+
}
|
|
1472
|
+
function parentDirectories(relativePath) {
|
|
1473
|
+
const clean = normalizePath(relativePath).replace(/^\/+/, "");
|
|
1474
|
+
const dir = path4.posix.dirname(clean);
|
|
1475
|
+
if (!dir || dir === ".") return [];
|
|
1476
|
+
const parts = dir.split("/");
|
|
1477
|
+
const out = [];
|
|
1478
|
+
for (let i = parts.length; i > 0; i -= 1) {
|
|
1479
|
+
out.push(parts.slice(0, i).join("/"));
|
|
1480
|
+
}
|
|
1481
|
+
return out;
|
|
1482
|
+
}
|
|
1483
|
+
function buildRelevantConfigFiles(moduleRel, tsconfigRel) {
|
|
1484
|
+
const dirs = new Set(parentDirectories(moduleRel));
|
|
1485
|
+
if (tsconfigRel) {
|
|
1486
|
+
parentDirectories(tsconfigRel).forEach((item) => dirs.add(item));
|
|
1487
|
+
dirs.add(path4.posix.dirname(normalizePath(tsconfigRel)));
|
|
1488
|
+
}
|
|
1489
|
+
const files = /* @__PURE__ */ new Set(["pnpm-workspace.yaml"]);
|
|
1490
|
+
for (const dir of dirs) {
|
|
1491
|
+
if (!dir || dir === ".") continue;
|
|
1492
|
+
files.add(`${dir}/package.json`);
|
|
1493
|
+
files.add(`${dir}/tsconfig.json`);
|
|
1494
|
+
}
|
|
1495
|
+
return files;
|
|
1496
|
+
}
|
|
1497
|
+
async function commitTouchedRelevantPaths(cwd, commit, moduleRel, tsconfigRel) {
|
|
1498
|
+
const changed = await runGit(cwd, [
|
|
1499
|
+
"show",
|
|
1500
|
+
"--pretty=format:",
|
|
1501
|
+
"--name-only",
|
|
1502
|
+
"--diff-filter=ACMRTUXB",
|
|
1503
|
+
commit
|
|
1504
|
+
]);
|
|
1505
|
+
const changedFiles = changed.split("\n").map((item) => normalizePath(item.trim()).replace(/^\/+/, "")).filter(Boolean);
|
|
1506
|
+
const moduleFile = normalizePath(moduleRel).replace(/^\/+/, "");
|
|
1507
|
+
const moduleDir = normalizePath(path4.posix.dirname(moduleFile)).replace(/^\/+/, "");
|
|
1508
|
+
const tsconfigFile = tsconfigRel ? normalizePath(tsconfigRel).replace(/^\/+/, "") : void 0;
|
|
1509
|
+
const configFiles = buildRelevantConfigFiles(moduleFile, tsconfigFile);
|
|
1510
|
+
return changedFiles.some((file) => {
|
|
1511
|
+
if (file === moduleFile) return true;
|
|
1512
|
+
if (moduleDir && moduleDir !== "." && (file === moduleDir || file.startsWith(`${moduleDir}/`))) {
|
|
1513
|
+
return true;
|
|
1514
|
+
}
|
|
1515
|
+
if (tsconfigFile && file === tsconfigFile) return true;
|
|
1516
|
+
return configFiles.has(file);
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
async function filterCommits(cwd, commits, moduleRel, tsconfigRel) {
|
|
1520
|
+
if (commits.length <= 1) {
|
|
1521
|
+
return commits;
|
|
1522
|
+
}
|
|
1523
|
+
const keep = [commits[0]];
|
|
1524
|
+
for (let i = 1; i < commits.length; i += 1) {
|
|
1525
|
+
const commit = commits[i];
|
|
1526
|
+
if (await commitTouchedRelevantPaths(cwd, commit, moduleRel, tsconfigRel)) {
|
|
1527
|
+
keep.push(commit);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
const tip = commits[commits.length - 1];
|
|
1531
|
+
if (!keep.includes(tip)) {
|
|
1532
|
+
keep.push(tip);
|
|
1533
|
+
}
|
|
1534
|
+
return keep;
|
|
1535
|
+
}
|
|
1536
|
+
async function getCommitMetadata(cwd, commit) {
|
|
1537
|
+
const out = await runGit(cwd, ["show", "-s", "--format=%H%x1f%aI%x1f%s", commit]);
|
|
1538
|
+
const [sha, authorDate, subject] = out.split("");
|
|
1539
|
+
if (!sha || !authorDate) {
|
|
1540
|
+
throw new Error(`Unable to read commit metadata for ${commit}`);
|
|
1541
|
+
}
|
|
1542
|
+
return {
|
|
1543
|
+
sha,
|
|
1544
|
+
authorDate,
|
|
1545
|
+
subject: subject ?? ""
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
function buildSourceIdentity(section, value) {
|
|
1549
|
+
const sourceName = typeof value.sourceName === "string" ? value.sourceName : void 0;
|
|
1550
|
+
const tag = typeof value.tag === "string" ? value.tag : void 0;
|
|
1551
|
+
const file = typeof value.file === "string" ? value.file : void 0;
|
|
1552
|
+
const line = typeof value.line === "number" ? value.line : void 0;
|
|
1553
|
+
const column = typeof value.column === "number" ? value.column : void 0;
|
|
1554
|
+
const fallbackName = sourceName ?? tag ?? section;
|
|
1555
|
+
const suffix = file ? `${file}:${line ?? 1}:${column ?? 1}` : `${section}:unknown`;
|
|
1556
|
+
return {
|
|
1557
|
+
id: sourceName ?? `${fallbackName}@${suffix}`,
|
|
1558
|
+
displayName: sourceName ?? `${fallbackName} (${suffix})`,
|
|
1559
|
+
sourceName,
|
|
1560
|
+
tag,
|
|
1561
|
+
file,
|
|
1562
|
+
line,
|
|
1563
|
+
column
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
function asRecord(value) {
|
|
1567
|
+
return value && typeof value === "object" ? value : {};
|
|
1568
|
+
}
|
|
1569
|
+
function toRouteSnapshots(payload) {
|
|
1570
|
+
const routes = {};
|
|
1571
|
+
for (const leaf of payload.leaves) {
|
|
1572
|
+
const flatSchema = payload.schemaFlatByLeaf[leaf.key] ?? {};
|
|
1573
|
+
const cfg = toCfgSubset(asRecord(leaf.cfg));
|
|
1574
|
+
const sourceLeaf = payload.sourceByLeaf?.[leaf.key];
|
|
1575
|
+
const sourceSchemas = sourceLeaf?.schemas;
|
|
1576
|
+
const sourceBySection = {};
|
|
1577
|
+
for (const section of SCHEMA_SECTIONS) {
|
|
1578
|
+
const schemaKey = SOURCE_KEY_BY_SECTION[section];
|
|
1579
|
+
const sectionSource = sourceSchemas?.[schemaKey];
|
|
1580
|
+
if (!sectionSource || typeof sectionSource !== "object") continue;
|
|
1581
|
+
sourceBySection[section] = buildSourceIdentity(
|
|
1582
|
+
section,
|
|
1583
|
+
sectionSource
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
routes[leaf.key] = {
|
|
1587
|
+
key: leaf.key,
|
|
1588
|
+
method: String(leaf.method).toUpperCase(),
|
|
1589
|
+
path: leaf.path,
|
|
1590
|
+
cfg,
|
|
1591
|
+
flatSchema,
|
|
1592
|
+
sourceBySection
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
return routes;
|
|
1596
|
+
}
|
|
1597
|
+
function sectionPathEntries(flatSchema, section) {
|
|
1598
|
+
return Object.entries(flatSchema).filter(
|
|
1599
|
+
([schemaPath]) => schemaPath === section || schemaPath.startsWith(`${section}.`) || schemaPath.startsWith(`${section}[]`)
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
function toSourceObjectSnapshots(routesByKey) {
|
|
1603
|
+
const working = /* @__PURE__ */ new Map();
|
|
1604
|
+
for (const route of Object.values(routesByKey)) {
|
|
1605
|
+
for (const section of SCHEMA_SECTIONS) {
|
|
1606
|
+
const sourceIdentity = route.sourceBySection[section];
|
|
1607
|
+
if (!sourceIdentity) continue;
|
|
1608
|
+
const entries = sectionPathEntries(route.flatSchema, section);
|
|
1609
|
+
if (entries.length === 0) continue;
|
|
1610
|
+
const existing = working.get(sourceIdentity.id) ?? {
|
|
1611
|
+
id: sourceIdentity.id,
|
|
1612
|
+
displayName: sourceIdentity.displayName,
|
|
1613
|
+
routeKeys: /* @__PURE__ */ new Set(),
|
|
1614
|
+
sections: {
|
|
1615
|
+
params: /* @__PURE__ */ new Map(),
|
|
1616
|
+
query: /* @__PURE__ */ new Map(),
|
|
1617
|
+
body: /* @__PURE__ */ new Map(),
|
|
1618
|
+
output: /* @__PURE__ */ new Map()
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
existing.routeKeys.add(route.key);
|
|
1622
|
+
for (const [schemaPath, schemaEntry] of entries) {
|
|
1623
|
+
const sectionMap = existing.sections[section];
|
|
1624
|
+
const signatures = sectionMap.get(schemaPath) ?? /* @__PURE__ */ new Set();
|
|
1625
|
+
signatures.add(entrySignature(schemaEntry));
|
|
1626
|
+
sectionMap.set(schemaPath, signatures);
|
|
1627
|
+
}
|
|
1628
|
+
working.set(sourceIdentity.id, existing);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
const out = {};
|
|
1632
|
+
for (const [id, item] of working.entries()) {
|
|
1633
|
+
const sections = {
|
|
1634
|
+
params: {},
|
|
1635
|
+
query: {},
|
|
1636
|
+
body: {},
|
|
1637
|
+
output: {}
|
|
1638
|
+
};
|
|
1639
|
+
for (const section of SCHEMA_SECTIONS) {
|
|
1640
|
+
for (const [schemaPath, signatures] of item.sections[section].entries()) {
|
|
1641
|
+
sections[section][schemaPath] = Array.from(signatures).sort();
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
out[id] = {
|
|
1645
|
+
id,
|
|
1646
|
+
displayName: item.displayName,
|
|
1647
|
+
routeKeys: Array.from(item.routeKeys).sort(),
|
|
1648
|
+
sections
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
return out;
|
|
1652
|
+
}
|
|
1653
|
+
function diffRouteSchemas(before, after) {
|
|
1654
|
+
const paths = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
1655
|
+
const out = [];
|
|
1656
|
+
for (const schemaPath of Array.from(paths).sort()) {
|
|
1657
|
+
const prev = before[schemaPath];
|
|
1658
|
+
const next = after[schemaPath];
|
|
1659
|
+
if (!prev && next) {
|
|
1660
|
+
out.push({ path: schemaPath, op: "added", after: next });
|
|
1661
|
+
continue;
|
|
1662
|
+
}
|
|
1663
|
+
if (prev && !next) {
|
|
1664
|
+
out.push({ path: schemaPath, op: "removed", before: prev });
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
if (!prev || !next) continue;
|
|
1668
|
+
if (entrySignature(prev) !== entrySignature(next)) {
|
|
1669
|
+
out.push({ path: schemaPath, op: "changed", before: prev, after: next });
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return out;
|
|
1673
|
+
}
|
|
1674
|
+
function diffRouteCfg(before, after) {
|
|
1675
|
+
const out = [];
|
|
1676
|
+
for (const field of CHANGESET_CFG_FIELDS) {
|
|
1677
|
+
const prev = before[field];
|
|
1678
|
+
const next = after[field];
|
|
1679
|
+
if (stableStringify(prev) !== stableStringify(next)) {
|
|
1680
|
+
out.push({
|
|
1681
|
+
field,
|
|
1682
|
+
before: prev,
|
|
1683
|
+
after: next
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return out;
|
|
1688
|
+
}
|
|
1689
|
+
function flattenSourceSections(snapshot) {
|
|
1690
|
+
const map = /* @__PURE__ */ new Map();
|
|
1691
|
+
for (const section of SCHEMA_SECTIONS) {
|
|
1692
|
+
const entries = snapshot.sections[section];
|
|
1693
|
+
for (const [schemaPath, signatures] of Object.entries(entries)) {
|
|
1694
|
+
map.set(`${section}:${schemaPath}`, signatures);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return map;
|
|
1698
|
+
}
|
|
1699
|
+
function diffSourceSchemas(before, after) {
|
|
1700
|
+
const prev = flattenSourceSections(before);
|
|
1701
|
+
const next = flattenSourceSections(after);
|
|
1702
|
+
const keys = /* @__PURE__ */ new Set([...prev.keys(), ...next.keys()]);
|
|
1703
|
+
const out = [];
|
|
1704
|
+
for (const key of Array.from(keys).sort()) {
|
|
1705
|
+
const prevSignatures = prev.get(key);
|
|
1706
|
+
const nextSignatures = next.get(key);
|
|
1707
|
+
const [section, ...rest] = key.split(":");
|
|
1708
|
+
const schemaPath = rest.join(":");
|
|
1709
|
+
if (!prevSignatures && nextSignatures) {
|
|
1710
|
+
out.push({
|
|
1711
|
+
section,
|
|
1712
|
+
path: schemaPath,
|
|
1713
|
+
op: "added",
|
|
1714
|
+
after: nextSignatures
|
|
1715
|
+
});
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
if (prevSignatures && !nextSignatures) {
|
|
1719
|
+
out.push({
|
|
1720
|
+
section,
|
|
1721
|
+
path: schemaPath,
|
|
1722
|
+
op: "removed",
|
|
1723
|
+
before: prevSignatures
|
|
1724
|
+
});
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
if (!prevSignatures || !nextSignatures) continue;
|
|
1728
|
+
if (stableStringify(prevSignatures) !== stableStringify(nextSignatures)) {
|
|
1729
|
+
out.push({
|
|
1730
|
+
section,
|
|
1731
|
+
path: schemaPath,
|
|
1732
|
+
op: "changed",
|
|
1733
|
+
before: prevSignatures,
|
|
1734
|
+
after: nextSignatures
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return out;
|
|
1739
|
+
}
|
|
1740
|
+
function routeRecordByKey(events) {
|
|
1741
|
+
var _a;
|
|
1742
|
+
const out = {};
|
|
1743
|
+
for (const event of events) {
|
|
1744
|
+
out[_a = event.routeKey] ?? (out[_a] = []);
|
|
1745
|
+
out[event.routeKey].push(event);
|
|
1746
|
+
}
|
|
1747
|
+
return out;
|
|
1748
|
+
}
|
|
1749
|
+
function sourceRecordById(events) {
|
|
1750
|
+
var _a;
|
|
1751
|
+
const out = {};
|
|
1752
|
+
for (const event of events) {
|
|
1753
|
+
out[_a = event.sourceObjectId] ?? (out[_a] = []);
|
|
1754
|
+
out[event.sourceObjectId].push(event);
|
|
1755
|
+
}
|
|
1756
|
+
return out;
|
|
1757
|
+
}
|
|
1758
|
+
function buildRollups(routeEvents, sourceEvents, commits) {
|
|
1759
|
+
var _a, _b;
|
|
1760
|
+
const route = {
|
|
1761
|
+
route_added: 0,
|
|
1762
|
+
route_removed: 0,
|
|
1763
|
+
schema_changed: 0,
|
|
1764
|
+
cfg_changed: 0
|
|
1765
|
+
};
|
|
1766
|
+
const sourceObject = {
|
|
1767
|
+
source_object_added: 0,
|
|
1768
|
+
source_object_removed: 0,
|
|
1769
|
+
source_schema_changed: 0
|
|
1770
|
+
};
|
|
1771
|
+
for (const event of routeEvents) {
|
|
1772
|
+
route[event.type] += 1;
|
|
1773
|
+
}
|
|
1774
|
+
for (const event of sourceEvents) {
|
|
1775
|
+
sourceObject[event.type] += 1;
|
|
1776
|
+
}
|
|
1777
|
+
const byCommit = {};
|
|
1778
|
+
for (const commit of commits) {
|
|
1779
|
+
byCommit[commit.sha] = { routeEvents: 0, sourceEvents: 0 };
|
|
1780
|
+
}
|
|
1781
|
+
for (const event of routeEvents) {
|
|
1782
|
+
byCommit[_a = event.commitSha] ?? (byCommit[_a] = { routeEvents: 0, sourceEvents: 0 });
|
|
1783
|
+
byCommit[event.commitSha].routeEvents += 1;
|
|
1784
|
+
}
|
|
1785
|
+
for (const event of sourceEvents) {
|
|
1786
|
+
byCommit[_b = event.commitSha] ?? (byCommit[_b] = { routeEvents: 0, sourceEvents: 0 });
|
|
1787
|
+
byCommit[event.commitSha].sourceEvents += 1;
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
route,
|
|
1791
|
+
sourceObject,
|
|
1792
|
+
byCommit
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
async function loadChangelogInput(modulePath, exportName) {
|
|
1796
|
+
const mod = await import(`${pathToFileURL2(modulePath).href}?v=${Date.now()}`);
|
|
1797
|
+
const value = mod[exportName] ?? (mod.default && mod.default[exportName]);
|
|
1798
|
+
if (!value) {
|
|
1799
|
+
throw new Error(`Export "${exportName}" not found in module: ${modulePath}`);
|
|
1800
|
+
}
|
|
1801
|
+
return value;
|
|
1802
|
+
}
|
|
1803
|
+
async function createSnapshot(repoRoot, moduleRel, exportName, commit, tempRoot, tsconfigRel, exportOptions) {
|
|
1804
|
+
const worktreeDir = path4.join(tempRoot, `wt-${commit.sha.slice(0, 12)}`);
|
|
1805
|
+
await runGit(repoRoot, ["worktree", "add", "--detach", worktreeDir, commit.sha]);
|
|
1806
|
+
try {
|
|
1807
|
+
const modulePath = path4.resolve(worktreeDir, moduleRel);
|
|
1808
|
+
const tsconfigPath = tsconfigRel ? path4.resolve(worktreeDir, tsconfigRel) : void 0;
|
|
1809
|
+
const input = await loadChangelogInput(modulePath, exportName);
|
|
1810
|
+
const payload = await exportFinalizedLeaves(input, {
|
|
1811
|
+
includeSource: true,
|
|
1812
|
+
sourceModulePath: modulePath,
|
|
1813
|
+
sourceExportName: exportName,
|
|
1814
|
+
tsconfigPath,
|
|
1815
|
+
...exportOptions
|
|
1816
|
+
});
|
|
1817
|
+
const routesByKey = toRouteSnapshots(payload);
|
|
1818
|
+
const sourceObjectsById = toSourceObjectSnapshots(routesByKey);
|
|
1819
|
+
return {
|
|
1820
|
+
commit,
|
|
1821
|
+
routesByKey,
|
|
1822
|
+
sourceObjectsById
|
|
1823
|
+
};
|
|
1824
|
+
} finally {
|
|
1825
|
+
await runGit(repoRoot, ["worktree", "remove", "--force", worktreeDir]).catch(() => void 0);
|
|
1826
|
+
await fs2.rm(worktreeDir, { recursive: true, force: true }).catch(() => void 0);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
function diffSnapshots(previous, current) {
|
|
1830
|
+
const routeEvents = [];
|
|
1831
|
+
const sourceEvents = [];
|
|
1832
|
+
const routeKeys = /* @__PURE__ */ new Set([
|
|
1833
|
+
...Object.keys(previous.routesByKey),
|
|
1834
|
+
...Object.keys(current.routesByKey)
|
|
1835
|
+
]);
|
|
1836
|
+
for (const routeKey of Array.from(routeKeys).sort()) {
|
|
1837
|
+
const prevRoute = previous.routesByKey[routeKey];
|
|
1838
|
+
const nextRoute = current.routesByKey[routeKey];
|
|
1839
|
+
if (!prevRoute && nextRoute) {
|
|
1840
|
+
routeEvents.push({
|
|
1841
|
+
type: "route_added",
|
|
1842
|
+
commitSha: current.commit.sha,
|
|
1843
|
+
routeKey,
|
|
1844
|
+
method: nextRoute.method,
|
|
1845
|
+
path: nextRoute.path
|
|
1846
|
+
});
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
if (prevRoute && !nextRoute) {
|
|
1850
|
+
routeEvents.push({
|
|
1851
|
+
type: "route_removed",
|
|
1852
|
+
commitSha: current.commit.sha,
|
|
1853
|
+
routeKey,
|
|
1854
|
+
method: prevRoute.method,
|
|
1855
|
+
path: prevRoute.path
|
|
1856
|
+
});
|
|
1857
|
+
continue;
|
|
1858
|
+
}
|
|
1859
|
+
if (!prevRoute || !nextRoute) continue;
|
|
1860
|
+
const schemaDiff = diffRouteSchemas(prevRoute.flatSchema, nextRoute.flatSchema);
|
|
1861
|
+
if (schemaDiff.length > 0) {
|
|
1862
|
+
routeEvents.push({
|
|
1863
|
+
type: "schema_changed",
|
|
1864
|
+
commitSha: current.commit.sha,
|
|
1865
|
+
routeKey,
|
|
1866
|
+
method: nextRoute.method,
|
|
1867
|
+
path: nextRoute.path,
|
|
1868
|
+
schemaDiff
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
const cfgDiff = diffRouteCfg(prevRoute.cfg, nextRoute.cfg);
|
|
1872
|
+
if (cfgDiff.length > 0) {
|
|
1873
|
+
routeEvents.push({
|
|
1874
|
+
type: "cfg_changed",
|
|
1875
|
+
commitSha: current.commit.sha,
|
|
1876
|
+
routeKey,
|
|
1877
|
+
method: nextRoute.method,
|
|
1878
|
+
path: nextRoute.path,
|
|
1879
|
+
cfgDiff
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
const sourceKeys = /* @__PURE__ */ new Set([
|
|
1884
|
+
...Object.keys(previous.sourceObjectsById),
|
|
1885
|
+
...Object.keys(current.sourceObjectsById)
|
|
1886
|
+
]);
|
|
1887
|
+
for (const sourceObjectId of Array.from(sourceKeys).sort()) {
|
|
1888
|
+
const prevSource = previous.sourceObjectsById[sourceObjectId];
|
|
1889
|
+
const nextSource = current.sourceObjectsById[sourceObjectId];
|
|
1890
|
+
if (!prevSource && nextSource) {
|
|
1891
|
+
sourceEvents.push({
|
|
1892
|
+
type: "source_object_added",
|
|
1893
|
+
commitSha: current.commit.sha,
|
|
1894
|
+
sourceObjectId,
|
|
1895
|
+
displayName: nextSource.displayName,
|
|
1896
|
+
impactedRoutes: nextSource.routeKeys
|
|
1897
|
+
});
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
if (prevSource && !nextSource) {
|
|
1901
|
+
sourceEvents.push({
|
|
1902
|
+
type: "source_object_removed",
|
|
1903
|
+
commitSha: current.commit.sha,
|
|
1904
|
+
sourceObjectId,
|
|
1905
|
+
displayName: prevSource.displayName,
|
|
1906
|
+
impactedRoutes: prevSource.routeKeys
|
|
1907
|
+
});
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
if (!prevSource || !nextSource) continue;
|
|
1911
|
+
const schemaDiff = diffSourceSchemas(prevSource, nextSource);
|
|
1912
|
+
const prevRoutes = new Set(prevSource.routeKeys);
|
|
1913
|
+
const nextRoutes = new Set(nextSource.routeKeys);
|
|
1914
|
+
const routeAdded = Array.from(nextRoutes).filter((routeKey) => !prevRoutes.has(routeKey));
|
|
1915
|
+
const routeRemoved = Array.from(prevRoutes).filter((routeKey) => !nextRoutes.has(routeKey));
|
|
1916
|
+
if (schemaDiff.length > 0 || routeAdded.length > 0 || routeRemoved.length > 0) {
|
|
1917
|
+
sourceEvents.push({
|
|
1918
|
+
type: "source_schema_changed",
|
|
1919
|
+
commitSha: current.commit.sha,
|
|
1920
|
+
sourceObjectId,
|
|
1921
|
+
displayName: nextSource.displayName,
|
|
1922
|
+
impactedRoutes: nextSource.routeKeys,
|
|
1923
|
+
schemaDiff: schemaDiff.length > 0 ? schemaDiff : void 0,
|
|
1924
|
+
routeAdded: routeAdded.length > 0 ? routeAdded : void 0,
|
|
1925
|
+
routeRemoved: routeRemoved.length > 0 ? routeRemoved : void 0
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return {
|
|
1930
|
+
routeEvents,
|
|
1931
|
+
sourceEvents
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
var BAKED_PAYLOAD_MARKER2 = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
|
|
1935
|
+
function escapePayloadForInlineScript2(payload) {
|
|
1936
|
+
return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
|
|
1937
|
+
}
|
|
1938
|
+
function injectPayloadIntoViewerHtml2(htmlTemplate, payload) {
|
|
1939
|
+
const payloadScript = `${BAKED_PAYLOAD_MARKER2}
|
|
1940
|
+
<script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript2(
|
|
1941
|
+
payload
|
|
1942
|
+
)};</script>`;
|
|
1943
|
+
if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER2)) {
|
|
1944
|
+
return htmlTemplate.replace(BAKED_PAYLOAD_MARKER2, payloadScript);
|
|
1945
|
+
}
|
|
1946
|
+
if (htmlTemplate.includes("</body>")) {
|
|
1947
|
+
return htmlTemplate.replace("</body>", `${payloadScript}
|
|
1948
|
+
</body>`);
|
|
1949
|
+
}
|
|
1950
|
+
return `${payloadScript}
|
|
1951
|
+
${htmlTemplate}`;
|
|
1952
|
+
}
|
|
1953
|
+
async function resolveViewerTemplatePath2(viewerTemplateFile) {
|
|
1954
|
+
if (viewerTemplateFile) {
|
|
1955
|
+
const resolved = path4.resolve(viewerTemplateFile);
|
|
1956
|
+
await fs2.access(resolved);
|
|
1957
|
+
return resolved;
|
|
1958
|
+
}
|
|
1959
|
+
const candidates = [
|
|
1960
|
+
path4.resolve(
|
|
1961
|
+
process.cwd(),
|
|
1962
|
+
"node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
|
|
1963
|
+
),
|
|
1964
|
+
path4.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
|
|
1965
|
+
path4.resolve(process.cwd(), "packages/export/tools/finalized-leaves-viewer.html")
|
|
1966
|
+
];
|
|
1967
|
+
for (const candidate of candidates) {
|
|
1968
|
+
try {
|
|
1969
|
+
await fs2.access(candidate);
|
|
1970
|
+
return candidate;
|
|
1971
|
+
} catch {
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return void 0;
|
|
1975
|
+
}
|
|
1976
|
+
async function writeJsonExport2(payload, outFile) {
|
|
1977
|
+
const resolved = path4.resolve(outFile);
|
|
1978
|
+
await fs2.mkdir(path4.dirname(resolved), { recursive: true });
|
|
1979
|
+
await fs2.writeFile(resolved, `${JSON.stringify(payload, null, 2)}
|
|
1980
|
+
`, "utf8");
|
|
1981
|
+
return resolved;
|
|
1982
|
+
}
|
|
1983
|
+
async function writeBakedHtmlExport2(payload, htmlFile, viewerTemplateFile) {
|
|
1984
|
+
const templatePath = await resolveViewerTemplatePath2(viewerTemplateFile);
|
|
1985
|
+
const template = templatePath ? await fs2.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
1986
|
+
const baked = injectPayloadIntoViewerHtml2(template, payload);
|
|
1987
|
+
const resolved = path4.resolve(htmlFile);
|
|
1988
|
+
await fs2.mkdir(path4.dirname(resolved), { recursive: true });
|
|
1989
|
+
await fs2.writeFile(resolved, baked, "utf8");
|
|
1990
|
+
return resolved;
|
|
1991
|
+
}
|
|
1992
|
+
async function openHtmlInBrowser2(filePath) {
|
|
1993
|
+
const resolved = path4.resolve(filePath);
|
|
1994
|
+
const platform = process.platform;
|
|
1995
|
+
if (platform === "darwin") {
|
|
1996
|
+
spawn2("open", [resolved], { detached: true, stdio: "ignore" }).unref();
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
if (platform === "win32") {
|
|
2000
|
+
spawn2("cmd", ["/c", "start", "", resolved], {
|
|
2001
|
+
detached: true,
|
|
2002
|
+
stdio: "ignore"
|
|
2003
|
+
}).unref();
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
spawn2("xdg-open", [resolved], { detached: true, stdio: "ignore" }).unref();
|
|
2007
|
+
}
|
|
2008
|
+
async function writeFinalizedLeavesChangelogExport(payload, outFileOrOptions) {
|
|
2009
|
+
const options = typeof outFileOrOptions === "string" ? { outFile: outFileOrOptions } : outFileOrOptions;
|
|
2010
|
+
const written = {};
|
|
2011
|
+
if (options.outFile) {
|
|
2012
|
+
written.outFile = await writeJsonExport2(payload, options.outFile);
|
|
2013
|
+
}
|
|
2014
|
+
if (options.htmlFile) {
|
|
2015
|
+
written.htmlFile = await writeBakedHtmlExport2(
|
|
2016
|
+
payload,
|
|
2017
|
+
options.htmlFile,
|
|
2018
|
+
options.viewerTemplateFile
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
if (options.openOnFinish) {
|
|
2022
|
+
if (!written.htmlFile) {
|
|
2023
|
+
throw new Error(
|
|
2024
|
+
"openOnFinish requires htmlFile. Provide htmlFile to open the baked viewer."
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
await openHtmlInBrowser2(written.htmlFile);
|
|
2028
|
+
}
|
|
2029
|
+
return written;
|
|
2030
|
+
}
|
|
2031
|
+
async function exportFinalizedLeavesChangelog(options) {
|
|
2032
|
+
const log = options.log ?? (() => void 0);
|
|
2033
|
+
const cwd = process.cwd();
|
|
2034
|
+
log(`[changelog] resolving repository root from ${cwd}`);
|
|
2035
|
+
const repoRoot = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
2036
|
+
const modulePathAbsolute = path4.resolve(cwd, options.modulePath);
|
|
2037
|
+
const modulePathRelative = normalizePath(path4.relative(repoRoot, modulePathAbsolute));
|
|
2038
|
+
if (modulePathRelative.startsWith("..")) {
|
|
2039
|
+
throw new Error("modulePath must resolve inside the git repository root.");
|
|
2040
|
+
}
|
|
2041
|
+
const tsconfigAbsolute = options.tsconfigPath ? path4.resolve(cwd, options.tsconfigPath) : void 0;
|
|
2042
|
+
const tsconfigRelative = tsconfigAbsolute ? normalizePath(path4.relative(repoRoot, tsconfigAbsolute)) : void 0;
|
|
2043
|
+
if (tsconfigRelative && tsconfigRelative.startsWith("..")) {
|
|
2044
|
+
throw new Error("tsconfigPath must resolve inside the git repository root.");
|
|
2045
|
+
}
|
|
2046
|
+
const exportName = options.exportName ?? "leaves";
|
|
2047
|
+
log("[changelog] resolving commit window");
|
|
2048
|
+
const window = await resolveCommitWindow(repoRoot, options.from, options.to);
|
|
2049
|
+
log(`[changelog] window: ${window.from.slice(0, 12)}..${window.to.slice(0, 12)}`);
|
|
2050
|
+
const allCommits = await resolveCommitPath(repoRoot, window.from, window.to);
|
|
2051
|
+
log(`[changelog] commits in ancestry path: ${allCommits.length}`);
|
|
2052
|
+
const selectedCommitShas = await filterCommits(
|
|
2053
|
+
repoRoot,
|
|
2054
|
+
allCommits,
|
|
2055
|
+
modulePathRelative,
|
|
2056
|
+
tsconfigRelative
|
|
2057
|
+
);
|
|
2058
|
+
log(`[changelog] commits after path filter: ${selectedCommitShas.length}`);
|
|
2059
|
+
if (selectedCommitShas.length === 0) {
|
|
2060
|
+
selectedCommitShas.push(window.to);
|
|
2061
|
+
log("[changelog] filter returned no commits, forcing tip commit inclusion");
|
|
2062
|
+
}
|
|
2063
|
+
const commits = [];
|
|
2064
|
+
log("[changelog] loading commit metadata");
|
|
2065
|
+
for (const commit of selectedCommitShas) {
|
|
2066
|
+
commits.push(await getCommitMetadata(repoRoot, commit));
|
|
2067
|
+
}
|
|
2068
|
+
const tempRoot = await fs2.mkdtemp(path4.join(os.tmpdir(), "rrroutes-export-changelog-"));
|
|
2069
|
+
log(`[changelog] temp workspace: ${tempRoot}`);
|
|
2070
|
+
try {
|
|
2071
|
+
const snapshots = [];
|
|
2072
|
+
for (let index = 0; index < commits.length; index += 1) {
|
|
2073
|
+
const commit = commits[index];
|
|
2074
|
+
log(
|
|
2075
|
+
`[changelog] snapshot ${index + 1}/${commits.length}: ${commit.sha.slice(0, 12)} ${commit.subject}`
|
|
2076
|
+
);
|
|
2077
|
+
snapshots.push(
|
|
2078
|
+
await createSnapshot(
|
|
2079
|
+
repoRoot,
|
|
2080
|
+
modulePathRelative,
|
|
2081
|
+
exportName,
|
|
2082
|
+
commit,
|
|
2083
|
+
tempRoot,
|
|
2084
|
+
tsconfigRelative,
|
|
2085
|
+
options
|
|
2086
|
+
)
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
const routeEvents = [];
|
|
2090
|
+
const sourceEvents = [];
|
|
2091
|
+
log("[changelog] diffing adjacent snapshots");
|
|
2092
|
+
for (let i = 1; i < snapshots.length; i += 1) {
|
|
2093
|
+
const { routeEvents: nextRouteEvents, sourceEvents: nextSourceEvents } = diffSnapshots(
|
|
2094
|
+
snapshots[i - 1],
|
|
2095
|
+
snapshots[i]
|
|
2096
|
+
);
|
|
2097
|
+
routeEvents.push(...nextRouteEvents);
|
|
2098
|
+
sourceEvents.push(...nextSourceEvents);
|
|
2099
|
+
}
|
|
2100
|
+
log(
|
|
2101
|
+
`[changelog] diff complete: route events=${routeEvents.length}, source events=${sourceEvents.length}`
|
|
2102
|
+
);
|
|
2103
|
+
const payload = {
|
|
2104
|
+
kind: "finalized-leaves-changelog",
|
|
2105
|
+
_meta: {
|
|
2106
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2107
|
+
description: "Git-history changelog for RRRoutes finalized leaves with route-level and source-object schema diffs.",
|
|
2108
|
+
modulePath: options.modulePath,
|
|
2109
|
+
exportName,
|
|
2110
|
+
from: window.from,
|
|
2111
|
+
to: window.to,
|
|
2112
|
+
selectedCommitCount: commits.length
|
|
2113
|
+
},
|
|
2114
|
+
commits,
|
|
2115
|
+
byRoute: routeRecordByKey(routeEvents),
|
|
2116
|
+
bySourceObject: sourceRecordById(sourceEvents),
|
|
2117
|
+
rollups: buildRollups(routeEvents, sourceEvents, commits)
|
|
2118
|
+
};
|
|
2119
|
+
if (options.outFile || options.htmlFile) {
|
|
2120
|
+
log("[changelog] writing output artifacts");
|
|
2121
|
+
await writeFinalizedLeavesChangelogExport(payload, {
|
|
2122
|
+
outFile: options.outFile,
|
|
2123
|
+
htmlFile: options.htmlFile,
|
|
2124
|
+
viewerTemplateFile: options.viewerTemplateFile,
|
|
2125
|
+
openOnFinish: options.openOnFinish
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
log("[changelog] done");
|
|
2129
|
+
return payload;
|
|
2130
|
+
} finally {
|
|
2131
|
+
await fs2.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
2132
|
+
log("[changelog] cleaned temp workspace");
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
var __private = {
|
|
2136
|
+
resolveCommitWindow,
|
|
2137
|
+
resolveCommitPath,
|
|
2138
|
+
filterCommits,
|
|
2139
|
+
toRouteSnapshots,
|
|
2140
|
+
toSourceObjectSnapshots,
|
|
2141
|
+
diffSnapshots,
|
|
2142
|
+
buildSourceIdentity
|
|
2143
|
+
};
|
|
2144
|
+
|
|
2145
|
+
// src/exportFinalizedLeaves.changelog.cli.ts
|
|
2146
|
+
import path5 from "path";
|
|
2147
|
+
function parseFinalizedLeavesChangelogCliArgs(argv) {
|
|
2148
|
+
const args = /* @__PURE__ */ new Map();
|
|
2149
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
2150
|
+
const key = argv[i];
|
|
2151
|
+
if (key === "--") continue;
|
|
2152
|
+
if (!key.startsWith("--")) continue;
|
|
2153
|
+
if (key === "--with-source") {
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
const value = argv[i + 1];
|
|
2157
|
+
if (!value || value.startsWith("--")) {
|
|
2158
|
+
throw new Error(`Missing value for ${key}`);
|
|
2159
|
+
}
|
|
2160
|
+
args.set(key, value);
|
|
2161
|
+
i += 1;
|
|
2162
|
+
}
|
|
2163
|
+
const modulePath = args.get("--module");
|
|
2164
|
+
if (!modulePath) {
|
|
2165
|
+
throw new Error("Missing required --module argument");
|
|
2166
|
+
}
|
|
2167
|
+
return {
|
|
2168
|
+
modulePath,
|
|
2169
|
+
exportName: args.get("--export") ?? "leaves",
|
|
2170
|
+
outFile: args.get("--out") ?? "finalized-leaves.changelog.json",
|
|
2171
|
+
htmlFile: args.get("--html") ?? void 0,
|
|
2172
|
+
tsconfigPath: args.get("--tsconfig") ?? void 0,
|
|
2173
|
+
from: args.get("--from") ?? void 0,
|
|
2174
|
+
to: args.get("--to") ?? void 0
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
async function runExportFinalizedLeavesChangelogCli(argv) {
|
|
2178
|
+
const args = parseFinalizedLeavesChangelogCliArgs(argv);
|
|
2179
|
+
const log = (message) => {
|
|
2180
|
+
process.stdout.write(`${message}
|
|
2181
|
+
`);
|
|
2182
|
+
};
|
|
2183
|
+
const options = {
|
|
2184
|
+
modulePath: args.modulePath,
|
|
2185
|
+
exportName: args.exportName,
|
|
2186
|
+
outFile: args.outFile,
|
|
2187
|
+
htmlFile: args.htmlFile,
|
|
2188
|
+
tsconfigPath: args.tsconfigPath,
|
|
2189
|
+
from: args.from,
|
|
2190
|
+
to: args.to,
|
|
2191
|
+
includeSource: true,
|
|
2192
|
+
log
|
|
2193
|
+
};
|
|
2194
|
+
log("[changelog-cli] starting changelog export");
|
|
2195
|
+
const payload = await exportFinalizedLeavesChangelog(options);
|
|
2196
|
+
log("[changelog-cli] changelog export completed");
|
|
2197
|
+
return {
|
|
2198
|
+
payload,
|
|
2199
|
+
outFile: path5.resolve(args.outFile),
|
|
2200
|
+
htmlFile: args.htmlFile ? path5.resolve(args.htmlFile) : void 0
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
1318
2203
|
export {
|
|
1319
2204
|
DEFAULT_VIEWER_TEMPLATE,
|
|
2205
|
+
__private,
|
|
1320
2206
|
clearSchemaIntrospectionHandlers,
|
|
1321
2207
|
createSchemaIntrospector,
|
|
1322
2208
|
exportFinalizedLeaves,
|
|
2209
|
+
exportFinalizedLeavesChangelog,
|
|
1323
2210
|
extractLeafSourceByAst,
|
|
1324
2211
|
flattenLeafSchemas,
|
|
1325
2212
|
flattenSerializableSchema,
|
|
1326
2213
|
introspectSchema,
|
|
1327
2214
|
loadFinalizedLeavesInput,
|
|
2215
|
+
parseFinalizedLeavesChangelogCliArgs,
|
|
1328
2216
|
parseFinalizedLeavesCliArgs,
|
|
1329
2217
|
registerSchemaIntrospectionHandler,
|
|
2218
|
+
runExportFinalizedLeavesChangelogCli,
|
|
1330
2219
|
runExportFinalizedLeavesCli,
|
|
1331
2220
|
serializableSchemaKinds,
|
|
1332
2221
|
serializeLeafContract,
|
|
1333
2222
|
serializeLeavesContract,
|
|
2223
|
+
writeFinalizedLeavesChangelogExport,
|
|
1334
2224
|
writeFinalizedLeavesExport
|
|
1335
2225
|
};
|
|
1336
2226
|
//# sourceMappingURL=index.mjs.map
|