@revturbine/cli 0.11.0 → 0.13.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/cli.js +40 -20
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9041,13 +9041,21 @@ function formatDiff(diff) {
9041
9041
  return lines.join("\n");
9042
9042
  }
9043
9043
 
9044
+ // src/lib/prune.ts
9045
+ function pruneQuery(prune) {
9046
+ if (prune === false) return "?prune=false";
9047
+ if (prune === true) return "?prune=force";
9048
+ return "";
9049
+ }
9050
+
9044
9051
  // src/lib/config-validate.ts
9045
9052
  var BLOCKING_SEVERITIES = /* @__PURE__ */ new Set(["error_draft", "error_launch"]);
9046
- function isBlockingFinding(finding2) {
9053
+ function isBlockingFinding(finding2, force = false) {
9054
+ if (force && finding2.severity === "error_launch") return false;
9047
9055
  return BLOCKING_SEVERITIES.has(finding2.severity);
9048
9056
  }
9049
- function hasBlockingFindings(findings) {
9050
- return findings.some(isBlockingFinding);
9057
+ function hasBlockingFindings(findings, force = false) {
9058
+ return findings.some((f) => isBlockingFinding(f, force));
9051
9059
  }
9052
9060
  function formatFindings(findings) {
9053
9061
  if (findings.length === 0) return "No validation findings.";
@@ -9387,25 +9395,30 @@ var STARTER_PLAYBOOK = {
9387
9395
  tenant_id: "local",
9388
9396
  environment_id: "local",
9389
9397
  plans: [
9390
- { id: "plan_free", unique_handle: "free", name: "Free", tier_position: 0, sort_order: 0 },
9391
- { id: "plan_pro", unique_handle: "pro", name: "Pro", tier_position: 1, sort_order: 1 }
9398
+ { unique_handle: "free", name: "Free", tier_position: 0, sort_order: 0 },
9399
+ { unique_handle: "pro", name: "Pro", tier_position: 1, sort_order: 1 }
9392
9400
  ],
9393
9401
  entitlements: [
9394
- { id: "ent_advanced_export", unique_handle: "advanced_export", name: "Advanced Export", type: "feature" }
9402
+ { unique_handle: "advanced_export", name: "Advanced Export", type: "feature" }
9395
9403
  ],
9404
+ // Rules reference entities by their HANDLE, never a synthetic id: the import
9405
+ // stores these references verbatim and the runtime resolves them by handle
9406
+ // (entitlement-check.ts matches rule.entitlement_id / plan targets against
9407
+ // unique_handle). Authoring `ent_*` / `plan_*` ids here would produce rules
9408
+ // that never link — the exact bug the plan-154 dogfood surfaced.
9396
9409
  entitlement_rules: [
9397
9410
  {
9398
9411
  id: "er_advanced_export_free",
9399
- entitlement_id: "ent_advanced_export",
9400
- targets: [{ kind: "plan", id: "plan_free" }],
9412
+ entitlement_id: "advanced_export",
9413
+ targets: [{ kind: "plan", id: "free" }],
9401
9414
  segment_ids: [],
9402
9415
  enabled: false,
9403
9416
  current_usage: 0
9404
9417
  },
9405
9418
  {
9406
9419
  id: "er_advanced_export_pro",
9407
- entitlement_id: "ent_advanced_export",
9408
- targets: [{ kind: "plan", id: "plan_pro" }],
9420
+ entitlement_id: "advanced_export",
9421
+ targets: [{ kind: "plan", id: "pro" }],
9409
9422
  segment_ids: [],
9410
9423
  enabled: true,
9411
9424
  current_usage: 0
@@ -9733,17 +9746,22 @@ async function loadVersion(conn, sel) {
9733
9746
  return downloadConfig(conn, await requireOpenDraft(conn));
9734
9747
  }
9735
9748
  }
9736
- async function launchDraft(conn, playbookVersionId) {
9749
+ async function launchDraft(conn, playbookVersionId, force = false) {
9737
9750
  diag(`Launch gate: validating draft ${playbookVersionId} \u2026`);
9738
9751
  const validation = await fetchValidation(conn.url, playbookVersionId, conn.headers);
9739
9752
  if (!validation.ok) httpFail(conn, "validate", validation.status, validation.error);
9740
9753
  if (validation.findings.length > 0) diagRaw(formatFindings(validation.findings));
9741
- if (hasBlockingFindings(validation.findings)) {
9754
+ if (hasBlockingFindings(validation.findings, force)) {
9742
9755
  fail(EXIT.VALIDATION, "blocking findings \u2014 this draft cannot launch.");
9743
9756
  }
9757
+ const overridden = force ? validation.findings.filter((f) => f.severity === "error_launch") : [];
9758
+ if (overridden.length > 0) {
9759
+ diag(`--force: overriding ${overridden.length} launch-gate finding(s) (error_launch).`);
9760
+ }
9744
9761
  diag(`Launching playbook version ${playbookVersionId} (submit \u2192 approve \u2192 deploy) \u2026`);
9745
9762
  for (const step of ["submit", "approve", "deploy"]) {
9746
- const { res, json } = await postJson(conn, `/api/playbook-versions/${playbookVersionId}/${step}`, {});
9763
+ const body = step === "deploy" ? { force } : {};
9764
+ const { res, json } = await postJson(conn, `/api/playbook-versions/${playbookVersionId}/${step}`, body);
9747
9765
  if (!res.ok) {
9748
9766
  diag(`\u2717 ${step} failed (${res.status}). The draft is staged but NOT live.`);
9749
9767
  diagRaw(` ${JSON.stringify(json)}`);
@@ -10266,12 +10284,14 @@ program.command("show").description(`Render a summary view of a config version.
10266
10284
  emit(data, Boolean(opts.json), text);
10267
10285
  }
10268
10286
  );
10269
- program.command("upload").description("Stage a Config File as the open draft (POST /api/config/import).").argument("<config>", "Path to a Config File").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").action(async (configFile2, opts) => {
10287
+ var PRUNE_OPTION = ["--prune", "Confirm a convergent delete past the mass-deletion guard (removes entities absent from the file)"];
10288
+ var NO_PRUNE_OPTION = ["--no-prune", "Additive import \u2014 keep entities that are absent from the file (disables convergent delete)"];
10289
+ program.command("upload").description("Stage a Config File as the open draft (POST /api/config/import).").argument("<config>", "Path to a Config File").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option(...PRUNE_OPTION).option(...NO_PRUNE_OPTION).action(async (configFile2, opts) => {
10270
10290
  const config = verifyConfig(configFile2);
10271
10291
  if (config === null) fail(EXIT.VALIDATION, `Fix the issues above in ${configFile2}, then re-run.`);
10272
10292
  const conn = connect(opts.url, uploadTenantFor(opts.url, config, opts.tenantId));
10273
10293
  diag(`Staging ${configFile2} as the open draft (${conn.url}/api/config/import) \u2026`);
10274
- const { res, json } = await postJson(conn, "/api/config/import", config);
10294
+ const { res, json } = await postJson(conn, `/api/config/import${pruneQuery(opts.prune)}`, config);
10275
10295
  if (!res.ok) {
10276
10296
  if (json && typeof json === "object" && json.stage) {
10277
10297
  diagRaw(` stage: ${json.stage}`);
@@ -10287,7 +10307,7 @@ program.command("upload").description("Stage a Config File as the open draft (PO
10287
10307
  if (playbookVersionId) diagRaw(` playbook_version_id: ${playbookVersionId}`);
10288
10308
  diag("Launch it with `revturbine launch --draft`, or from the UI (Drafts & Releases).");
10289
10309
  });
10290
- program.command("launch").description("Take a config live as a new Release: validate (launch gate), then submit \u2192 approve \u2192 deploy. Synchronous.").argument("[file]", "Config File to upload and launch directly").option("--draft", "Launch the already-open draft").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--yes", "Accepted for parity with discard/restore; launch has no confirmation prompt").action(async (file, opts) => {
10310
+ program.command("launch").description("Take a config live as a new Release: validate (launch gate), then submit \u2192 approve \u2192 deploy. Synchronous.").argument("[file]", "Config File to upload and launch directly").option("--draft", "Launch the already-open draft").option("--force", "Launch past incomplete-but-valid findings (error_launch); structural errors (error_draft) still block").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option(...PRUNE_OPTION).option(...NO_PRUNE_OPTION).option("--yes", "Accepted for parity with discard/restore; launch has no confirmation prompt").action(async (file, opts) => {
10291
10311
  const [sel] = requireSelectors(opts, file ? [file] : [], { count: 1, allowed: ["file", "draft"], command: "launch" });
10292
10312
  let conn;
10293
10313
  let playbookVersionId;
@@ -10296,7 +10316,7 @@ program.command("launch").description("Take a config live as a new Release: vali
10296
10316
  if (config === null) fail(EXIT.VALIDATION, `Fix the issues above in ${sel.path}, then re-run.`);
10297
10317
  conn = connect(opts.url, uploadTenantFor(opts.url, config, opts.tenantId));
10298
10318
  diag(`Staging ${sel.path} as the open draft \u2026`);
10299
- const { res, json } = await postJson(conn, "/api/config/import", config);
10319
+ const { res, json } = await postJson(conn, `/api/config/import${pruneQuery(opts.prune)}`, config);
10300
10320
  if (!res.ok) {
10301
10321
  diagRaw(` ${JSON.stringify(json)}`);
10302
10322
  httpFail(conn, "upload", res.status);
@@ -10308,7 +10328,7 @@ program.command("launch").description("Take a config live as a new Release: vali
10308
10328
  conn = connect(opts.url, opts.tenantId);
10309
10329
  playbookVersionId = await requireOpenDraft(conn);
10310
10330
  }
10311
- await launchDraft(conn, playbookVersionId);
10331
+ await launchDraft(conn, playbookVersionId, !!opts.force);
10312
10332
  });
10313
10333
  program.command("discard").description("Discard (archive) the open draft so a fresh one can start.").argument("[playbook-version-id]", "A specific draft id (default: the open draft)").option("--draft", "The open draft (explicit form of the default)").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--yes", "Skip the confirmation prompt").action(async (id, opts) => {
10314
10334
  const conn = connect(opts.url, opts.tenantId);
@@ -10318,7 +10338,7 @@ program.command("discard").description("Discard (archive) the open draft so a fr
10318
10338
  if (!res.ok) httpFail(conn, "discard", res.status, json);
10319
10339
  diag(`\u2713 Discarded draft ${playbookVersionId}.`);
10320
10340
  });
10321
- program.command("restore").description("Stage a draft that restores a past release (from its frozen snapshot); `--launch` takes it live. Halts if a draft is already open.").argument("<playbook-version-id>", "The deployed playbook version to restore (see `history`)").option("--launch", "Launch the restoring draft immediately (gate + submit \u2192 approve \u2192 deploy)").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--yes", "Skip the confirmation prompt").action(async (playbookVersionId, opts) => {
10341
+ program.command("restore").description("Stage a draft that restores a past release (from its frozen snapshot); `--launch` takes it live. Halts if a draft is already open.").argument("<playbook-version-id>", "The deployed playbook version to restore (see `history`)").option("--launch", "Launch the restoring draft immediately (gate + submit \u2192 approve \u2192 deploy)").option("--force", "With --launch, launch past incomplete-but-valid findings (error_launch); error_draft still blocks").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--yes", "Skip the confirmation prompt").action(async (playbookVersionId, opts) => {
10322
10342
  const conn = connect(opts.url, opts.tenantId);
10323
10343
  await confirmOrExit(
10324
10344
  `Restore playbook version ${playbookVersionId} on ${conn.url}?${opts.launch ? " --launch will take it LIVE." : " (stages a draft; launch separately)"}`,
@@ -10333,7 +10353,7 @@ program.command("restore").description("Stage a draft that restores a past relea
10333
10353
  return;
10334
10354
  }
10335
10355
  if (!reverting) fail(EXIT.SERVER, "No reverting draft id returned \u2014 cannot launch.");
10336
- await launchDraft(conn, reverting);
10356
+ await launchDraft(conn, reverting, !!opts.force);
10337
10357
  });
10338
10358
  program.command("status").description("The current live Release and the open draft, side by side.").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--json", "Machine-readable output").action(async (opts) => {
10339
10359
  const conn = connect(opts.url, opts.tenantId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revturbine/cli",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "revturbine — verify RevTurbine ExportedConfig files and ship them to a RevTurbine instance through the Change Set lifecycle.",
5
5
  "license": "MIT",
6
6
  "repository": {