@revturbine/cli 0.5.0 → 0.5.1
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 +359 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { createRequire } from "module";
|
|
|
7
7
|
import { createInterface } from "readline/promises";
|
|
8
8
|
import { spawn as spawn2 } from "child_process";
|
|
9
9
|
import { Command, CommanderError, Option } from "commander";
|
|
10
|
-
import { z as
|
|
10
|
+
import { z as z29 } from "zod";
|
|
11
11
|
|
|
12
12
|
// src/schema/exported-config.snapshot.mjs
|
|
13
13
|
import { z as z2 } from "zod";
|
|
@@ -5223,6 +5223,344 @@ var AuthSsoProviderSchema = IdField.extend({
|
|
|
5223
5223
|
domain: z26.string().min(1).meta(Unrestricted23)
|
|
5224
5224
|
}).meta({ id: "AuthSsoProvider", "x-revturbine-schema-persistence": Persisted23, "x-revturbine-schema-exposure": Internal19 });
|
|
5225
5225
|
|
|
5226
|
+
// src/schema/validators.snapshot.mjs
|
|
5227
|
+
import { z as z27 } from "zod";
|
|
5228
|
+
import { z as z28 } from "zod";
|
|
5229
|
+
var SeveritySchema2 = z27.enum([
|
|
5230
|
+
"error_draft",
|
|
5231
|
+
"error_launch",
|
|
5232
|
+
"warning",
|
|
5233
|
+
"ai_check"
|
|
5234
|
+
]);
|
|
5235
|
+
var CallSiteSchema = z27.enum([
|
|
5236
|
+
"studio",
|
|
5237
|
+
// inline studio editing / modal commit
|
|
5238
|
+
"publish",
|
|
5239
|
+
// the launch gate (literal kept as 'publish' — see NOTE above)
|
|
5240
|
+
"ingestion",
|
|
5241
|
+
// external ingestion — CLI / MCP / agents
|
|
5242
|
+
"compile"
|
|
5243
|
+
// compile / activate backstop
|
|
5244
|
+
]);
|
|
5245
|
+
var TargetRefSchema = z27.object({
|
|
5246
|
+
object_type: z27.string().optional(),
|
|
5247
|
+
object_id: z27.string().optional(),
|
|
5248
|
+
field: z27.string().optional(),
|
|
5249
|
+
/** Studio key the UI resolves a deep-link against. */
|
|
5250
|
+
studio: z27.string().optional(),
|
|
5251
|
+
/** A free-form deep-link target the UI can resolve into a URL. */
|
|
5252
|
+
href: z27.string().optional(),
|
|
5253
|
+
/** Zod issue path, for structural-tier findings. */
|
|
5254
|
+
path: z27.array(z27.union([z27.string(), z27.number()])).optional()
|
|
5255
|
+
});
|
|
5256
|
+
var ValidationFindingSchema = z27.object({
|
|
5257
|
+
/** Stable rule ID (e.g. `VAL-PLN-01`); for schema findings, the Zod issue code. */
|
|
5258
|
+
code: z27.string(),
|
|
5259
|
+
severity: SeveritySchema2,
|
|
5260
|
+
targetRef: TargetRefSchema,
|
|
5261
|
+
/** Single user-facing message — same string at the gate, modal, and CLI. */
|
|
5262
|
+
message: z27.string(),
|
|
5263
|
+
/** Pointer to the originating spec rule. Beta inspection aid — remove post-beta. */
|
|
5264
|
+
specRef: z27.string().optional(),
|
|
5265
|
+
/** Optional extended context (preserved from the old `ValidationIssue.detail`). */
|
|
5266
|
+
detail: z27.string().optional(),
|
|
5267
|
+
/**
|
|
5268
|
+
* Set by `evaluate` when a `focus` is supplied and this finding touches the
|
|
5269
|
+
* focused object — surfaces use it to spotlight inline. Never narrows what is
|
|
5270
|
+
* checked (spec §3.1).
|
|
5271
|
+
*/
|
|
5272
|
+
spotlight: z27.boolean().optional()
|
|
5273
|
+
});
|
|
5274
|
+
var CATALOG = {
|
|
5275
|
+
// no-Stripe-price. Interim `warning` (does not block): a plan price that has
|
|
5276
|
+
// all its billing info but is entered statically rather than synced from
|
|
5277
|
+
// Stripe is valid to launch — it should warn, not block. The stricter
|
|
5278
|
+
// three-way model (block if billing info is INCOMPLETE; warn if complete-but-
|
|
5279
|
+
// static; pass if complete-and-Stripe-connected) is a tracked follow-up
|
|
5280
|
+
// (static-pricing plan). Spec §5.3's blanket error_launch is superseded by
|
|
5281
|
+
// that follow-up; see the plan.
|
|
5282
|
+
"VAL-PLN-01": {
|
|
5283
|
+
id: "VAL-PLN-01",
|
|
5284
|
+
severity: "warning",
|
|
5285
|
+
message: "Plan '{plan}' has no Stripe price linked.",
|
|
5286
|
+
specRef: "optimization-studio-ui.md \xA73.1; plans-entitlements-studio-ui.md \xA72.2"
|
|
5287
|
+
},
|
|
5288
|
+
// entitlement-rule overlap. Origin: the `rule.overlap` check (plan 40).
|
|
5289
|
+
"VAL-PLN-05": {
|
|
5290
|
+
id: "VAL-PLN-05",
|
|
5291
|
+
severity: "warning",
|
|
5292
|
+
message: "These entitlement rules target the same plan and segment \u2014 only one will apply.",
|
|
5293
|
+
specRef: "config-validation.md \xA75.3 (provisional \u2014 plan 73 Q-1)"
|
|
5294
|
+
},
|
|
5295
|
+
// multiple public variations. Origin: `*_variation.multiple_public`.
|
|
5296
|
+
"VAL-PLN-06": {
|
|
5297
|
+
id: "VAL-PLN-06",
|
|
5298
|
+
severity: "error_draft",
|
|
5299
|
+
message: "Only one public variation is allowed per billing period and segment.",
|
|
5300
|
+
specRef: "config-validation.md \xA75.3 (provisional \u2014 plan 73 Q-1)"
|
|
5301
|
+
}
|
|
5302
|
+
};
|
|
5303
|
+
function getCatalogEntry(id) {
|
|
5304
|
+
return CATALOG[id];
|
|
5305
|
+
}
|
|
5306
|
+
function runSemanticRules(graph) {
|
|
5307
|
+
return [
|
|
5308
|
+
...checkPlansHaveStripePrice(graph),
|
|
5309
|
+
...checkRuleOverlaps(graph),
|
|
5310
|
+
...checkPublicVariationCollisions(graph)
|
|
5311
|
+
];
|
|
5312
|
+
}
|
|
5313
|
+
function finding(catalogId, target, opts = {}) {
|
|
5314
|
+
const entry = getCatalogEntry(catalogId);
|
|
5315
|
+
const severity = entry?.severity ?? "warning";
|
|
5316
|
+
return {
|
|
5317
|
+
code: catalogId,
|
|
5318
|
+
severity,
|
|
5319
|
+
targetRef: target,
|
|
5320
|
+
message: opts.message ?? entry?.message ?? catalogId,
|
|
5321
|
+
...entry?.specRef ? { specRef: entry.specRef } : {},
|
|
5322
|
+
...opts.detail ? { detail: opts.detail } : {}
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
5325
|
+
function checkPlansHaveStripePrice(graph) {
|
|
5326
|
+
const plans = graph.plans;
|
|
5327
|
+
if (!plans?.length) return [];
|
|
5328
|
+
const plansWithVariations = /* @__PURE__ */ new Set();
|
|
5329
|
+
const pricedPlanIds = /* @__PURE__ */ new Set();
|
|
5330
|
+
for (const v of graph.plan_variations ?? []) {
|
|
5331
|
+
const planId = String(v.plan_id ?? "");
|
|
5332
|
+
if (!planId) continue;
|
|
5333
|
+
plansWithVariations.add(planId);
|
|
5334
|
+
const priceId = v.stripe_price_id;
|
|
5335
|
+
if (typeof priceId === "string" && priceId.length > 0) pricedPlanIds.add(planId);
|
|
5336
|
+
}
|
|
5337
|
+
const findings = [];
|
|
5338
|
+
for (const row of plans) {
|
|
5339
|
+
const planKeys = [String(row.handle ?? ""), String(row.id ?? "")].filter(Boolean);
|
|
5340
|
+
const hasVariations = planKeys.some((k) => plansWithVariations.has(k));
|
|
5341
|
+
if (!hasVariations) continue;
|
|
5342
|
+
const hasStripePrice = planKeys.some((k) => pricedPlanIds.has(k));
|
|
5343
|
+
if (hasStripePrice) continue;
|
|
5344
|
+
const id = String(row.handle ?? row.id ?? "");
|
|
5345
|
+
const name = String(row.name ?? id);
|
|
5346
|
+
findings.push(
|
|
5347
|
+
finding(
|
|
5348
|
+
"VAL-PLN-01",
|
|
5349
|
+
{
|
|
5350
|
+
object_type: "plan",
|
|
5351
|
+
object_id: id,
|
|
5352
|
+
field: "plan_variations",
|
|
5353
|
+
studio: "plans-entitlements"
|
|
5354
|
+
},
|
|
5355
|
+
{
|
|
5356
|
+
message: `Plan '${name}' has no Stripe price linked.`,
|
|
5357
|
+
detail: "Link a Plan Variation to a Stripe Price before activating the plan for paid customers."
|
|
5358
|
+
}
|
|
5359
|
+
)
|
|
5360
|
+
);
|
|
5361
|
+
}
|
|
5362
|
+
return findings;
|
|
5363
|
+
}
|
|
5364
|
+
function checkRuleOverlaps(graph) {
|
|
5365
|
+
const rules = graph.entitlement_rules;
|
|
5366
|
+
if (!rules?.length) return [];
|
|
5367
|
+
const segmentDimensions = /* @__PURE__ */ new Map();
|
|
5368
|
+
for (const seg of graph.segments ?? []) {
|
|
5369
|
+
const segId = String(seg.handle ?? seg.id ?? "");
|
|
5370
|
+
const dim = typeof seg.dimension_id === "string" ? seg.dimension_id : "";
|
|
5371
|
+
if (segId) segmentDimensions.set(segId, dim);
|
|
5372
|
+
}
|
|
5373
|
+
const sigs = [];
|
|
5374
|
+
for (const rule of rules) {
|
|
5375
|
+
const id = String(rule.handle ?? rule.id ?? "");
|
|
5376
|
+
const targetIds = /* @__PURE__ */ new Set();
|
|
5377
|
+
if (Array.isArray(rule.targets)) {
|
|
5378
|
+
for (const t of rule.targets) {
|
|
5379
|
+
if (t && typeof t === "object") {
|
|
5380
|
+
const obj = t;
|
|
5381
|
+
if (typeof obj.id === "string") {
|
|
5382
|
+
const kind = typeof obj.kind === "string" ? obj.kind : "plan";
|
|
5383
|
+
targetIds.add(`${kind}:${obj.id}`);
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
} else if (Array.isArray(rule.plan_ids)) {
|
|
5388
|
+
for (const pid of rule.plan_ids) {
|
|
5389
|
+
if (typeof pid === "string") targetIds.add(`plan:${pid}`);
|
|
5390
|
+
}
|
|
5391
|
+
} else if (rule.plan_id !== void 0 && rule.plan_id !== null) {
|
|
5392
|
+
targetIds.add(`plan:${String(rule.plan_id)}`);
|
|
5393
|
+
}
|
|
5394
|
+
const segmentIds = Array.isArray(rule.segment_ids) ? rule.segment_ids.filter((s) => typeof s === "string") : [];
|
|
5395
|
+
const segmentPairs = /* @__PURE__ */ new Set();
|
|
5396
|
+
for (const segId of segmentIds) {
|
|
5397
|
+
const dim = segmentDimensions.get(segId) ?? "";
|
|
5398
|
+
segmentPairs.add(`${dim}::${segId}`);
|
|
5399
|
+
}
|
|
5400
|
+
sigs.push({
|
|
5401
|
+
rule,
|
|
5402
|
+
id,
|
|
5403
|
+
targetIds,
|
|
5404
|
+
segmentPairs,
|
|
5405
|
+
matchesAllSegments: segmentIds.length === 0
|
|
5406
|
+
});
|
|
5407
|
+
}
|
|
5408
|
+
const overlapping = /* @__PURE__ */ new Set();
|
|
5409
|
+
for (let i = 0; i < sigs.length; i++) {
|
|
5410
|
+
for (let j = i + 1; j < sigs.length; j++) {
|
|
5411
|
+
const a = sigs[i];
|
|
5412
|
+
const b = sigs[j];
|
|
5413
|
+
let sharedTarget = false;
|
|
5414
|
+
for (const t of a.targetIds) {
|
|
5415
|
+
if (b.targetIds.has(t)) {
|
|
5416
|
+
sharedTarget = true;
|
|
5417
|
+
break;
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
if (!sharedTarget) continue;
|
|
5421
|
+
let overlaps = false;
|
|
5422
|
+
if (a.matchesAllSegments || b.matchesAllSegments) {
|
|
5423
|
+
overlaps = true;
|
|
5424
|
+
} else {
|
|
5425
|
+
for (const pair of a.segmentPairs) {
|
|
5426
|
+
if (b.segmentPairs.has(pair)) {
|
|
5427
|
+
overlaps = true;
|
|
5428
|
+
break;
|
|
5429
|
+
}
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
if (overlaps) {
|
|
5433
|
+
overlapping.add(i);
|
|
5434
|
+
overlapping.add(j);
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
const findings = [];
|
|
5439
|
+
for (const idx of overlapping) {
|
|
5440
|
+
const sig = sigs[idx];
|
|
5441
|
+
const name = String(sig.rule.name ?? sig.id);
|
|
5442
|
+
findings.push(
|
|
5443
|
+
finding(
|
|
5444
|
+
"VAL-PLN-05",
|
|
5445
|
+
{
|
|
5446
|
+
object_type: "entitlement_rule",
|
|
5447
|
+
object_id: sig.id,
|
|
5448
|
+
studio: "plans-entitlements"
|
|
5449
|
+
},
|
|
5450
|
+
{
|
|
5451
|
+
message: `Entitlement rule '${name}' overlaps another rule on a shared target and segment \u2014 only one will apply.`,
|
|
5452
|
+
detail: "Where rules overlap, the most permissive value applies (plans-entitlements-studio-ui.md \xA72.3.2). Adjust the segment selection if this is unintended."
|
|
5453
|
+
}
|
|
5454
|
+
)
|
|
5455
|
+
);
|
|
5456
|
+
}
|
|
5457
|
+
return findings;
|
|
5458
|
+
}
|
|
5459
|
+
function checkPublicVariationCollisions(graph) {
|
|
5460
|
+
return [
|
|
5461
|
+
...collectPublicCollisions(graph.plan_variations, "plan_variation", "plan_id"),
|
|
5462
|
+
...collectPublicCollisions(graph.addon_variations, "addon_variation", "addon_id")
|
|
5463
|
+
];
|
|
5464
|
+
}
|
|
5465
|
+
function collectPublicCollisions(rows, objectType, parentField) {
|
|
5466
|
+
if (!rows?.length) return [];
|
|
5467
|
+
const publicByTuple = /* @__PURE__ */ new Map();
|
|
5468
|
+
for (const row of rows) {
|
|
5469
|
+
if (row.is_current === false) continue;
|
|
5470
|
+
if (String(row.visibility ?? "public") !== "public") continue;
|
|
5471
|
+
const tenant = String(row.tenant_id ?? "");
|
|
5472
|
+
const parent = String(row[parentField] ?? "");
|
|
5473
|
+
const period = String(row.billing_period ?? "");
|
|
5474
|
+
const segment = row.segment_id == null ? "_default" : String(row.segment_id);
|
|
5475
|
+
const key = `${tenant} ${parent} ${period} ${segment}`;
|
|
5476
|
+
const id = String(row.handle ?? row.id ?? "");
|
|
5477
|
+
const bucket = publicByTuple.get(key);
|
|
5478
|
+
if (bucket) bucket.ids.push(id);
|
|
5479
|
+
else publicByTuple.set(key, { ids: [id], parent, period, segment });
|
|
5480
|
+
}
|
|
5481
|
+
const findings = [];
|
|
5482
|
+
for (const bucket of publicByTuple.values()) {
|
|
5483
|
+
if (bucket.ids.length < 2) continue;
|
|
5484
|
+
const allIds = [...bucket.ids].sort();
|
|
5485
|
+
const segmentLabel = bucket.segment === "_default" ? "no segment" : `segment ${bucket.segment}`;
|
|
5486
|
+
const parentLabel = parentField === "plan_id" ? "plan" : "add-on";
|
|
5487
|
+
for (const id of bucket.ids) {
|
|
5488
|
+
findings.push(
|
|
5489
|
+
finding(
|
|
5490
|
+
"VAL-PLN-06",
|
|
5491
|
+
{
|
|
5492
|
+
object_type: objectType,
|
|
5493
|
+
object_id: id,
|
|
5494
|
+
field: "visibility",
|
|
5495
|
+
studio: "plans-entitlements"
|
|
5496
|
+
},
|
|
5497
|
+
{
|
|
5498
|
+
message: `Variation '${id}' is one of ${bucket.ids.length} marked Public for the same ${parentLabel}, billing period '${bucket.period}', and ${segmentLabel} \u2014 only one may be Public.`,
|
|
5499
|
+
detail: `Set all but one of [${allIds.join(", ")}] to Unlisted or Legacy before deploying.`
|
|
5500
|
+
}
|
|
5501
|
+
)
|
|
5502
|
+
);
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
return findings;
|
|
5506
|
+
}
|
|
5507
|
+
function fieldLabel(path3) {
|
|
5508
|
+
if (!path3 || path3.length === 0) return "This value";
|
|
5509
|
+
return String(path3[path3.length - 1]);
|
|
5510
|
+
}
|
|
5511
|
+
function messageForZodIssue(issue) {
|
|
5512
|
+
const field = fieldLabel(issue.path);
|
|
5513
|
+
switch (issue.code) {
|
|
5514
|
+
case "invalid_type":
|
|
5515
|
+
return issue.input === void 0 ? `${field} is required.` : `${field} must be a ${issue.expected ?? "valid value"}.`;
|
|
5516
|
+
case "invalid_value":
|
|
5517
|
+
return `${field} must be one of the allowed values.`;
|
|
5518
|
+
case "invalid_format":
|
|
5519
|
+
return `${field} isn't in a valid format.`;
|
|
5520
|
+
case "too_small":
|
|
5521
|
+
return `${field} is too small.`;
|
|
5522
|
+
case "too_big":
|
|
5523
|
+
return `${field} is too large.`;
|
|
5524
|
+
case "unrecognized_keys":
|
|
5525
|
+
return `${field} isn't a recognized setting.`;
|
|
5526
|
+
default:
|
|
5527
|
+
return issue.message ?? `${field} is invalid.`;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
function targetRefForIssue(issue, basePath) {
|
|
5531
|
+
const path3 = [...basePath, ...issue.path ?? []].map(
|
|
5532
|
+
(seg) => typeof seg === "number" ? seg : String(seg)
|
|
5533
|
+
);
|
|
5534
|
+
return { path: path3 };
|
|
5535
|
+
}
|
|
5536
|
+
function zodErrorToFindings(error, opts = {}) {
|
|
5537
|
+
const basePath = opts.basePath ?? [];
|
|
5538
|
+
return error.issues.map((issue) => ({
|
|
5539
|
+
code: issue.code ?? "invalid",
|
|
5540
|
+
severity: "error_draft",
|
|
5541
|
+
targetRef: targetRefForIssue(issue, basePath),
|
|
5542
|
+
message: messageForZodIssue(issue),
|
|
5543
|
+
...opts.specRef ? { specRef: opts.specRef } : {}
|
|
5544
|
+
}));
|
|
5545
|
+
}
|
|
5546
|
+
function spotlights(finding2, focus) {
|
|
5547
|
+
if (!focus) return false;
|
|
5548
|
+
const { object_type, object_id } = finding2.targetRef;
|
|
5549
|
+
if (focus.object_id != null && object_id !== focus.object_id) return false;
|
|
5550
|
+
if (focus.object_type != null && object_type !== focus.object_type) return false;
|
|
5551
|
+
return focus.object_id != null || focus.object_type != null;
|
|
5552
|
+
}
|
|
5553
|
+
function evaluate(graph, opts = {}) {
|
|
5554
|
+
const findings = [];
|
|
5555
|
+
if (opts.structuralErrors) {
|
|
5556
|
+
const errors = Array.isArray(opts.structuralErrors) ? opts.structuralErrors : [opts.structuralErrors];
|
|
5557
|
+
for (const error of errors) findings.push(...zodErrorToFindings(error));
|
|
5558
|
+
}
|
|
5559
|
+
findings.push(...runSemanticRules(graph));
|
|
5560
|
+
if (!opts.focus) return findings;
|
|
5561
|
+
return findings.map((f) => spotlights(f, opts.focus) ? { ...f, spotlight: true } : f);
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5226
5564
|
// src/schema/version.ts
|
|
5227
5565
|
var SCHEMA_VERSION = "0.1.107";
|
|
5228
5566
|
|
|
@@ -5556,8 +5894,8 @@ function formatDiff(diff) {
|
|
|
5556
5894
|
|
|
5557
5895
|
// src/lib/config-validate.ts
|
|
5558
5896
|
var BLOCKING_SEVERITIES = /* @__PURE__ */ new Set(["error_draft", "error_launch"]);
|
|
5559
|
-
function isBlockingFinding(
|
|
5560
|
-
return BLOCKING_SEVERITIES.has(
|
|
5897
|
+
function isBlockingFinding(finding2) {
|
|
5898
|
+
return BLOCKING_SEVERITIES.has(finding2.severity);
|
|
5561
5899
|
}
|
|
5562
5900
|
function hasBlockingFindings(findings) {
|
|
5563
5901
|
return findings.some(isBlockingFinding);
|
|
@@ -6055,7 +6393,7 @@ program.command("whoami").description("Show the resolved instance, tenant, crede
|
|
|
6055
6393
|
});
|
|
6056
6394
|
program.command("schema").description("Emit the bundled RevTurbineConfig JSON schema for an agent to author against.").option("--json", "Accepted for symmetry; output is always JSON").action(() => {
|
|
6057
6395
|
try {
|
|
6058
|
-
const jsonSchema =
|
|
6396
|
+
const jsonSchema = z29.toJSONSchema(RevTurbineConfigSchema, { unrepresentable: "any" });
|
|
6059
6397
|
emit({ schema_version: SCHEMA_VERSION, schema: jsonSchema }, true);
|
|
6060
6398
|
} catch (err) {
|
|
6061
6399
|
fail(EXIT.UNEXPECTED, `could not render the bundled schema (${SCHEMA_VERSION}): ${err.message}`);
|
|
@@ -6112,9 +6450,23 @@ program.command("validate").description("Validate a Config File offline (schema)
|
|
|
6112
6450
|
throw new SelectorError("STATE_REQUIRED \u2014 validate needs a version: <file> (offline) or --draft (server catalog).");
|
|
6113
6451
|
}
|
|
6114
6452
|
if (!opts.draft) {
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6453
|
+
let blockedFiles = 0;
|
|
6454
|
+
for (const file of files) {
|
|
6455
|
+
const raw = loadConfig(file);
|
|
6456
|
+
const parsed = schema.safeParse(raw);
|
|
6457
|
+
const findings = evaluate(parsed.success ? parsed.data : raw, {
|
|
6458
|
+
structuralErrors: parsed.success ? void 0 : parsed.error
|
|
6459
|
+
});
|
|
6460
|
+
diag(`Validation for ${file} (offline, schema ${SCHEMA_VERSION}):`);
|
|
6461
|
+
process.stdout.write(`${formatFindings(findings)}
|
|
6462
|
+
`);
|
|
6463
|
+
if (hasBlockingFindings(findings)) blockedFiles += 1;
|
|
6464
|
+
}
|
|
6465
|
+
if (files.length > 1) diag(`${files.length - blockedFiles}/${files.length} passed.`);
|
|
6466
|
+
if (blockedFiles > 0) {
|
|
6467
|
+
fail(EXIT.VALIDATION, `${blockedFiles} file(s) have blocking findings.`);
|
|
6468
|
+
}
|
|
6469
|
+
process.exit(0);
|
|
6118
6470
|
}
|
|
6119
6471
|
const conn = connect(opts.url, opts.tenantId);
|
|
6120
6472
|
const playbookVersionId = await requireOpenDraft(conn);
|
package/package.json
CHANGED