@rogatio/cli 6.14.1 → 6.14.3

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/node/index.js +262 -269
  2. package/package.json +1 -1
@@ -24445,8 +24445,8 @@ function normalizeMockConfig(value, matcherById) {
24445
24445
  if (!Array.isArray(record.headers) || record.headers.length > LIMITS.maxMockHeadersPerRule)
24446
24446
  return null;
24447
24447
  const normalizedHeaders = [];
24448
- for (const headerValue3 of record.headers) {
24449
- const header = normalizeMockHeader(headerValue3);
24448
+ for (const headerValue32 of record.headers) {
24449
+ const header = normalizeMockHeader(headerValue32);
24450
24450
  if (header === null) return null;
24451
24451
  normalizedHeaders.push(header);
24452
24452
  }
@@ -27188,25 +27188,6 @@ import { readFile as readFile4 } from "node:fs/promises";
27188
27188
  import { dirname as dirname6, isAbsolute as isAbsolute5, resolve as resolve2 } from "node:path";
27189
27189
 
27190
27190
  // packages/dry-run/dist/node/index.js
27191
- function parseTestUrl(input) {
27192
- if (typeof input !== "string" || input.length === 0) {
27193
- return { ok: false };
27194
- }
27195
- let url;
27196
- try {
27197
- url = new URL(input);
27198
- } catch {
27199
- return { ok: false };
27200
- }
27201
- if (url.protocol !== "http:" && url.protocol !== "https:") {
27202
- return { ok: false };
27203
- }
27204
- if (url.origin === "null") {
27205
- return { ok: false };
27206
- }
27207
- return { ok: true, value: { origin: url.origin, href: url.href } };
27208
- }
27209
- var DEFAULT_MAX_CASES = 256;
27210
27191
  function invalidCase(index, message = "Test case is invalid") {
27211
27192
  return {
27212
27193
  code: "dryrun.invalid-case",
@@ -27220,6 +27201,69 @@ function invalidUrl(index, message = "Test case URL is invalid") {
27220
27201
  function invalidOptions() {
27221
27202
  return invalidCase(void 0, "Dry-run options are invalid");
27222
27203
  }
27204
+ function batchLimit(maxCases) {
27205
+ return {
27206
+ code: "dryrun.batch-limit",
27207
+ message: `Test batch exceeds maxCases (${maxCases})`
27208
+ };
27209
+ }
27210
+ function buildDimension(state, detail) {
27211
+ return {
27212
+ state,
27213
+ matched: state === "not-applicable" ? null : state === "matched",
27214
+ detail
27215
+ };
27216
+ }
27217
+ function sourceDetail(source, matched) {
27218
+ const subject = source.key === "host" ? "hostname" : "url";
27219
+ return matched ? `${subject} matched /${source.value}/ (${source.key})` : `${subject} did not match /${source.value}/ (${source.key})`;
27220
+ }
27221
+ function sourceDimension(matcher, url) {
27222
+ const sourceState = sourceMatches(matcher.source, url) ? "matched" : "unmatched";
27223
+ return buildDimension(
27224
+ sourceState,
27225
+ sourceDetail(matcher.source, sourceState === "matched")
27226
+ );
27227
+ }
27228
+ function methodDimension(matcher, testCase) {
27229
+ const methodState = testCase.method === void 0 ? "not-applicable" : matcher.method === void 0 || matcher.method === testCase.method ? "matched" : "unmatched";
27230
+ return buildDimension(
27231
+ methodState,
27232
+ methodState === "not-applicable" ? "method not specified" : methodState === "matched" ? matcher.method === void 0 ? "rule has no method constraint" : `method ${testCase.method} matches` : `rule method ${matcher.method} != ${testCase.method}`
27233
+ );
27234
+ }
27235
+ function resourceTypeDimension(matcher, testCase) {
27236
+ const resourceState = testCase.resourceType === void 0 ? "not-applicable" : matcher.resourceTypes.length === 0 || matcher.resourceTypes.includes(testCase.resourceType) ? "matched" : "unmatched";
27237
+ return buildDimension(
27238
+ resourceState,
27239
+ resourceState === "not-applicable" ? "resource type not specified" : resourceState === "matched" ? matcher.resourceTypes.length === 0 ? "rule has no resource type constraint" : `resource type ${testCase.resourceType} matches` : `rule resource types [${matcher.resourceTypes.join(", ")}] exclude ${testCase.resourceType}`
27240
+ );
27241
+ }
27242
+ function safePreview(fn, operation, url, testCase) {
27243
+ try {
27244
+ return fn(operation, url, testCase);
27245
+ } catch {
27246
+ return null;
27247
+ }
27248
+ }
27249
+ function evaluateRule(operation, testCase, previewAction) {
27250
+ const matcher = operation.matcher;
27251
+ const source = sourceDimension(matcher, testCase.url);
27252
+ const method = methodDimension(matcher, testCase);
27253
+ const resourceType = resourceTypeDimension(matcher, testCase);
27254
+ const matched = source.state === "matched" && method.state !== "unmatched" && resourceType.state !== "unmatched";
27255
+ const actionPreview = previewAction ? safePreview(previewAction, operation, testCase.url, testCase) : null;
27256
+ return {
27257
+ groupId: operation.groupId,
27258
+ ruleId: operation.ruleId,
27259
+ matched,
27260
+ source,
27261
+ method,
27262
+ resourceType,
27263
+ actionPreview
27264
+ };
27265
+ }
27266
+ var DEFAULT_MAX_CASES = 256;
27223
27267
  function ownPropertyNames(value) {
27224
27268
  try {
27225
27269
  const names = Object.getOwnPropertyNames(value);
@@ -27291,32 +27335,6 @@ function validateCase(raw, index) {
27291
27335
  }
27292
27336
  };
27293
27337
  }
27294
- function safePreview(fn, operation, url, testCase) {
27295
- try {
27296
- return fn(operation, url, testCase);
27297
- } catch {
27298
- return null;
27299
- }
27300
- }
27301
- function buildDimension(state, detail) {
27302
- return {
27303
- state,
27304
- matched: state === "not-applicable" ? null : state === "matched",
27305
- detail
27306
- };
27307
- }
27308
- function sourceDetail(source, matched) {
27309
- const subject = source.key === "host" ? "hostname" : "url";
27310
- return matched ? `${subject} matched /${source.value}/ (${source.key})` : `${subject} did not match /${source.value}/ (${source.key})`;
27311
- }
27312
- function emptySummary() {
27313
- return {
27314
- caseCount: 0,
27315
- urlCount: 0,
27316
- matchedUrlCount: 0,
27317
- matchedRuleTotal: 0
27318
- };
27319
- }
27320
27338
  function normalizeOptions(raw) {
27321
27339
  if (raw === void 0) {
27322
27340
  return { ok: true, maxCases: DEFAULT_MAX_CASES };
@@ -27354,47 +27372,29 @@ function normalizeOptions(raw) {
27354
27372
  }
27355
27373
  return { ok: true, maxCases, previewAction };
27356
27374
  }
27357
- function dryRunProject(operations, cases, options) {
27358
- const normalizedOptions = normalizeOptions(options);
27359
- if (!normalizedOptions.ok) {
27360
- return { results: [], errors: [invalidOptions()], summary: emptySummary() };
27361
- }
27362
- const errors = [];
27375
+ function readCaseBatch(cases, maxCases) {
27363
27376
  if (!Array.isArray(cases)) {
27364
27377
  return {
27365
- results: [],
27366
- errors: [invalidCase(void 0, "Test cases must be an array")],
27367
- summary: emptySummary()
27378
+ ok: false,
27379
+ error: invalidCase(void 0, "Test cases must be an array")
27368
27380
  };
27369
27381
  }
27370
27382
  let caseCount;
27371
27383
  try {
27372
27384
  caseCount = cases.length;
27373
- if (caseCount > normalizedOptions.maxCases) {
27374
- return {
27375
- results: [],
27376
- errors: [
27377
- {
27378
- code: "dryrun.batch-limit",
27379
- message: `Test batch exceeds maxCases (${normalizedOptions.maxCases})`
27380
- }
27381
- ],
27382
- summary: emptySummary()
27383
- };
27385
+ if (caseCount > maxCases) {
27386
+ return { ok: false, error: batchLimit(maxCases) };
27384
27387
  }
27385
27388
  for (let index = 0; index < caseCount; index += 1) {
27386
27389
  if (!Object.hasOwn(cases, index)) {
27387
- return {
27388
- results: [],
27389
- errors: [invalidCase(index)],
27390
- summary: emptySummary()
27391
- };
27390
+ return { ok: false, error: invalidCase(index) };
27392
27391
  }
27393
27392
  }
27394
27393
  } catch {
27395
- return { results: [], errors: [invalidCase()], summary: emptySummary() };
27394
+ return { ok: false, error: invalidCase() };
27396
27395
  }
27397
- const validCases = [];
27396
+ const errors = [];
27397
+ const valid = [];
27398
27398
  for (let index = 0; index < caseCount; index += 1) {
27399
27399
  let raw;
27400
27400
  try {
@@ -27405,15 +27405,57 @@ function dryRunProject(operations, cases, options) {
27405
27405
  }
27406
27406
  const result = validateCase(raw, index);
27407
27407
  if (result.ok) {
27408
- validCases.push({ value: result.value, index });
27408
+ valid.push({ value: result.value, index });
27409
27409
  } else {
27410
27410
  errors.push(result.error);
27411
27411
  }
27412
27412
  }
27413
+ return { ok: true, valid, errors };
27414
+ }
27415
+ function parseTestUrl(input) {
27416
+ if (typeof input !== "string" || input.length === 0) {
27417
+ return { ok: false };
27418
+ }
27419
+ let url;
27420
+ try {
27421
+ url = new URL(input);
27422
+ } catch {
27423
+ return { ok: false };
27424
+ }
27425
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
27426
+ return { ok: false };
27427
+ }
27428
+ if (url.origin === "null") {
27429
+ return { ok: false };
27430
+ }
27431
+ return { ok: true, value: { origin: url.origin, href: url.href } };
27432
+ }
27433
+ function emptySummary() {
27434
+ return {
27435
+ caseCount: 0,
27436
+ urlCount: 0,
27437
+ matchedUrlCount: 0,
27438
+ matchedRuleTotal: 0
27439
+ };
27440
+ }
27441
+ function dryRunProject(operations, cases, options) {
27442
+ const normalizedOptions = normalizeOptions(options);
27443
+ if (!normalizedOptions.ok) {
27444
+ return { results: [], errors: [invalidOptions()], summary: emptySummary() };
27445
+ }
27446
+ const batch = readCaseBatch(cases, normalizedOptions.maxCases);
27447
+ if (!batch.ok) {
27448
+ return {
27449
+ results: [],
27450
+ errors: [batch.error],
27451
+ summary: emptySummary()
27452
+ };
27453
+ }
27454
+ const errors = [...batch.errors];
27413
27455
  const results = [];
27414
27456
  let matchedUrlCount = 0;
27415
27457
  let matchedRuleTotal = 0;
27416
- for (const validCase of validCases) {
27458
+ for (const validCase of batch.valid) {
27417
27459
  const testCase = validCase.value;
27418
27460
  const parsed = parseTestUrl(testCase.url);
27419
27461
  if (!parsed.ok) {
@@ -27422,41 +27464,7 @@ function dryRunProject(operations, cases, options) {
27422
27464
  }
27423
27465
  const rules = [];
27424
27466
  for (const op of operations) {
27425
- const matcher = op.matcher;
27426
- const sourceState = sourceMatches(
27427
- matcher.source,
27428
- testCase.url
27429
- ) ? "matched" : "unmatched";
27430
- const methodState = testCase.method === void 0 ? "not-applicable" : matcher.method === void 0 || matcher.method === testCase.method ? "matched" : "unmatched";
27431
- const resourceState = testCase.resourceType === void 0 ? "not-applicable" : matcher.resourceTypes.length === 0 || matcher.resourceTypes.includes(testCase.resourceType) ? "matched" : "unmatched";
27432
- const matched = sourceState === "matched" && methodState !== "unmatched" && resourceState !== "unmatched";
27433
- const sourceDim = buildDimension(
27434
- sourceState,
27435
- sourceDetail(matcher.source, sourceState === "matched")
27436
- );
27437
- const methodDim = buildDimension(
27438
- methodState,
27439
- methodState === "not-applicable" ? "method not specified" : methodState === "matched" ? matcher.method === void 0 ? "rule has no method constraint" : `method ${testCase.method} matches` : `rule method ${matcher.method} != ${testCase.method}`
27440
- );
27441
- const resourceDim = buildDimension(
27442
- resourceState,
27443
- resourceState === "not-applicable" ? "resource type not specified" : resourceState === "matched" ? matcher.resourceTypes.length === 0 ? "rule has no resource type constraint" : `resource type ${testCase.resourceType} matches` : `rule resource types [${matcher.resourceTypes.join(", ")}] exclude ${testCase.resourceType}`
27444
- );
27445
- const actionPreview = normalizedOptions.previewAction ? safePreview(
27446
- normalizedOptions.previewAction,
27447
- op,
27448
- testCase.url,
27449
- testCase
27450
- ) : null;
27451
- rules.push({
27452
- groupId: op.groupId,
27453
- ruleId: op.ruleId,
27454
- matched,
27455
- source: sourceDim,
27456
- method: methodDim,
27457
- resourceType: resourceDim,
27458
- actionPreview
27459
- });
27467
+ rules.push(evaluateRule(op, testCase, normalizedOptions.previewAction));
27460
27468
  }
27461
27469
  const matchedRuleCount = rules.filter((rule) => rule.matched).length;
27462
27470
  results.push({
@@ -27473,7 +27481,7 @@ function dryRunProject(operations, cases, options) {
27473
27481
  results,
27474
27482
  errors,
27475
27483
  summary: {
27476
- caseCount: validCases.length,
27484
+ caseCount: batch.valid.length,
27477
27485
  urlCount: results.length,
27478
27486
  matchedUrlCount,
27479
27487
  matchedRuleTotal
@@ -27548,6 +27556,45 @@ function previewRuleAction(operation, url) {
27548
27556
  return null;
27549
27557
  }
27550
27558
 
27559
+ // packages/cli/src/commands/diagnose.ts
27560
+ function diagnoseProject(projectData) {
27561
+ const diagnostics = [];
27562
+ const schemaResult = validateProjectDetailed(projectData);
27563
+ if (!schemaResult.valid) {
27564
+ for (const error of schemaResult.errors) {
27565
+ diagnostics.push({
27566
+ code: `schema.${error.keyword}`,
27567
+ severity: "error",
27568
+ path: error.instancePath || "/",
27569
+ message: error.message,
27570
+ params: error.params
27571
+ });
27572
+ }
27573
+ return { diagnostics, stage: "schema" };
27574
+ }
27575
+ const compileResult = compileProject(schemaResult.data);
27576
+ if (!compileResult.ok) {
27577
+ for (const diag of compileResult.diagnostics) {
27578
+ diagnostics.push({
27579
+ code: diag.code,
27580
+ severity: diag.severity,
27581
+ path: diag.path,
27582
+ message: diag.message,
27583
+ params: { ...diag.params }
27584
+ });
27585
+ }
27586
+ return { diagnostics, stage: "compiler" };
27587
+ }
27588
+ return {
27589
+ diagnostics,
27590
+ project: schemaResult.data,
27591
+ operations: compileResult.operations
27592
+ };
27593
+ }
27594
+ function formatDiagnostics(diagnostics) {
27595
+ return diagnostics.map((diag) => `${diag.path}: ${diag.message} (${diag.code})`).join("\n");
27596
+ }
27597
+
27551
27598
  // packages/cli/src/mock-root-config.ts
27552
27599
  import { mkdir as mkdir3, readFile as readFile3, realpath as realpath3, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
27553
27600
  import { homedir } from "node:os";
@@ -27616,6 +27663,26 @@ async function writeSavedMockRoot(filePath, root, env = process.env) {
27616
27663
  }
27617
27664
 
27618
27665
  // packages/cli/src/server/routes.ts
27666
+ function headerValue3(value) {
27667
+ if (typeof value === "string") return value;
27668
+ if (Array.isArray(value) && value.length === 1 && typeof value[0] === "string") {
27669
+ return value[0];
27670
+ }
27671
+ return void 0;
27672
+ }
27673
+ function isAllowedEditorHost(host, boundPort) {
27674
+ if (host === void 0 || !Number.isInteger(boundPort) || boundPort <= 0) {
27675
+ return false;
27676
+ }
27677
+ const expected = String(boundPort);
27678
+ return host === `127.0.0.1:${expected}` || host === `localhost:${expected}`;
27679
+ }
27680
+ function isAllowedEditorOrigin(origin, boundPort) {
27681
+ if (origin === void 0) return true;
27682
+ if (!Number.isInteger(boundPort) || boundPort <= 0) return false;
27683
+ const expected = String(boundPort);
27684
+ return origin === `http://127.0.0.1:${expected}` || origin === `http://localhost:${expected}`;
27685
+ }
27619
27686
  function generateCsrfToken() {
27620
27687
  return randomBytes3(16).toString("hex");
27621
27688
  }
@@ -27694,26 +27761,14 @@ function parseUrl(req) {
27694
27761
  );
27695
27762
  return { pathname: url.pathname, searchParams: url.searchParams };
27696
27763
  }
27697
- function parseDiagnostics(result) {
27698
- if (!result.valid) {
27699
- return result.errors.map((e) => ({
27700
- code: `schema.${e.keyword}`,
27701
- severity: "error",
27702
- path: e.instancePath || "/",
27703
- message: e.message,
27704
- params: e.params
27705
- }));
27706
- }
27707
- return [];
27708
- }
27709
27764
  function schemaDiagnosticsForAssist(value) {
27710
- const result = validateProjectDetailed(value);
27711
- if (result.valid) return [];
27712
- return result.errors.map((error) => ({
27713
- code: `schema.${error.keyword}`,
27765
+ const diagnosis = diagnoseProject(value);
27766
+ if (diagnosis.stage !== "schema") return [];
27767
+ return diagnosis.diagnostics.map((diagnostic) => ({
27768
+ code: diagnostic.code,
27714
27769
  severity: "error",
27715
- path: error.instancePath || "/",
27716
- message: error.message ?? "The project contains invalid data."
27770
+ path: diagnostic.path,
27771
+ message: diagnostic.message
27717
27772
  }));
27718
27773
  }
27719
27774
  function isAIAssistRequestBody(body) {
@@ -27725,36 +27780,42 @@ function isAIAssistRequestBody(body) {
27725
27780
  if (!isRecord2(body.context) || !isRecord2(body.context.project)) return false;
27726
27781
  return true;
27727
27782
  }
27728
- function parseCompilerDiagnostics(result) {
27729
- if (!result.ok) {
27730
- return result.diagnostics.map((d) => ({
27731
- code: d.code,
27732
- severity: d.severity,
27733
- path: d.path,
27734
- message: d.message,
27735
- params: d.params
27736
- }));
27737
- }
27738
- return [];
27739
- }
27740
27783
  function createRoutes(context) {
27741
27784
  return async function handleRequest(req, res) {
27785
+ const host = headerValue3(req.headers.host);
27786
+ if (!isAllowedEditorHost(host, context.boundPort)) {
27787
+ res.writeHead(403, { "Content-Type": "application/json" });
27788
+ res.end(
27789
+ JSON.stringify({
27790
+ code: "host-invalid",
27791
+ message: "Invalid Host header"
27792
+ })
27793
+ );
27794
+ return;
27795
+ }
27742
27796
  const { pathname } = parseUrl(req);
27743
27797
  const method = req.method || "GET";
27744
- const corsHeaders = {
27745
- "Access-Control-Allow-Origin": "http://127.0.0.1:*",
27746
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
27747
- "Access-Control-Allow-Headers": "Content-Type, X-CSRF-Token"
27748
- };
27798
+ if (method === "POST") {
27799
+ const origin = headerValue3(req.headers.origin);
27800
+ if (!isAllowedEditorOrigin(origin, context.boundPort)) {
27801
+ res.writeHead(403, { "Content-Type": "application/json" });
27802
+ res.end(
27803
+ JSON.stringify({
27804
+ code: "origin-invalid",
27805
+ message: "Invalid Origin header"
27806
+ })
27807
+ );
27808
+ return;
27809
+ }
27810
+ }
27749
27811
  if (method === "OPTIONS") {
27750
- res.writeHead(204, corsHeaders);
27812
+ res.writeHead(204);
27751
27813
  res.end();
27752
27814
  return;
27753
27815
  }
27754
27816
  if (pathname === "/editor.html" && method === "GET") {
27755
27817
  res.writeHead(200, {
27756
- "Content-Type": "text/html; charset=utf-8",
27757
- ...corsHeaders
27818
+ "Content-Type": "text/html; charset=utf-8"
27758
27819
  });
27759
27820
  res.end(context.editorHtml);
27760
27821
  return;
@@ -27763,8 +27824,7 @@ function createRoutes(context) {
27763
27824
  try {
27764
27825
  const bundle = await readFile4(context.editorBundlePath, "utf-8");
27765
27826
  res.writeHead(200, {
27766
- "Content-Type": "text/javascript; charset=utf-8",
27767
- ...corsHeaders
27827
+ "Content-Type": "text/javascript; charset=utf-8"
27768
27828
  });
27769
27829
  res.end(bundle);
27770
27830
  } catch (e) {
@@ -27782,8 +27842,7 @@ function createRoutes(context) {
27782
27842
  try {
27783
27843
  const css = await readFile4(context.editorCssPath, "utf-8");
27784
27844
  res.writeHead(200, {
27785
- "Content-Type": "text/css; charset=utf-8",
27786
- ...corsHeaders
27845
+ "Content-Type": "text/css; charset=utf-8"
27787
27846
  });
27788
27847
  res.end(css);
27789
27848
  } catch (e) {
@@ -27807,8 +27866,7 @@ function createRoutes(context) {
27807
27866
  try {
27808
27867
  const font = await readFile4(resolve2(context.editorFontsPath, fileName));
27809
27868
  res.writeHead(200, {
27810
- "Content-Type": "font/woff2",
27811
- ...corsHeaders
27869
+ "Content-Type": "font/woff2"
27812
27870
  });
27813
27871
  res.end(font);
27814
27872
  } catch {
@@ -27843,14 +27901,9 @@ function createRoutes(context) {
27843
27901
  res.end(JSON.stringify(failure2.body));
27844
27902
  return;
27845
27903
  }
27846
- const schemaResult = validateProjectDetailed(body);
27847
- const diagnostics = [...parseDiagnostics(schemaResult)];
27848
- if (schemaResult.valid) {
27849
- const compileResult = compileProject(schemaResult.data);
27850
- diagnostics.push(...parseCompilerDiagnostics(compileResult));
27851
- }
27904
+ const diagnosis = diagnoseProject(body);
27852
27905
  res.writeHead(200, { "Content-Type": "application/json" });
27853
- res.end(JSON.stringify({ diagnostics }));
27906
+ res.end(JSON.stringify({ diagnostics: diagnosis.diagnostics }));
27854
27907
  return;
27855
27908
  }
27856
27909
  if (pathname === "/api/mock-root" && method === "GET") {
@@ -27928,26 +27981,25 @@ function createRoutes(context) {
27928
27981
  res.end(JSON.stringify(failure2.body));
27929
27982
  return;
27930
27983
  }
27931
- const schemaResult = validateProjectDetailed(body);
27932
- if (!schemaResult.valid) {
27984
+ const diagnosis = diagnoseProject(body);
27985
+ if (diagnosis.stage === "schema") {
27933
27986
  res.writeHead(400, { "Content-Type": "application/json" });
27934
27987
  res.end(
27935
27988
  JSON.stringify({
27936
27989
  code: "validation-failed",
27937
27990
  message: "Project validation failed",
27938
- diagnostics: parseDiagnostics(schemaResult)
27991
+ diagnostics: diagnosis.diagnostics
27939
27992
  })
27940
27993
  );
27941
27994
  return;
27942
27995
  }
27943
- const compileResult = compileProject(schemaResult.data);
27944
- if (!compileResult.ok) {
27996
+ if (diagnosis.stage === "compiler") {
27945
27997
  res.writeHead(400, { "Content-Type": "application/json" });
27946
27998
  res.end(
27947
27999
  JSON.stringify({
27948
28000
  code: "compilation-failed",
27949
28001
  message: "Project compilation failed",
27950
- diagnostics: parseCompilerDiagnostics(compileResult)
28002
+ diagnostics: diagnosis.diagnostics
27951
28003
  })
27952
28004
  );
27953
28005
  return;
@@ -28026,35 +28078,37 @@ function createRoutes(context) {
28026
28078
  );
28027
28079
  return;
28028
28080
  }
28029
- const schemaResult = validateProjectDetailed(body.project);
28030
- if (!schemaResult.valid) {
28081
+ const diagnosis = diagnoseProject(body.project);
28082
+ if (diagnosis.stage === "schema") {
28031
28083
  res.writeHead(400, { "Content-Type": "application/json" });
28032
28084
  res.end(
28033
28085
  JSON.stringify({
28034
28086
  code: "validation-failed",
28035
28087
  message: "Project validation failed",
28036
- diagnostics: parseDiagnostics(schemaResult)
28088
+ diagnostics: diagnosis.diagnostics
28037
28089
  })
28038
28090
  );
28039
28091
  return;
28040
28092
  }
28041
- const compileResult = compileProject(schemaResult.data);
28042
- if (!compileResult.ok) {
28093
+ if (diagnosis.stage === "compiler") {
28043
28094
  res.writeHead(400, { "Content-Type": "application/json" });
28044
28095
  res.end(
28045
28096
  JSON.stringify({
28046
28097
  code: "compilation-failed",
28047
28098
  message: "Project compilation failed",
28048
- diagnostics: parseCompilerDiagnostics(compileResult)
28099
+ diagnostics: diagnosis.diagnostics
28049
28100
  })
28050
28101
  );
28051
28102
  return;
28052
28103
  }
28104
+ if (diagnosis.operations === void 0) {
28105
+ throw new Error("Valid project diagnosis is missing operations");
28106
+ }
28053
28107
  const requested = isRecord2(body.options) && typeof body.options.maxCases === "number" && Number.isSafeInteger(body.options.maxCases) && body.options.maxCases > 0 ? body.options.maxCases : void 0;
28054
28108
  const options = { previewAction: previewRuleAction };
28055
28109
  if (requested !== void 0) options.maxCases = requested;
28056
28110
  const result = dryRunProject(
28057
- compileResult.operations,
28111
+ diagnosis.operations,
28058
28112
  body.cases,
28059
28113
  options
28060
28114
  );
@@ -28177,8 +28231,7 @@ function createRoutes(context) {
28177
28231
  res.writeHead(200, {
28178
28232
  "Content-Type": "text/event-stream",
28179
28233
  "Cache-Control": "no-cache",
28180
- Connection: "keep-alive",
28181
- ...corsHeaders
28234
+ Connection: "keep-alive"
28182
28235
  });
28183
28236
  const stream = context.aiClient?.stream(options);
28184
28237
  for await (const chunk of stream) {
@@ -28805,6 +28858,7 @@ async function editCommand(args, options = {}) {
28805
28858
  shutdown: () => {
28806
28859
  shutdown();
28807
28860
  },
28861
+ boundPort: 0,
28808
28862
  editorHtml: "",
28809
28863
  editorBundlePath: "",
28810
28864
  editorCssPath: "",
@@ -28824,6 +28878,7 @@ async function editCommand(args, options = {}) {
28824
28878
  return { exitCode: Promise.resolve(2), shutdown: () => {
28825
28879
  } };
28826
28880
  }
28881
+ context.boundPort = server.port;
28827
28882
  const serverUrl = `http://127.0.0.1:${server.port}`;
28828
28883
  const editorUrl = `${serverUrl}/editor.html`;
28829
28884
  const {
@@ -28950,9 +29005,9 @@ function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
28950
29005
  import { createEditor } from '@rogatio/editor';
28951
29006
 
28952
29007
  const root = document.getElementById('editor-root');
28953
- const apiBase = '${apiBase}';
28954
- const csrfToken = '${csrfToken}';
28955
- const filePath = '${filePath}';
29008
+ const apiBase = ${JSON.stringify(apiBase)};
29009
+ const csrfToken = ${JSON.stringify(csrfToken)};
29010
+ const filePath = ${JSON.stringify(filePath)};
28956
29011
 
28957
29012
  async function fetchProject() {
28958
29013
  const res = await fetch(apiBase + '/api/project');
@@ -29181,9 +29236,12 @@ var METHOD_SET = new Set(HTTP_METHODS);
29181
29236
  var SOURCE_CHANGE = {
29182
29237
  caseInsensitive: "The case-insensitive match flag was dropped. Rogatio regular expressions are case-sensitive.",
29183
29238
  hostPort: "Requestly compares the host and a non-default port. This host condition matches the hostname on any port.",
29184
- hostAsUrl: "The host condition was imported as a URL regular expression so the port stays part of the match.",
29239
+ hostAsUrl: "The host condition was imported as a URL regular expression over the authority. Wildcard host patterns accept any numeric port; Contains does not pin a port.",
29185
29240
  path: "The path condition was imported as a URL regular expression."
29186
29241
  };
29242
+ var HOST_MATCHES_SKIP_REASON = "Host regular expressions cannot be confined to the host in a URL regex.";
29243
+ var HOST_AUTHORITY_META_SKIP_REASON = "Host Contains or Wildcard values with /, ?, #, @, or \\ cannot be confined to the host in a URL regex.";
29244
+ var HOST_AUTHORITY_META = /[/?#@\\]/;
29187
29245
  function escapeRegex(literal) {
29188
29246
  return literal.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
29189
29247
  }
@@ -29317,24 +29375,26 @@ function convertHost(operator, value) {
29317
29375
  };
29318
29376
  }
29319
29377
  if (operator === "Contains") {
29320
- const body = value.length === 0 ? "[^/?#]*" : `[^/?#]*${escapeRegex(value)}[^/?#]*`;
29378
+ if (HOST_AUTHORITY_META.test(value)) {
29379
+ return { ok: false, reason: HOST_AUTHORITY_META_SKIP_REASON };
29380
+ }
29381
+ const body = value.length === 0 ? "[^/?#@]*" : `[^/?#@]*${escapeRegex(value)}[^/?#@]*`;
29321
29382
  return finishUrl(`^https?:\\/\\/${body}(?:[/?#].*)?$`, [
29322
29383
  SOURCE_CHANGE.hostAsUrl
29323
29384
  ]);
29324
29385
  }
29325
29386
  if (operator === "Wildcard_Matches") {
29326
- return finishUrl(`^https?:\\/\\/${wildcardBody(value)}(?:[/?#].*)?$`, [
29327
- SOURCE_CHANGE.hostAsUrl
29328
- ]);
29329
- }
29330
- if (operator === "Matches") {
29331
- const parsed = parseRequestlyRegex(value);
29332
- if (!parsed.ok) return parsed;
29387
+ if (HOST_AUTHORITY_META.test(value)) {
29388
+ return { ok: false, reason: HOST_AUTHORITY_META_SKIP_REASON };
29389
+ }
29333
29390
  return finishUrl(
29334
- `^https?:\\/\\/(?:${parsed.source})(?:[/?#].*)?$`,
29335
- parsed.caseInsensitive ? [SOURCE_CHANGE.hostAsUrl, SOURCE_CHANGE.caseInsensitive] : [SOURCE_CHANGE.hostAsUrl]
29391
+ `^https?:\\/\\/${hostWildcardBody(value)}(?::[0-9]+)?(?:[/?#].*)?$`,
29392
+ [SOURCE_CHANGE.hostAsUrl]
29336
29393
  );
29337
29394
  }
29395
+ if (operator === "Matches") {
29396
+ return { ok: false, reason: HOST_MATCHES_SKIP_REASON };
29397
+ }
29338
29398
  return { ok: false, reason: `Unsupported source operator "${operator}".` };
29339
29399
  }
29340
29400
  function convertPath(operator, value) {
@@ -29403,6 +29463,9 @@ function parseRequestlyRegex(value) {
29403
29463
  function wildcardBody(pattern) {
29404
29464
  return pattern.split("*").map((part) => escapeRegex(part)).join("(.*?)");
29405
29465
  }
29466
+ function hostWildcardBody(pattern) {
29467
+ return pattern.split("*").map((part) => escapeRegex(part)).join("([^/?#@]*?)");
29468
+ }
29406
29469
  function isRequestlyRegexLiteral(value) {
29407
29470
  return REQUESTLY_REGEX.test(value);
29408
29471
  }
@@ -30987,41 +31050,6 @@ function cloneProject(value) {
30987
31050
  return record;
30988
31051
  }
30989
31052
 
30990
- // packages/cli/src/commands/diagnose.ts
30991
- function diagnoseProject(projectData) {
30992
- const diagnostics = [];
30993
- const schemaResult = validateProjectDetailed(projectData);
30994
- if (!schemaResult.valid) {
30995
- for (const error of schemaResult.errors) {
30996
- diagnostics.push({
30997
- code: `schema.${error.keyword}`,
30998
- severity: "error",
30999
- path: error.instancePath || "/",
31000
- message: error.message,
31001
- params: error.params
31002
- });
31003
- }
31004
- return { diagnostics };
31005
- }
31006
- const compileResult = compileProject(schemaResult.data);
31007
- if (!compileResult.ok) {
31008
- for (const diag of compileResult.diagnostics) {
31009
- diagnostics.push({
31010
- code: diag.code,
31011
- severity: diag.severity,
31012
- path: diag.path,
31013
- message: diag.message,
31014
- params: { ...diag.params }
31015
- });
31016
- }
31017
- return { diagnostics };
31018
- }
31019
- return { diagnostics, project: schemaResult.data };
31020
- }
31021
- function formatDiagnostics(diagnostics) {
31022
- return diagnostics.map((diag) => `${diag.path}: ${diag.message} (${diag.code})`).join("\n");
31023
- }
31024
-
31025
31053
  // packages/cli/src/commands/import.ts
31026
31054
  async function importCommand(args) {
31027
31055
  const parsed = parseImportArgs(args);
@@ -31579,27 +31607,6 @@ function resultOptions(maxCases) {
31579
31607
  if (maxCases !== void 0) options.maxCases = maxCases;
31580
31608
  return options;
31581
31609
  }
31582
- function diagnosticsFromSchema(projectData) {
31583
- const result = validateProjectDetailed(projectData);
31584
- if (result.valid) return [];
31585
- return result.errors.map((error) => ({
31586
- code: `schema.${error.keyword}`,
31587
- severity: "error",
31588
- path: error.instancePath || "/",
31589
- message: error.message,
31590
- params: error.params
31591
- }));
31592
- }
31593
- function diagnosticsFromCompiler(result) {
31594
- if (result.ok) return [];
31595
- return result.diagnostics.map((diagnostic) => ({
31596
- code: diagnostic.code,
31597
- severity: diagnostic.severity,
31598
- path: diagnostic.path,
31599
- message: diagnostic.message,
31600
- params: diagnostic.params
31601
- }));
31602
- }
31603
31610
  function jsonOutput(value) {
31604
31611
  return `${JSON.stringify(value, null, 2)}
31605
31612
  `;
@@ -31741,21 +31748,9 @@ async function testCommandImpl(args, stdinInput, captureOutput, options = {}) {
31741
31748
  console.error(output.trim());
31742
31749
  return 2;
31743
31750
  }
31744
- const schemaResult = validateProjectDetailed(projectData);
31745
- let diagnostics = diagnosticsFromSchema(projectData);
31746
- if (!schemaResult.valid) {
31747
- const output = jsonMode ? jsonOutput({ diagnostics }) : diagnostics.map(
31748
- (diagnostic) => `${diagnostic.path}: ${diagnostic.message} (${diagnostic.code})
31749
- `
31750
- ).join("");
31751
- if (captureOutput) return output;
31752
- if (jsonMode) console.log(output.trim());
31753
- else console.error(output.trim());
31754
- return 1;
31755
- }
31756
- const compileResult = compileProject(schemaResult.data);
31757
- diagnostics = diagnosticsFromCompiler(compileResult);
31758
- if (!compileResult.ok) {
31751
+ const diagnosis = diagnoseProject(projectData);
31752
+ if (diagnosis.stage !== void 0 || diagnosis.project === void 0 || diagnosis.operations === void 0) {
31753
+ const diagnostics = diagnosis.diagnostics;
31759
31754
  const output = jsonMode ? jsonOutput({ diagnostics }) : diagnostics.map(
31760
31755
  (diagnostic) => `${diagnostic.path}: ${diagnostic.message} (${diagnostic.code})
31761
31756
  `
@@ -31771,7 +31766,7 @@ async function testCommandImpl(args, stdinInput, captureOutput, options = {}) {
31771
31766
  defaultResourceType
31772
31767
  );
31773
31768
  const dryRunResult = dryRunProject(
31774
- compileResult.operations,
31769
+ diagnosis.operations,
31775
31770
  testCases,
31776
31771
  resultOptions(maxCases)
31777
31772
  );
@@ -31781,13 +31776,11 @@ async function testCommandImpl(args, stdinInput, captureOutput, options = {}) {
31781
31776
  console.log(output.trim());
31782
31777
  } else {
31783
31778
  const groupNames = /* @__PURE__ */ new Map();
31784
- if (schemaResult.valid) {
31785
- for (const group of schemaResult.data.groups) {
31786
- groupNames.set(group.id, group.name);
31787
- }
31779
+ for (const group of diagnosis.project.groups) {
31780
+ groupNames.set(group.id, group.name);
31788
31781
  }
31789
31782
  const labels = /* @__PURE__ */ new Map();
31790
- for (const operation of compileResult.operations) {
31783
+ for (const operation of diagnosis.operations) {
31791
31784
  const groupName = groupNames.get(operation.groupId);
31792
31785
  labels.set(
31793
31786
  `${operation.groupId}/${operation.ruleId}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogatio/cli",
3
- "version": "6.14.1",
3
+ "version": "6.14.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local-first browser request and response rules — editor host, file verification, test runner, and runtime dispatch.",