@myna-sh/cli 0.7.0 → 0.8.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.
package/dist/main.js CHANGED
@@ -590,12 +590,40 @@ var ManagementClient = class {
590
590
  body
591
591
  ),
592
592
  /** Field-level before/after for everything this change set would change. */
593
+ /**
594
+ * Replay the change set's edits on top of what is published now.
595
+ *
596
+ * Refuses by default when a field changed on both sides; pass
597
+ * `onConflict` to take one side, or `dryRun` to see the plan first.
598
+ */
599
+ rebase: (project, changeSet, body = {}) => this.mutate(
600
+ "POST",
601
+ `/projects/${enc(project)}/change-sets/${enc(changeSet)}/rebase`,
602
+ body
603
+ ),
593
604
  diff: (project, changeSet, signal) => this.get(
594
605
  `/projects/${enc(project)}/change-sets/${enc(changeSet)}/diff`,
595
606
  void 0,
596
607
  signal
597
608
  )
598
609
  };
610
+ // --- Releases -------------------------------------------------------------
611
+ /**
612
+ * What shipped, and how to take it back.
613
+ *
614
+ * `revert` returns an open change set rather than publishing: it goes through
615
+ * the same review, preview, and gate path as anything written by hand.
616
+ */
617
+ releases = {
618
+ list: (project, opts = {}) => this.page(`/projects/${enc(project)}/releases`, opts),
619
+ /** `release` is a release number or the published change set's id. */
620
+ get: (project, release, signal) => this.get(`/projects/${enc(project)}/releases/${enc(String(release))}`, void 0, signal),
621
+ revert: (project, release, body = {}) => this.mutate(
622
+ "POST",
623
+ `/projects/${enc(project)}/releases/${enc(String(release))}/revert`,
624
+ body
625
+ )
626
+ };
599
627
  // --- Declared external checks ---------------------------------------------
600
628
  checks = {
601
629
  list: (project, signal) => this.get(`/projects/${enc(project)}/checks`, void 0, signal),
@@ -2459,6 +2487,38 @@ function registerChanges(program) {
2459
2487
  });
2460
2488
  })
2461
2489
  );
2490
+ changes.command("rebase").description("Replay a change set's edits on top of what is published now").argument("<id>", "change set id").option("--dry-run", "show what a rebase would do without writing", false).option("--take-mine", "on conflict, keep this change set's value", false).option("--take-theirs", "on conflict, accept what was published", false).action(
2491
+ handle(async (ctx, args, opts) => {
2492
+ const project = ctx.requireProject();
2493
+ if (opts.takeMine && opts.takeTheirs) {
2494
+ throw new UsageError("Provide at most one of --take-mine or --take-theirs.");
2495
+ }
2496
+ const result = await ctx.management().changeSets.rebase(project, args[0], {
2497
+ ...opts.dryRun ? { dryRun: true } : {},
2498
+ ...opts.takeMine ? { onConflict: "take-mine" } : {},
2499
+ ...opts.takeTheirs ? { onConflict: "take-theirs" } : {}
2500
+ });
2501
+ emit(result, () => {
2502
+ if (result.items.length === 0) {
2503
+ diag(`Nothing to rebase; ${result.upToDate.length} item(s) already on the current head.`);
2504
+ return;
2505
+ }
2506
+ for (const item of result.items) {
2507
+ const label = item.collection && item.slug ? `${item.collection}/${item.slug}` : item.entryId;
2508
+ diag(`${label} \u2014 ${item.appliedPaths.length} field(s) replayed, ${item.conflicts.length} conflict(s)`);
2509
+ if (item.conflicts.length > 0) {
2510
+ table(item.conflicts, [
2511
+ { header: "FIELD", value: (c) => c.path },
2512
+ { header: "MINE", value: (c) => preview(c.mine) },
2513
+ { header: "THEIRS", value: (c) => preview(c.theirs) }
2514
+ ]);
2515
+ }
2516
+ }
2517
+ diag(result.dryRun ? "Dry run \u2014 nothing written." : "Rebased. Approvals are now stale; re-review.");
2518
+ });
2519
+ if (result.items.some((i) => i.conflicts.length > 0) && result.dryRun) process.exitCode = 1;
2520
+ })
2521
+ );
2462
2522
  changes.command("update").description("Retitle a change set or mark it ready for review").argument("<id>", "change set id").option("--title <title>", "new title").option("--description <text>", "new description").option("--ready", "mark ready for review", false).action(
2463
2523
  handle(async (ctx, args, opts) => {
2464
2524
  const project = ctx.requireProject();
@@ -2510,6 +2570,75 @@ function preview(value) {
2510
2570
  return flat.length > 48 ? `${flat.slice(0, 47)}\u2026` : flat;
2511
2571
  }
2512
2572
 
2573
+ // src/commands/releases.ts
2574
+ function registerReleases(program) {
2575
+ const releases = program.command("releases").description("Inspect what shipped and generate a revert");
2576
+ releases.command("list").description("List releases, newest first").option("--limit <n>", "page size", "25").action(
2577
+ handle(async (ctx, _args, opts) => {
2578
+ const project = ctx.requireProject();
2579
+ const page = await ctx.management().releases.list(project, { limit: Number(opts.limit) });
2580
+ emit(
2581
+ { data: page.data, nextCursor: page.nextCursor },
2582
+ () => table(page.data, [
2583
+ { header: "#", value: (r) => String(r.number) },
2584
+ { header: "TITLE", value: (r) => r.title },
2585
+ { header: "PUBLISHED", value: (r) => r.publishedAt },
2586
+ { header: "BY", value: (r) => r.publishedBy?.displayName ?? "\u2014" },
2587
+ { header: "ITEMS", value: (r) => String(r.itemCount) },
2588
+ {
2589
+ header: "STATE",
2590
+ value: (r) => r.revertedBy ? r.revertedBy.status === "published" ? `reverted by #${r.revertedBy.releaseNumber ?? "?"}` : `revert ${r.revertedBy.status}` : r.revertsReleaseNumber ? `reverts #${r.revertsReleaseNumber}` : "live"
2591
+ }
2592
+ ])
2593
+ );
2594
+ })
2595
+ );
2596
+ releases.command("inspect").description("Show one release").argument("<release>", "release number or published change set id").action(
2597
+ handle(async (ctx, args) => {
2598
+ const project = ctx.requireProject();
2599
+ const release = await ctx.management().releases.get(project, args[0]);
2600
+ emit(release, () => {
2601
+ diag(`#${release.number} \u2014 ${release.title}`);
2602
+ diag(`published ${release.publishedAt} by ${release.publishedBy?.displayName ?? "unknown"}`);
2603
+ diag(`${release.itemCount} item(s), change set ${release.id}`);
2604
+ if (release.revertsReleaseNumber) diag(`reverts release #${release.revertsReleaseNumber}`);
2605
+ if (release.revertedBy) {
2606
+ diag(`revert ${release.revertedBy.status}: change set ${release.revertedBy.changeSetId}`);
2607
+ }
2608
+ });
2609
+ })
2610
+ );
2611
+ releases.command("revert").description("Generate a change set restoring the state before a release").argument("<release>", "release number or published change set id").option("--title <title>", "title for the generated change set").option("--force", "revert resources a later release already changed", false).action(
2612
+ handle(async (ctx, args, opts) => {
2613
+ const project = ctx.requireProject();
2614
+ const result = await ctx.management().releases.revert(project, args[0], {
2615
+ ...opts.title ? { title: opts.title } : {},
2616
+ ...opts.force ? { force: true } : {}
2617
+ });
2618
+ emit(result, () => {
2619
+ diag(`Created change set ${result.changeSet.id} \u2014 ${result.changeSet.title}`);
2620
+ table(result.reverted, [
2621
+ { header: "OP", value: (r) => r.operation },
2622
+ {
2623
+ header: "RESOURCE",
2624
+ value: (r) => r.collection && r.slug ? `${r.collection}/${r.slug}` : r.resourceId
2625
+ },
2626
+ { header: "RESTORES", value: (r) => r.restoredFromRevisionId ?? "\u2014" }
2627
+ ]);
2628
+ if (result.skipped.length > 0) {
2629
+ diag(`${result.skipped.length} resource(s) skipped:`);
2630
+ for (const s of result.skipped) {
2631
+ const label = s.collection && s.slug ? `${s.collection}/${s.slug}` : s.resourceId;
2632
+ process.stderr.write(` ${label} \u2014 ${s.reason}: ${s.message}
2633
+ `);
2634
+ }
2635
+ }
2636
+ diag(`Not published. Review, then: myna changes publish ${result.changeSet.id} --confirm-publish`);
2637
+ });
2638
+ })
2639
+ );
2640
+ }
2641
+
2513
2642
  // src/commands/previews.ts
2514
2643
  function previewTarget(ref) {
2515
2644
  if (ref.startsWith("chs_")) return { changeSetId: ref };
@@ -3334,7 +3463,7 @@ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync a
3334
3463
  import { join as join6 } from "path";
3335
3464
 
3336
3465
  // src/version.ts
3337
- var VERSION = true ? "0.7.0" : "0.0.0-dev";
3466
+ var VERSION = true ? "0.8.0" : "0.0.0-dev";
3338
3467
 
3339
3468
  // src/commands/doctor.ts
3340
3469
  var NPM_REGISTRY = "https://registry.npmjs.org";
@@ -3856,6 +3985,7 @@ function buildProgram() {
3856
3985
  registerEntries(program);
3857
3986
  registerSync(program);
3858
3987
  registerChanges(program);
3988
+ registerReleases(program);
3859
3989
  registerPreviews(program);
3860
3990
  registerAssets(program);
3861
3991
  registerAdmin(program);