@driftdev/cli 0.37.0 → 0.38.0

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 (2) hide show
  1. package/dist/drift.js +176 -23
  2. package/package.json +1 -1
package/dist/drift.js CHANGED
@@ -175703,6 +175703,54 @@ function renderBatchExamples(data) {
175703
175703
  return lines.join(`
175704
175704
  `);
175705
175705
  }
175706
+ function renderBatchDiff(data) {
175707
+ const lines = [""];
175708
+ const rows = data.packages;
175709
+ const nameW = Math.max(7, ...rows.map((r) => r.name.length));
175710
+ lines.push(indent(`${c.gray(pad("PACKAGE", nameW))} ${c.gray(pad("BREAKING", 8))} ${c.gray(pad("ADDED", 5))} ${c.gray("CHANGED")}`));
175711
+ for (const r of rows) {
175712
+ const bStr = pad(String(r.breaking), 8);
175713
+ const aStr = pad(String(r.added), 5);
175714
+ const cStr = String(r.changed);
175715
+ const breaking = r.breaking === 0 ? c.green(bStr) : c.red(bStr);
175716
+ const added = r.added === 0 ? aStr : c.green(aStr);
175717
+ const changed = r.changed === 0 ? cStr : c.yellow(cStr);
175718
+ lines.push(indent(`${pad(r.name, nameW)} ${breaking} ${added} ${changed}`));
175719
+ }
175720
+ lines.push("");
175721
+ const parts = [];
175722
+ if (data.aggregate.breaking > 0)
175723
+ parts.push(`${data.aggregate.breaking} breaking`);
175724
+ if (data.aggregate.added > 0)
175725
+ parts.push(`${data.aggregate.added} added`);
175726
+ if (data.aggregate.changed > 0)
175727
+ parts.push(`${data.aggregate.changed} changed`);
175728
+ lines.push(indent(`${c.bold("Total")}: ${parts.length > 0 ? parts.join(", ") : "no changes"}`));
175729
+ if (data.skipped && data.skipped.length > 0) {
175730
+ lines.push(indent(`${c.gray(`Skipped ${data.skipped.length} private: ${data.skipped.join(", ")}`)}`));
175731
+ }
175732
+ lines.push("");
175733
+ return lines.join(`
175734
+ `);
175735
+ }
175736
+ function renderBatchBreaking(data) {
175737
+ const lines = [""];
175738
+ const rows = data.packages;
175739
+ const nameW = Math.max(7, ...rows.map((r) => r.name.length));
175740
+ lines.push(indent(`${c.gray(pad("PACKAGE", nameW))} ${c.gray("BREAKING")}`));
175741
+ for (const r of rows) {
175742
+ const count = r.count === 0 ? c.green("0") : c.red(String(r.count));
175743
+ lines.push(indent(`${pad(r.name, nameW)} ${count}`));
175744
+ }
175745
+ lines.push("");
175746
+ lines.push(indent(`${c.bold("Total")}: ${data.aggregate.count} breaking change${data.aggregate.count === 1 ? "" : "s"}`));
175747
+ if (data.skipped && data.skipped.length > 0) {
175748
+ lines.push(indent(`${c.gray(`Skipped ${data.skipped.length} private: ${data.skipped.join(", ")}`)}`));
175749
+ }
175750
+ lines.push("");
175751
+ return lines.join(`
175752
+ `);
175753
+ }
175706
175754
  function pad(s, w) {
175707
175755
  return s + " ".repeat(Math.max(0, w - s.length));
175708
175756
  }
@@ -176127,7 +176175,7 @@ function registerHealthCommand(program) {
176127
176175
  import { readFileSync as readFileSync15 } from "node:fs";
176128
176176
  import * as path19 from "node:path";
176129
176177
  import { fileURLToPath as fileURLToPath2 } from "node:url";
176130
- import { diffSpec as diffSpec3, categorizeBreakingChanges as categorizeBreakingChanges2 } from "@openpkg-ts/spec";
176178
+ import { diffSpec as diffSpec3, categorizeBreakingChanges as categorizeBreakingChanges2, normalize as normalize4 } from "@openpkg-ts/spec";
176131
176179
 
176132
176180
  // src/formatters/breaking.ts
176133
176181
  function renderBreaking(data) {
@@ -176150,17 +176198,20 @@ function renderBreaking(data) {
176150
176198
  `);
176151
176199
  }
176152
176200
 
176153
- // src/utils/resolve-specs.ts
176154
- import { readFileSync as readFileSync14 } from "node:fs";
176155
- import * as path18 from "node:path";
176156
- import { normalize as normalize3 } from "@openpkg-ts/spec";
176157
-
176158
176201
  // src/utils/git-extract.ts
176159
176202
  import { execSync } from "node:child_process";
176160
176203
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, mkdtempSync, rmSync as rmSync2, symlinkSync, writeFileSync as writeFileSync4, cpSync } from "node:fs";
176161
176204
  import * as os4 from "node:os";
176162
176205
  import * as path17 from "node:path";
176163
176206
  import { normalize as normalize2 } from "@openpkg-ts/spec";
176207
+ function getGitRoot(cwd) {
176208
+ return execSync("git rev-parse --show-toplevel", {
176209
+ encoding: "utf-8",
176210
+ cwd,
176211
+ timeout: 5000,
176212
+ stdio: ["pipe", "pipe", "pipe"]
176213
+ }).trim();
176214
+ }
176164
176215
  function listFilesAtRef(ref, prefix, cwd) {
176165
176216
  try {
176166
176217
  const output = execSync(`git ls-tree -r --name-only ${ref} -- ${prefix}`, {
@@ -176200,19 +176251,21 @@ function validateRef(ref, cwd = process.cwd()) {
176200
176251
  }
176201
176252
  }
176202
176253
  async function extractSpecFromRef(ref, entry, cwd = process.cwd()) {
176203
- const relEntry = path17.relative(cwd, path17.resolve(cwd, entry));
176204
- const entryDir = path17.dirname(relEntry);
176254
+ const gitRoot = getGitRoot(cwd);
176255
+ const absEntry = path17.resolve(cwd, entry);
176256
+ const repoRelEntry = path17.relative(gitRoot, absEntry);
176257
+ const entryDir = path17.dirname(repoRelEntry);
176205
176258
  const srcPrefix = entryDir.includes("/") ? entryDir.split("/").slice(0, 2).join("/") : ".";
176206
176259
  const tmpDir = mkdtempSync(path17.join(os4.tmpdir(), "drift-git-"));
176207
176260
  try {
176208
176261
  const prefixes = srcPrefix === "." ? [""] : [srcPrefix];
176209
176262
  const rootFiles = ["tsconfig.json", "tsconfig.base.json", "package.json"];
176210
176263
  for (const prefix of prefixes) {
176211
- const files = listFilesAtRef(ref, prefix || ".", cwd);
176264
+ const files = listFilesAtRef(ref, prefix || ".", gitRoot);
176212
176265
  for (const file of files) {
176213
176266
  if (!file.match(/\.(ts|tsx|json|js|mjs|cjs)$/))
176214
176267
  continue;
176215
- const content = getFileAtRef(ref, file, cwd);
176268
+ const content = getFileAtRef(ref, file, gitRoot);
176216
176269
  if (content === null)
176217
176270
  continue;
176218
176271
  const destPath = path17.join(tmpDir, file);
@@ -176223,7 +176276,7 @@ async function extractSpecFromRef(ref, entry, cwd = process.cwd()) {
176223
176276
  for (const rootFile of rootFiles) {
176224
176277
  const dest = path17.join(tmpDir, rootFile);
176225
176278
  if (!existsSync14(dest)) {
176226
- const content = getFileAtRef(ref, rootFile, cwd);
176279
+ const content = getFileAtRef(ref, rootFile, gitRoot);
176227
176280
  if (content) {
176228
176281
  writeFileSync4(dest, content);
176229
176282
  }
@@ -176234,8 +176287,15 @@ async function extractSpecFromRef(ref, entry, cwd = process.cwd()) {
176234
176287
  if (existsSync14(nmSrc) && !existsSync14(nmDest)) {
176235
176288
  symlinkSync(nmSrc, nmDest, "dir");
176236
176289
  }
176290
+ if (cwd !== gitRoot) {
176291
+ const rootNm = path17.join(gitRoot, "node_modules");
176292
+ const rootNmDest = path17.join(tmpDir, "node_modules");
176293
+ if (existsSync14(rootNm) && !existsSync14(rootNmDest)) {
176294
+ symlinkSync(rootNm, rootNmDest, "dir");
176295
+ }
176296
+ }
176237
176297
  if (srcPrefix !== ".") {
176238
- const nestedNm = path17.join(cwd, srcPrefix, "node_modules");
176298
+ const nestedNm = path17.join(gitRoot, srcPrefix, "node_modules");
176239
176299
  const nestedDest = path17.join(tmpDir, srcPrefix, "node_modules");
176240
176300
  if (existsSync14(nestedNm) && !existsSync14(nestedDest)) {
176241
176301
  symlinkSync(nestedNm, nestedDest, "dir");
@@ -176246,9 +176306,9 @@ async function extractSpecFromRef(ref, entry, cwd = process.cwd()) {
176246
176306
  if (existsSync14(tsconfigSrc) && !existsSync14(tsconfigDest)) {
176247
176307
  cpSync(tsconfigSrc, tsconfigDest);
176248
176308
  }
176249
- const entryFile = path17.join(tmpDir, relEntry);
176309
+ const entryFile = path17.join(tmpDir, repoRelEntry);
176250
176310
  if (!existsSync14(entryFile)) {
176251
- throw new Error(`Entry file not found at ref ${ref}: ${relEntry}`);
176311
+ throw new Error(`Entry file not found at ref ${ref}: ${repoRelEntry}`);
176252
176312
  }
176253
176313
  const result = await extract({ entryFile });
176254
176314
  return normalize2(result.spec);
@@ -176258,6 +176318,9 @@ async function extractSpecFromRef(ref, entry, cwd = process.cwd()) {
176258
176318
  }
176259
176319
 
176260
176320
  // src/utils/resolve-specs.ts
176321
+ import { readFileSync as readFileSync14 } from "node:fs";
176322
+ import * as path18 from "node:path";
176323
+ import { normalize as normalize3 } from "@openpkg-ts/spec";
176261
176324
  function loadSpec(filePath) {
176262
176325
  return JSON.parse(readFileSync14(path18.resolve(process.cwd(), filePath), "utf-8"));
176263
176326
  }
@@ -176316,10 +176379,52 @@ function getVersion2() {
176316
176379
  }
176317
176380
  }
176318
176381
  function registerBreakingCommand(program) {
176319
- program.command("breaking [old] [new]").description("Detect breaking changes between two specs").option("--base <ref>", "Git ref for old spec").option("--head <ref>", "Git ref for new spec (default: working tree)").option("--entry <file>", "Entry file for git ref extraction").action(async (oldPath, newPath, options) => {
176382
+ program.command("breaking [old] [new]").description("Detect breaking changes between two specs").option("--base <ref>", "Git ref for old spec").option("--head <ref>", "Git ref for new spec (default: working tree)").option("--entry <file>", "Entry file for git ref extraction").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").action(async (oldPath, newPath, options) => {
176320
176383
  const startTime = Date.now();
176321
176384
  const version = getVersion2();
176322
176385
  try {
176386
+ const cwd = process.cwd();
176387
+ const allPackages = options.all ? discoverPackages(cwd) : null;
176388
+ const autoDetected = !options.all && options.base && !options.entry && !oldPath ? discoverPackages(cwd) : null;
176389
+ const batchPackages = allPackages ?? autoDetected;
176390
+ if (batchPackages && batchPackages.length > 0 && options.base) {
176391
+ const skipped = options.private ? [] : batchPackages.filter((p) => p.private).map((p) => p.name);
176392
+ const packages = options.private ? batchPackages : filterPublic(batchPackages);
176393
+ if (packages.length === 0) {
176394
+ formatError("breaking", "No workspace packages found", startTime, version);
176395
+ return;
176396
+ }
176397
+ const rows = [];
176398
+ let totalBreaking = 0;
176399
+ for (const pkg of packages) {
176400
+ const relEntry = path19.relative(cwd, pkg.entry);
176401
+ const oldSpec2 = await extractSpecFromRef(options.base, relEntry, cwd);
176402
+ let newSpec2;
176403
+ if (options.head) {
176404
+ newSpec2 = await extractSpecFromRef(options.head, relEntry, cwd);
176405
+ } else {
176406
+ const result = await extract({ entryFile: pkg.entry });
176407
+ newSpec2 = normalize4(result.spec);
176408
+ }
176409
+ const diff2 = diffSpec3(oldSpec2, newSpec2);
176410
+ const breaking2 = categorizeBreakingChanges2(diff2.breaking, oldSpec2, newSpec2);
176411
+ rows.push({ name: pkg.name, breaking: breaking2, count: breaking2.length });
176412
+ totalBreaking += breaking2.length;
176413
+ }
176414
+ const data2 = { packages: rows, aggregate: { count: totalBreaking }, ...skipped.length > 0 ? { skipped } : {} };
176415
+ formatOutput("breaking", data2, startTime, version, renderBatchBreaking);
176416
+ if (totalBreaking > 0) {
176417
+ if (!shouldRenderHuman()) {
176418
+ process.stderr.write(`${totalBreaking} breaking change${totalBreaking === 1 ? "" : "s"} found
176419
+ `);
176420
+ }
176421
+ process.exitCode = 1;
176422
+ } else if (!shouldRenderHuman()) {
176423
+ process.stderr.write(`No breaking changes
176424
+ `);
176425
+ }
176426
+ return;
176427
+ }
176323
176428
  const args = [oldPath, newPath].filter(Boolean);
176324
176429
  const { oldSpec, newSpec } = await resolveSpecs({ args, ...options });
176325
176430
  const diff = diffSpec3(oldSpec, newSpec);
@@ -176896,7 +177001,7 @@ function buildPRComment(results, pass, commit) {
176896
177001
  `);
176897
177002
  }
176898
177003
  function registerCiCommand(program) {
176899
- program.command("ci").description("Run CI checks on changed packages").option("--all", "Check all packages, not just changed ones").option("--private", "Include private packages").action(async (options) => {
177004
+ program.command("ci").description("Run CI checks on changed packages").option("--all", "Check all packages, not just changed ones").option("--private", "Include private packages").option("--min <number>", "Minimum coverage percentage (0-100)").action(async (options) => {
176900
177005
  const startTime = Date.now();
176901
177006
  const version = getVersion5();
176902
177007
  const cwd = process.cwd();
@@ -176913,7 +177018,7 @@ function registerCiCommand(program) {
176913
177018
  }
176914
177019
  if (packageDirs.length === 0)
176915
177020
  packageDirs = allDirs;
176916
- let minThreshold = config.coverage?.min ?? 0;
177021
+ let minThreshold = options.min ? Number(options.min) : config.coverage?.min ?? 0;
176917
177022
  if (minThreshold > 0 && config.coverage?.ratchet) {
176918
177023
  const ratchet = computeRatchetMin(minThreshold);
176919
177024
  minThreshold = ratchet.effectiveMin;
@@ -177371,7 +177476,7 @@ function registerExamplesCommand(program) {
177371
177476
  import { readFileSync as readFileSync23 } from "node:fs";
177372
177477
  import * as path27 from "node:path";
177373
177478
  import { fileURLToPath as fileURLToPath8 } from "node:url";
177374
- import { diffSpec as diffSpec7, categorizeBreakingChanges as categorizeBreakingChanges5 } from "@openpkg-ts/spec";
177479
+ import { diffSpec as diffSpec7, categorizeBreakingChanges as categorizeBreakingChanges5, normalize as normalize5 } from "@openpkg-ts/spec";
177375
177480
 
177376
177481
  // src/formatters/diff.ts
177377
177482
  function renderDiff(data) {
@@ -177431,10 +177536,58 @@ function getVersion8() {
177431
177536
  }
177432
177537
  }
177433
177538
  function registerDiffCommand(program) {
177434
- program.command("diff [old] [new]").description("Compare two specs and show what changed").option("--base <ref>", "Git ref for old spec").option("--head <ref>", "Git ref for new spec (default: working tree)").option("--entry <file>", "Entry file for git ref extraction").action(async (oldPath, newPath, options) => {
177539
+ program.command("diff [old] [new]").description("Compare two specs and show what changed").option("--base <ref>", "Git ref for old spec").option("--head <ref>", "Git ref for new spec (default: working tree)").option("--entry <file>", "Entry file for git ref extraction").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").action(async (oldPath, newPath, options) => {
177435
177540
  const startTime = Date.now();
177436
177541
  const version = getVersion8();
177437
177542
  try {
177543
+ const cwd = process.cwd();
177544
+ const allPackages = options.all ? discoverPackages(cwd) : null;
177545
+ const autoDetected = !options.all && options.base && !options.entry && !oldPath ? discoverPackages(cwd) : null;
177546
+ const batchPackages = allPackages ?? autoDetected;
177547
+ if (batchPackages && batchPackages.length > 0 && options.base) {
177548
+ const skipped = options.private ? [] : batchPackages.filter((p) => p.private).map((p) => p.name);
177549
+ const packages = options.private ? batchPackages : filterPublic(batchPackages);
177550
+ if (packages.length === 0) {
177551
+ formatError("diff", "No workspace packages found", startTime, version);
177552
+ return;
177553
+ }
177554
+ const rows = [];
177555
+ let totalBreaking = 0;
177556
+ let totalAdded = 0;
177557
+ let totalChanged = 0;
177558
+ for (const pkg of packages) {
177559
+ const relEntry = path27.relative(cwd, pkg.entry);
177560
+ const oldSpec2 = await extractSpecFromRef(options.base, relEntry, cwd);
177561
+ let newSpec2;
177562
+ if (options.head) {
177563
+ newSpec2 = await extractSpecFromRef(options.head, relEntry, cwd);
177564
+ } else {
177565
+ const result = await extract({ entryFile: pkg.entry });
177566
+ newSpec2 = normalize5(result.spec);
177567
+ }
177568
+ const diff2 = diffSpec7(oldSpec2, newSpec2);
177569
+ rows.push({ name: pkg.name, breaking: diff2.breaking.length, added: diff2.nonBreaking.length, changed: diff2.docsOnly.length });
177570
+ totalBreaking += diff2.breaking.length;
177571
+ totalAdded += diff2.nonBreaking.length;
177572
+ totalChanged += diff2.docsOnly.length;
177573
+ }
177574
+ const data2 = { packages: rows, aggregate: { breaking: totalBreaking, added: totalAdded, changed: totalChanged }, ...skipped.length > 0 ? { skipped } : {} };
177575
+ formatOutput("diff", data2, startTime, version, renderBatchDiff);
177576
+ if (!shouldRenderHuman()) {
177577
+ const parts = [];
177578
+ if (totalBreaking > 0)
177579
+ parts.push(`${totalBreaking} breaking`);
177580
+ if (totalAdded > 0)
177581
+ parts.push(`${totalAdded} added`);
177582
+ if (totalChanged > 0)
177583
+ parts.push(`${totalChanged} changed`);
177584
+ process.stderr.write(`${parts.length > 0 ? parts.join(", ") : "no changes"}
177585
+ `);
177586
+ }
177587
+ if (totalBreaking > 0)
177588
+ process.exitCode = 1;
177589
+ return;
177590
+ }
177438
177591
  const args = [oldPath, newPath].filter(Boolean);
177439
177592
  const { oldSpec, newSpec } = await resolveSpecs({ args, ...options });
177440
177593
  const diff = diffSpec7(oldSpec, newSpec);
@@ -177474,7 +177627,7 @@ import { readFileSync as readFileSync24 } from "node:fs";
177474
177627
  import * as path28 from "node:path";
177475
177628
  import { fileURLToPath as fileURLToPath9 } from "node:url";
177476
177629
  import { DocCov } from "@driftdev/sdk";
177477
- import { normalize as normalize4 } from "@openpkg-ts/spec";
177630
+ import { normalize as normalize6 } from "@openpkg-ts/spec";
177478
177631
 
177479
177632
  // src/formatters/extract.ts
177480
177633
  function renderExtract(data) {
@@ -177542,7 +177695,7 @@ function registerExtractCommand(program) {
177542
177695
  formatError("extract", "Failed to extract spec", startTime, version);
177543
177696
  return;
177544
177697
  }
177545
- spec = normalize4(result.spec);
177698
+ spec = normalize6(result.spec);
177546
177699
  } else {
177547
177700
  const result = await cachedExtract(entryFile);
177548
177701
  spec = result.spec;
@@ -178152,7 +178305,7 @@ import { existsSync as existsSync19, mkdirSync as mkdirSync7, readFileSync as re
178152
178305
  import * as path33 from "node:path";
178153
178306
  import { fileURLToPath as fileURLToPath14 } from "node:url";
178154
178307
  init_global();
178155
- import { normalize as normalize5 } from "@openpkg-ts/spec";
178308
+ import { normalize as normalize7 } from "@openpkg-ts/spec";
178156
178309
 
178157
178310
  // src/formatters/init.ts
178158
178311
  function renderInit(data) {
@@ -178474,7 +178627,7 @@ async function scanPackage(cwd, pkgDir) {
178474
178627
  try {
178475
178628
  const entryFile = detectEntry(absDir);
178476
178629
  const result = await extract({ entryFile });
178477
- const spec = normalize5(result.spec);
178630
+ const spec = normalize7(result.spec);
178478
178631
  const exports = spec.exports ?? [];
178479
178632
  const total = exports.length;
178480
178633
  let documented = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftdev/cli",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "Drift CLI - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",