@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.
- package/dist/cli.js +40 -20
- 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
|
-
{
|
|
9391
|
-
{
|
|
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
|
-
{
|
|
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: "
|
|
9400
|
-
targets: [{ kind: "plan", id: "
|
|
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: "
|
|
9408
|
-
targets: [{ kind: "plan", id: "
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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