@revturbine/cli 0.5.2 → 0.7.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 +73 -16
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3979,6 +3979,13 @@ var RevTurbineConfigSchema = z19.object({
3979
3979
  // The change set this export represents: the active change set by default,
3980
3980
  // or a specific change set when one is requested. Null for an unscoped export.
3981
3981
  change_set_id: z19.string().nullable().default(null).meta({ ...Unrestricted16, readOnly: true }),
3982
+ // Origin target identity (plan 131 TASK-10, cli.md "Target-aware, portable"):
3983
+ // stamped by the server on export so a downloaded Config File records where
3984
+ // it came from; upload tooling targets these by default and flags a tenant
3985
+ // mismatch against the session before sending. Optional — hand-authored and
3986
+ // pre-existing configs carry no target.
3987
+ tenant_id: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
3988
+ environment_id: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
3982
3989
  plans: z19.array(RevTurbineConfigPlansItemSchema).meta(Unrestricted16),
3983
3990
  // Optional for back-compat: pre-plan-88 configs (and the live export until web
3984
3991
  // adopts the new @revt-eng/schema) omit it. Add-on definitions only; pricing
@@ -5562,7 +5569,7 @@ function evaluate(graph, opts = {}) {
5562
5569
  }
5563
5570
 
5564
5571
  // src/schema/version.ts
5565
- var SCHEMA_VERSION = "0.1.107";
5572
+ var SCHEMA_VERSION = "0.1.108";
5566
5573
 
5567
5574
  // src/lib/credentials.ts
5568
5575
  import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
@@ -6024,6 +6031,26 @@ function requireSelectors(opts, positionalFiles, {
6024
6031
  return found;
6025
6032
  }
6026
6033
 
6034
+ // src/lib/target.ts
6035
+ function resolveUploadTarget(params) {
6036
+ const embedded = params.embedded ?? void 0;
6037
+ const explicit = params.explicit ?? void 0;
6038
+ if (explicit) {
6039
+ return {
6040
+ ok: true,
6041
+ tenantId: explicit,
6042
+ note: embedded && embedded !== explicit ? `retargeting deliberately: config was exported from ${embedded}, sending to ${explicit} (-t)` : void 0
6043
+ };
6044
+ }
6045
+ if (!embedded || embedded === params.session) {
6046
+ return { ok: true, tenantId: params.session };
6047
+ }
6048
+ return {
6049
+ ok: false,
6050
+ error: `this config was exported from tenant ${embedded}, but the session targets ${params.session}. Pass -t ${embedded} to send it back where it came from, or -t ${params.session} to retarget it deliberately.`
6051
+ };
6052
+ }
6053
+
6027
6054
  // src/lib/version-trail.ts
6028
6055
  function serverSchemaIsNewer(config, bundledVersion) {
6029
6056
  const server = config?.schema_version;
@@ -6086,6 +6113,17 @@ function connect(rawUrl, explicitTenantId) {
6086
6113
  }
6087
6114
  };
6088
6115
  }
6116
+ function uploadTenantFor(rawUrl, config, explicit) {
6117
+ const cred = getCredential(normalizeBaseUrl(rawUrl));
6118
+ const target = resolveUploadTarget({
6119
+ embedded: config?.tenant_id,
6120
+ explicit,
6121
+ session: cred?.tenant_id ?? "dev-tenant-001"
6122
+ });
6123
+ if (!target.ok) fail(EXIT.VALIDATION, target.error);
6124
+ if (target.note) diag(target.note);
6125
+ return target.tenantId;
6126
+ }
6089
6127
  function authHint(url, status) {
6090
6128
  if (status === 401 || status === 403) {
6091
6129
  diag(`Authentication required for ${url}. Log in with:
@@ -6526,7 +6564,7 @@ program.command("show").description(`Render a summary view of a config version.
6526
6564
  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) => {
6527
6565
  const config = verifyConfig(configFile2);
6528
6566
  if (config === null) fail(EXIT.VALIDATION, `Fix the issues above in ${configFile2}, then re-run.`);
6529
- const conn = connect(opts.url, opts.tenantId);
6567
+ const conn = connect(opts.url, uploadTenantFor(opts.url, config, opts.tenantId));
6530
6568
  diag(`Staging ${configFile2} as the open draft (${conn.url}/api/config/import) \u2026`);
6531
6569
  const { res, json } = await postJson(conn, "/api/config/import", config);
6532
6570
  if (!res.ok) {
@@ -6546,11 +6584,12 @@ program.command("upload").description("Stage a Config File as the open draft (PO
6546
6584
  });
6547
6585
  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) => {
6548
6586
  const [sel] = requireSelectors(opts, file ? [file] : [], { count: 1, allowed: ["file", "draft"], command: "launch" });
6549
- const conn = connect(opts.url, opts.tenantId);
6587
+ let conn;
6550
6588
  let playbookVersionId;
6551
6589
  if (sel.kind === "file") {
6552
6590
  const config = verifyConfig(sel.path);
6553
6591
  if (config === null) fail(EXIT.VALIDATION, `Fix the issues above in ${sel.path}, then re-run.`);
6592
+ conn = connect(opts.url, uploadTenantFor(opts.url, config, opts.tenantId));
6554
6593
  diag(`Staging ${sel.path} as the open draft \u2026`);
6555
6594
  const { res, json } = await postJson(conn, "/api/config/import", config);
6556
6595
  if (!res.ok) {
@@ -6561,6 +6600,7 @@ program.command("launch").description("Take a config live as a new Release: vali
6561
6600
  if (!playbookVersionId) fail(EXIT.SERVER, "No playbook_version_id returned by the import \u2014 cannot launch.");
6562
6601
  diag(`\u2713 Staged (${playbookVersionId})`);
6563
6602
  } else {
6603
+ conn = connect(opts.url, opts.tenantId);
6564
6604
  playbookVersionId = await requireOpenDraft(conn);
6565
6605
  }
6566
6606
  await launchDraft(conn, playbookVersionId);
@@ -6638,19 +6678,35 @@ program.command("preview").description("The open draft's staged changes (runtime
6638
6678
  if (!res.ok) httpFail(conn, "preview", res.status, json);
6639
6679
  emit(json, true);
6640
6680
  });
6641
- program.command("evaluate").description("Run the live config's placement/entitlement decisions for a user context (from a JSON file). Draft/Release-targeted evaluation lands with plan 131 TASK-7.").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).requiredOption("--user <file>", "JSON file: { user_id, customer_id?, plan_handle?, placement_ids?, entitlement_handles?, traits?, now_iso? }").option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").action(async (opts) => {
6642
- const conn = connect(opts.url, opts.tenantId);
6643
- if (!existsSync2(opts.user)) fail(EXIT.USAGE, `user file not found: ${opts.user}`);
6644
- let body;
6645
- try {
6646
- body = JSON.parse(readFileSync2(opts.user, "utf8"));
6647
- } catch (err) {
6648
- fail(EXIT.VALIDATION, `invalid JSON in ${opts.user}: ${err.message}`);
6681
+ program.command("evaluate").description("Run placement/entitlement decisions for a user context against a config version. Requires --live, --draft, or --release <id>.").option("--live", "Evaluate the live configuration").option("--draft", "Evaluate the tenant's open draft (clean-room: no suppression/cap history)").option("--release <id>", "Evaluate a past release (from its frozen snapshot)").option("--entitlement <handle>", "Check one entitlement (the checkEntitlement result)").option("--slot <id>", "Evaluate one placement/slot (the getPlacement decision)").option("--plan-handle <handle>", "Evaluate as if the user were on this plan (overrides the ctx file)").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).requiredOption("--user <file>", "JSON file: { user_id, customer_id?, plan_handle?, traits?, now_iso? }").option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").action(
6682
+ async (opts) => {
6683
+ const [sel] = requireSelectors(opts, [], { count: 1, allowed: ["draft", "live", "release"], command: "evaluate" });
6684
+ const conn = connect(opts.url, opts.tenantId);
6685
+ if (!existsSync2(opts.user)) fail(EXIT.USAGE, `user file not found: ${opts.user}`);
6686
+ let ctx;
6687
+ try {
6688
+ ctx = JSON.parse(readFileSync2(opts.user, "utf8"));
6689
+ } catch (err) {
6690
+ fail(EXIT.VALIDATION, `invalid JSON in ${opts.user}: ${err.message}`);
6691
+ }
6692
+ const body = { ...ctx };
6693
+ if (opts.planHandle) body.plan_handle = opts.planHandle;
6694
+ if (opts.entitlement && opts.slot) fail(EXIT.USAGE, "pass exactly one of --entitlement or --slot (or neither for the ctx file lists).");
6695
+ if (opts.entitlement) {
6696
+ body.entitlement_handles = [opts.entitlement];
6697
+ body.placement_ids = [];
6698
+ }
6699
+ if (opts.slot) {
6700
+ body.placement_ids = [opts.slot];
6701
+ body.entitlement_handles = [];
6702
+ }
6703
+ if (sel.kind === "release") body.playbook_version_id = sel.id;
6704
+ if (sel.kind === "draft") body.playbook_version_id = await requireOpenDraft(conn);
6705
+ const { res, json } = await postJson(conn, "/api/sdk/evaluate", body);
6706
+ if (!res.ok) httpFail(conn, "evaluate", res.status, json);
6707
+ emit(json, true);
6649
6708
  }
6650
- const { res, json } = await postJson(conn, "/api/sdk/evaluate", body);
6651
- if (!res.ok) httpFail(conn, "evaluate", res.status, json);
6652
- emit(json, true);
6653
- });
6709
+ );
6654
6710
  var COMMAND_EXAMPLES = {
6655
6711
  download: [
6656
6712
  "",
@@ -6682,7 +6738,8 @@ var COMMAND_EXAMPLES = {
6682
6738
  evaluate: [
6683
6739
  "",
6684
6740
  "Example:",
6685
- " revturbine evaluate --url <url> --user ./ctx.json",
6741
+ " revturbine evaluate --live --user ./ctx.json --entitlement seats",
6742
+ " revturbine evaluate --draft --user ./ctx.json --slot upgrade_banner --plan-handle pro",
6686
6743
  ' ctx.json: { "user_id": "u1", "plan_handle": "pro", "entitlement_handles": ["seats"] }'
6687
6744
  ].join("\n")
6688
6745
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revturbine/cli",
3
- "version": "0.5.2",
3
+ "version": "0.7.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": {