norn-cli 1.4.3 → 1.4.5

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.
@@ -3,7 +3,7 @@
3
3
  "urls": {
4
4
  "https://petstore.swagger.io/v2/swagger.json": {
5
5
  "baseUrl": "https://petstore.swagger.io/v2",
6
- "fetchedAt": "2026-02-22T11:34:46.683Z",
6
+ "fetchedAt": "2026-02-24T16:44:27.288Z",
7
7
  "schemas": [
8
8
  {
9
9
  "operationId": "AddPet",
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  All notable changes to the "Norn" extension will be documented in this file.
4
4
 
5
+ ## [1.4.5] - 2026-02-24
6
+
7
+ ### Fixed
8
+ - **Diagnostics False Positive (JSON Property Assignment)**:
9
+ - Fixed editor squiggles on valid JSON property assignments with array indexing, for example `config.users[0].name = "Updated Alice"`.
10
+
11
+ ## [1.4.4] - 2026-02-24
12
+
13
+ ### Improved
14
+ - **Error UX (Extension + CLI)**:
15
+ - Added structured, more actionable request/sequence error messages with environment-aware hints.
16
+ - Improved preflight validation so unresolved variables and invalid URLs are caught before request execution.
17
+ - Standardized error formatting across extension response panel and CLI output.
18
+
19
+ - **Response Panel Error Readability**:
20
+ - Sequence errors/warnings now render as timeline entries (same visual style as execution steps) and appear at the end of the run.
21
+ - Error details are grouped into clearer sections with primary fields surfaced first.
22
+
23
+ - **Diagnostics Coverage and Persistence**:
24
+ - Expanded `.norn` editor diagnostics for malformed statements and invalid command combinations.
25
+ - Added workspace-wide diagnostic refresh so Explorer error badges persist after files are closed.
26
+
27
+ ### Fixed
28
+ - **Diagnostics False Positives**:
29
+ - Fixed false squiggles for valid request body variable lines (for example `payload` used as a request body).
30
+ - Fixed false squiggles for endpoint calls with parameters (for example `GET MultiParam(hello, world)` and named args).
31
+ - Fixed false squiggles for commented-out empty variable references (`{{}}`) in comment lines.
32
+
33
+ - **Preflight Validation Regression**:
34
+ - Fixed request preflight validation incorrectly blocking some `GET`/`HEAD` requests due to unresolved placeholders detected in parsed body content.
35
+
5
36
  ## [1.4.3] - 2026-02-22
6
37
 
7
38
  ### Improved
package/dist/cli.js CHANGED
@@ -26208,6 +26208,29 @@ var CookieJar = class _CookieJar {
26208
26208
  }
26209
26209
  };
26210
26210
 
26211
+ // src/errors/nornError.ts
26212
+ var NornError = class extends Error {
26213
+ category;
26214
+ code;
26215
+ hint;
26216
+ details;
26217
+ context;
26218
+ cause;
26219
+ constructor(options) {
26220
+ super(options.message);
26221
+ this.name = "NornError";
26222
+ this.category = options.category;
26223
+ this.code = options.code;
26224
+ this.hint = options.hint;
26225
+ this.details = Array.isArray(options.details) ? options.details : options.details ? [options.details] : void 0;
26226
+ this.context = options.context;
26227
+ this.cause = options.cause;
26228
+ }
26229
+ };
26230
+ function isNornError(error) {
26231
+ return error instanceof NornError;
26232
+ }
26233
+
26211
26234
  // src/httpClient.ts
26212
26235
  var sharedCookieJar = new CookieJar();
26213
26236
  function createCookieJar() {
@@ -26368,8 +26391,41 @@ async function sendRequestWithJar(request, jar, retryOptions) {
26368
26391
  return result;
26369
26392
  } catch (error) {
26370
26393
  const errorMessage = error.message || String(error);
26371
- lastError = new Error(`${errorMessage}
26372
- URL used: ${currentUrl || request.url}`);
26394
+ const effectiveUrl = currentUrl || request.url;
26395
+ const isUrlError = /invalid url/i.test(errorMessage) || /unsupported protocol/i.test(errorMessage);
26396
+ const isAxiosNetworkError = Boolean(error?.isAxiosError) && !error?.response;
26397
+ if (isUrlError) {
26398
+ lastError = new NornError({
26399
+ category: "url",
26400
+ code: "http-invalid-url",
26401
+ message: `Invalid request URL: ${effectiveUrl}`,
26402
+ details: errorMessage,
26403
+ hint: "Check the URL and any variables used to build it before sending the request.",
26404
+ context: {
26405
+ source: "httpClient",
26406
+ method: currentMethod,
26407
+ url: effectiveUrl
26408
+ },
26409
+ cause: error
26410
+ });
26411
+ } else if (isAxiosNetworkError) {
26412
+ lastError = new NornError({
26413
+ category: "network",
26414
+ code: "http-network-error",
26415
+ message: `Network request failed.`,
26416
+ details: [`${errorMessage}`, `URL used: ${effectiveUrl}`],
26417
+ hint: "Check connectivity, DNS/host availability, TLS settings, and the request URL.",
26418
+ context: {
26419
+ source: "httpClient",
26420
+ method: currentMethod,
26421
+ url: effectiveUrl
26422
+ },
26423
+ cause: error
26424
+ });
26425
+ } else {
26426
+ lastError = new Error(`${errorMessage}
26427
+ URL used: ${effectiveUrl}`);
26428
+ }
26373
26429
  if (attempt < totalAttempts) {
26374
26430
  const waitMs = backoffMs * attempt;
26375
26431
  retriedErrors.push(`Attempt ${attempt}: ${errorMessage}`);
@@ -26758,6 +26814,202 @@ function parsePropertyAssignment(line2) {
26758
26814
  };
26759
26815
  }
26760
26816
 
26817
+ // src/errors/formatError.ts
26818
+ function mergeContext(base, overrides) {
26819
+ if (!base && !overrides) {
26820
+ return void 0;
26821
+ }
26822
+ return {
26823
+ ...base || {},
26824
+ ...overrides || {},
26825
+ environment: {
26826
+ ...base?.environment || {},
26827
+ ...overrides?.environment || {}
26828
+ }
26829
+ };
26830
+ }
26831
+ function normalizeKnownError(error, context) {
26832
+ if (isNornError(error)) {
26833
+ if (!context) {
26834
+ return error;
26835
+ }
26836
+ return new NornError({
26837
+ category: error.category,
26838
+ code: error.code,
26839
+ message: error.message,
26840
+ hint: error.hint,
26841
+ details: error.details,
26842
+ context: mergeContext(error.context, context),
26843
+ cause: error.cause
26844
+ });
26845
+ }
26846
+ if (error instanceof Error) {
26847
+ const msg = error.message || String(error);
26848
+ if (msg === "No valid HTTP method found") {
26849
+ return new NornError({
26850
+ category: "syntax",
26851
+ code: "request-missing-method",
26852
+ message: "Could not parse request: no valid HTTP method found.",
26853
+ hint: "Start the request with a valid HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS).",
26854
+ context: mergeContext(void 0, context)
26855
+ });
26856
+ }
26857
+ if (msg.startsWith("Unknown endpoint: ")) {
26858
+ const endpointName = msg.slice("Unknown endpoint: ".length).trim();
26859
+ return new NornError({
26860
+ category: "validation",
26861
+ code: "unknown-endpoint",
26862
+ message: `Unknown endpoint '${endpointName}'.`,
26863
+ hint: "Import a .nornapi file that defines this endpoint, or fix the endpoint name.",
26864
+ context: mergeContext(void 0, context)
26865
+ });
26866
+ }
26867
+ if (/invalid url/i.test(msg) || /unsupported protocol/i.test(msg)) {
26868
+ return new NornError({
26869
+ category: "url",
26870
+ code: "invalid-url",
26871
+ message: `Invalid request URL.`,
26872
+ details: msg,
26873
+ hint: "Check the request URL and any substituted variables (for example {{baseUrl}}).",
26874
+ context: mergeContext(void 0, context)
26875
+ });
26876
+ }
26877
+ return error;
26878
+ }
26879
+ return new Error(String(error));
26880
+ }
26881
+ function formatUserFacingError(error, context) {
26882
+ const normalized = normalizeKnownError(error, context);
26883
+ if (!isNornError(normalized)) {
26884
+ if (normalized instanceof Error) {
26885
+ return normalized.message;
26886
+ }
26887
+ return String(normalized);
26888
+ }
26889
+ const lines = [normalized.message];
26890
+ const ctx = normalized.context;
26891
+ if (ctx?.stepIndex !== void 0) {
26892
+ lines.push(`Step: ${ctx.stepIndex}`);
26893
+ }
26894
+ if (ctx?.requestName) {
26895
+ lines.push(`Request: ${ctx.requestName}`);
26896
+ } else if (ctx?.method || ctx?.url) {
26897
+ const requestLine = [ctx.method, ctx.url].filter(Boolean).join(" ");
26898
+ if (requestLine) {
26899
+ lines.push(`Request: ${requestLine}`);
26900
+ }
26901
+ }
26902
+ if (ctx?.filePath) {
26903
+ lines.push(`File: ${ctx.filePath}`);
26904
+ }
26905
+ if (ctx?.environment) {
26906
+ const env3 = ctx.environment;
26907
+ if (env3.hasEnvFile) {
26908
+ const active = env3.activeEnvironment ?? "none";
26909
+ const available = env3.availableEnvironments?.length ? env3.availableEnvironments.join(", ") : "none";
26910
+ lines.push(`Environment: ${active} (available: ${available})`);
26911
+ }
26912
+ }
26913
+ if (normalized.details) {
26914
+ for (const detail of normalized.details) {
26915
+ lines.push(detail);
26916
+ }
26917
+ }
26918
+ if (normalized.hint) {
26919
+ lines.push(`Hint: ${normalized.hint}`);
26920
+ }
26921
+ return lines.join("\n");
26922
+ }
26923
+
26924
+ // src/requestValidation.ts
26925
+ var PLACEHOLDER_REGEX = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
26926
+ function collectUnresolvedPlaceholders(text) {
26927
+ if (!text) {
26928
+ return [];
26929
+ }
26930
+ const names = /* @__PURE__ */ new Set();
26931
+ let match;
26932
+ PLACEHOLDER_REGEX.lastIndex = 0;
26933
+ while ((match = PLACEHOLDER_REGEX.exec(text)) !== null) {
26934
+ names.add(match[1]);
26935
+ }
26936
+ return [...names];
26937
+ }
26938
+ function buildMissingVariableHint(missingVars, context) {
26939
+ const env3 = context?.environment;
26940
+ if (env3?.hasEnvFile && env3.availableEnvironments && env3.availableEnvironments.length > 0 && !env3.activeEnvironment) {
26941
+ return `A .nornenv file was found but no environment is selected. Select one of: ${env3.availableEnvironments.join(", ")}.`;
26942
+ }
26943
+ if (env3?.hasEnvFile && env3.activeEnvironment) {
26944
+ return `Check that ${missingVars.map((v) => `'${v}'`).join(", ")} exist in the active environment '${env3.activeEnvironment}' or as file-level variables.`;
26945
+ }
26946
+ return `Define ${missingVars.length === 1 ? "the variable" : "the variables"} ${missingVars.map((v) => `'${v}'`).join(", ")} before sending the request.`;
26947
+ }
26948
+ function buildRequestContext(parsed, context) {
26949
+ return {
26950
+ ...context,
26951
+ method: context?.method || parsed.method,
26952
+ url: context?.url || parsed.url,
26953
+ environment: context?.environment
26954
+ };
26955
+ }
26956
+ function describeUnresolvedLocation(label, vars) {
26957
+ if (vars.length === 0) {
26958
+ return void 0;
26959
+ }
26960
+ return `${label}: unresolved ${vars.length === 1 ? "variable" : "variables"} ${vars.map((v) => `{{${v}}}`).join(", ")}`;
26961
+ }
26962
+ function shouldValidateBodyPlaceholders(parsed) {
26963
+ const method = String(parsed.method || "").toUpperCase();
26964
+ return method !== "GET" && method !== "HEAD";
26965
+ }
26966
+ function validatePreparedRequest(parsed, context) {
26967
+ const urlVars = collectUnresolvedPlaceholders(parsed.url);
26968
+ const headerVars = [...new Set(Object.values(parsed.headers).flatMap((value) => collectUnresolvedPlaceholders(value)))];
26969
+ const bodyVars = shouldValidateBodyPlaceholders(parsed) ? collectUnresolvedPlaceholders(parsed.body) : [];
26970
+ const missingVars = [.../* @__PURE__ */ new Set([...urlVars, ...headerVars, ...bodyVars])];
26971
+ if (missingVars.length > 0) {
26972
+ const details = [
26973
+ describeUnresolvedLocation("URL", urlVars),
26974
+ describeUnresolvedLocation("Headers", headerVars),
26975
+ describeUnresolvedLocation("Body", bodyVars)
26976
+ ].filter((d) => Boolean(d));
26977
+ throw new NornError({
26978
+ category: "variable-resolution",
26979
+ code: "unresolved-request-variables",
26980
+ message: `Request could not be prepared: unresolved ${missingVars.length === 1 ? "variable" : "variables"} ${missingVars.map((v) => `{{${v}}}`).join(", ")}.`,
26981
+ details,
26982
+ hint: buildMissingVariableHint(missingVars, context),
26983
+ context: {
26984
+ ...buildRequestContext(parsed, context),
26985
+ unresolvedVariables: missingVars
26986
+ }
26987
+ });
26988
+ }
26989
+ if (!parsed.url || !parsed.url.trim()) {
26990
+ throw new NornError({
26991
+ category: "url",
26992
+ code: "missing-request-url",
26993
+ message: "Request URL is missing.",
26994
+ hint: "Add a URL after the HTTP method (for example: GET https://api.example.com/path).",
26995
+ context: buildRequestContext(parsed, context)
26996
+ });
26997
+ }
26998
+ try {
26999
+ new URL(parsed.url);
27000
+ } catch (error) {
27001
+ throw new NornError({
27002
+ category: "url",
27003
+ code: "invalid-request-url",
27004
+ message: `Invalid request URL: ${parsed.url}`,
27005
+ details: error instanceof Error ? error.message : String(error),
27006
+ hint: "Check the URL format and any variables used to build it.",
27007
+ context: buildRequestContext(parsed, context),
27008
+ cause: error
27009
+ });
27010
+ }
27011
+ }
27012
+
26761
27013
  // src/sequenceRunner.ts
26762
27014
  init_assertionRunner();
26763
27015
  function applyHeaderGroupsToRequest(parsed, requestText, headerGroups, variables) {
@@ -26818,6 +27070,9 @@ function applyHeaderGroupsToRequest(parsed, requestText, headerGroups, variables
26818
27070
  }
26819
27071
  return result;
26820
27072
  }
27073
+ function indentMultiline(value, prefix = " ") {
27074
+ return value.split("\n").map((line2) => `${prefix}${line2}`).join("\n");
27075
+ }
26821
27076
  function isRunNamedRequestCommand(line2) {
26822
27077
  const trimmed = line2.trim();
26823
27078
  if (!trimmed.toLowerCase().startsWith("run ")) {
@@ -27930,7 +28185,7 @@ function getValueByPath(response, path5) {
27930
28185
  return void 0;
27931
28186
  }
27932
28187
  }
27933
- async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, workingDir, fullDocumentText, onProgress, callStack, sequenceArgs, apiDefinitions, tagFilterOptions, sequenceSources) {
28188
+ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, workingDir, fullDocumentText, onProgress, callStack, sequenceArgs, apiDefinitions, tagFilterOptions, sequenceSources, executionContext) {
27934
28189
  const startTime = Date.now();
27935
28190
  const responses = [];
27936
28191
  const scriptResults = [];
@@ -27960,6 +28215,16 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
27960
28215
  }
27961
28216
  };
27962
28217
  const runtimeVariables = { ...fileVariables, ...sequenceArgs };
28218
+ const buildRequestValidationContext = (stepNumber, method, url2, requestName) => ({
28219
+ source: "sequence",
28220
+ filePath: executionContext?.filePath,
28221
+ sequenceName: executionContext?.sequenceName,
28222
+ stepIndex: stepNumber,
28223
+ method,
28224
+ url: url2,
28225
+ requestName,
28226
+ environment: executionContext?.environment
28227
+ });
27963
28228
  let requestIndex = 0;
27964
28229
  const ifStack = [];
27965
28230
  const shouldSkip = () => ifStack.length > 0 && ifStack.some((v) => !v);
@@ -28341,6 +28606,10 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28341
28606
  }
28342
28607
  }
28343
28608
  const requestParsed = parserHttpRequest(requestText, runtimeVariables);
28609
+ validatePreparedRequest(
28610
+ requestParsed,
28611
+ buildRequestValidationContext(stepIdx + 1, requestParsed.method, requestParsed.url, `var ${parsed.varName}`)
28612
+ );
28344
28613
  const retryOpts = parsed.retryCount ? {
28345
28614
  retryCount: parsed.retryCount,
28346
28615
  backoffMs: parsed.backoffMs || 1e3,
@@ -28372,8 +28641,13 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28372
28641
  }
28373
28642
  }
28374
28643
  } catch (error) {
28375
- let errorDetails = `Request failed for var ${parsed.varName}: ${error.message}`;
28376
- if (requestDescription) {
28644
+ const userMessage = formatUserFacingError(
28645
+ error,
28646
+ buildRequestValidationContext(stepIdx + 1, void 0, void 0, `var ${parsed.varName}`)
28647
+ );
28648
+ let errorDetails = `Request failed for var ${parsed.varName}:
28649
+ ${indentMultiline(userMessage)}`;
28650
+ if (requestDescription && !/\nRequest:\s/.test(userMessage)) {
28377
28651
  errorDetails += `
28378
28652
  Request: ${requestDescription}`;
28379
28653
  }
@@ -28558,8 +28832,9 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28558
28832
  // Pass API definitions for endpoint resolution
28559
28833
  tagFilterOptions,
28560
28834
  // Pass tag filter options for nested sequence filtering
28561
- sequenceSources
28835
+ sequenceSources,
28562
28836
  // Pass sequence sources for nested sequences
28837
+ executionContext
28563
28838
  );
28564
28839
  reportProgress(stepIdx, "sequenceEnd", `Finished sequence ${targetName}`, void 0, targetName);
28565
28840
  for (const subResponse of subResult.responses) {
@@ -28674,6 +28949,10 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28674
28949
  }
28675
28950
  requestUrl = requestParsed.url;
28676
28951
  requestMethod = requestParsed.method;
28952
+ validatePreparedRequest(
28953
+ requestParsed,
28954
+ buildRequestValidationContext(stepIdx + 1, requestMethod, requestUrl, targetName)
28955
+ );
28677
28956
  const effectiveRetryCount = parsed.retryCount ?? requestParsed.retryCount;
28678
28957
  const effectiveBackoffMs = parsed.backoffMs ?? requestParsed.backoffMs ?? 1e3;
28679
28958
  const retryOpts = effectiveRetryCount ? {
@@ -28704,8 +28983,13 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28704
28983
  }
28705
28984
  }
28706
28985
  } catch (error) {
28707
- let errorDetails = `Named request "${targetName}" failed: ${error.message}`;
28708
- if (requestUrl) {
28986
+ const userMessage = formatUserFacingError(
28987
+ error,
28988
+ buildRequestValidationContext(stepIdx + 1, requestMethod || void 0, requestUrl || void 0, targetName)
28989
+ );
28990
+ let errorDetails = `Named request "${targetName}" failed:
28991
+ ${indentMultiline(userMessage)}`;
28992
+ if (requestUrl && !/\nRequest:\s/.test(userMessage)) {
28709
28993
  errorDetails += `
28710
28994
  Request: ${requestMethod} ${requestUrl}`;
28711
28995
  }
@@ -28774,6 +29058,10 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28774
29058
  const requestParsed = parserHttpRequest(namedRequest.content, runtimeVariables);
28775
29059
  requestUrl = requestParsed.url;
28776
29060
  requestMethod = requestParsed.method;
29061
+ validatePreparedRequest(
29062
+ requestParsed,
29063
+ buildRequestValidationContext(stepIdx + 1, requestMethod, requestUrl, `${varName} = run ${sequenceName}`)
29064
+ );
28777
29065
  const effectiveRetryCount = parsed.retryCount ?? requestParsed.retryCount;
28778
29066
  const effectiveBackoffMs = parsed.backoffMs ?? requestParsed.backoffMs ?? 1e3;
28779
29067
  const retryOpts = effectiveRetryCount ? {
@@ -28811,8 +29099,13 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28811
29099
  }
28812
29100
  }
28813
29101
  } catch (error) {
28814
- let errorDetails = `Named request "${sequenceName}" failed: ${error.message}`;
28815
- if (requestUrl) {
29102
+ const userMessage = formatUserFacingError(
29103
+ error,
29104
+ buildRequestValidationContext(stepIdx + 1, requestMethod || void 0, requestUrl || void 0, `${varName} = run ${sequenceName}`)
29105
+ );
29106
+ let errorDetails = `Named request "${sequenceName}" failed:
29107
+ ${indentMultiline(userMessage)}`;
29108
+ if (requestUrl && !/\nRequest:\s/.test(userMessage)) {
28816
29109
  errorDetails += `
28817
29110
  Request: ${requestMethod} ${requestUrl}`;
28818
29111
  }
@@ -28890,8 +29183,9 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
28890
29183
  // Pass API definitions for endpoint resolution
28891
29184
  tagFilterOptions,
28892
29185
  // Pass tag filter options for nested sequence filtering
28893
- sequenceSources
29186
+ sequenceSources,
28894
29187
  // Pass sequence sources for nested sequences
29188
+ executionContext
28895
29189
  );
28896
29190
  reportProgress(stepIdx, "sequenceEnd", `Finished sequence ${sequenceName}`, void 0, sequenceName);
28897
29191
  for (const subResponse of subResult.responses) {
@@ -29030,6 +29324,10 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
29030
29324
  }
29031
29325
  }
29032
29326
  const { retryCount, backoffMs } = extractRetryOptions(step.content);
29327
+ validatePreparedRequest(
29328
+ parsed,
29329
+ buildRequestValidationContext(stepIdx + 1, parsed.method, parsed.url)
29330
+ );
29033
29331
  const retryOpts = retryCount ?? parsed.retryCount ? {
29034
29332
  retryCount: retryCount ?? parsed.retryCount ?? 0,
29035
29333
  backoffMs: backoffMs ?? parsed.backoffMs ?? 1e3,
@@ -29058,8 +29356,13 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
29058
29356
  }
29059
29357
  }
29060
29358
  } catch (error) {
29061
- let errorDetails = `Request ${requestIndex} failed: ${error.message}`;
29062
- if (requestDescription) {
29359
+ const userMessage = formatUserFacingError(
29360
+ error,
29361
+ buildRequestValidationContext(stepIdx + 1)
29362
+ );
29363
+ let errorDetails = `Request ${requestIndex} failed:
29364
+ ${indentMultiline(userMessage)}`;
29365
+ if (requestDescription && !/\nRequest:\s/.test(userMessage)) {
29063
29366
  errorDetails += `
29064
29367
  Request: ${requestDescription}`;
29065
29368
  }
@@ -30396,6 +30699,13 @@ function mergeSecrets(targetNames, targetValues, sourceNames, sourceValues) {
30396
30699
  targetValues.set(name, value);
30397
30700
  }
30398
30701
  }
30702
+ function buildCliEnvironmentValidationContext(resolvedEnv, selectedEnv) {
30703
+ return {
30704
+ hasEnvFile: Boolean(resolvedEnv.envFilePath),
30705
+ activeEnvironment: selectedEnv,
30706
+ availableEnvironments: resolvedEnv.availableEnvironments
30707
+ };
30708
+ }
30399
30709
  function generateTimestamp() {
30400
30710
  const now = /* @__PURE__ */ new Date();
30401
30711
  const year = now.getFullYear();
@@ -30534,7 +30844,7 @@ Examples:
30534
30844
  norn api-tests.norn --no-redact # Show all data (no redaction)
30535
30845
  `);
30536
30846
  }
30537
- async function runSingleRequest(fileContent, variables, cookieJar, apiDefinitions) {
30847
+ async function runSingleRequest(fileContent, variables, cookieJar, apiDefinitions, filePath, envContext) {
30538
30848
  const lines = fileContent.split("\n");
30539
30849
  const requestLines = [];
30540
30850
  let foundRequestLine = false;
@@ -30602,8 +30912,16 @@ async function runSingleRequest(fileContent, variables, cookieJar, apiDefinition
30602
30912
  headers: combinedHeaders,
30603
30913
  body: apiRequest.body
30604
30914
  };
30915
+ validatePreparedRequest(parsed2, {
30916
+ source: "cli",
30917
+ filePath,
30918
+ method: parsed2.method,
30919
+ url: parsed2.url,
30920
+ environment: envContext
30921
+ });
30605
30922
  return await sendRequestWithJar(parsed2, cookieJar);
30606
30923
  }
30924
+ throw new Error(`Unknown endpoint: ${apiRequest.endpointName}`);
30607
30925
  }
30608
30926
  }
30609
30927
  let parsed = parserHttpRequest(requestContent, variables);
@@ -30649,6 +30967,13 @@ async function runSingleRequest(fileContent, variables, cookieJar, apiDefinition
30649
30967
  }
30650
30968
  parsed.url = parsed.url.trim();
30651
30969
  }
30970
+ validatePreparedRequest(parsed, {
30971
+ source: "cli",
30972
+ filePath,
30973
+ method: parsed.method,
30974
+ url: parsed.url,
30975
+ environment: envContext
30976
+ });
30652
30977
  return await sendRequestWithJar(parsed, cookieJar);
30653
30978
  }
30654
30979
  function discoverNornFiles(dirPath) {
@@ -30689,7 +31014,7 @@ function formatCaseLabel(params) {
30689
31014
  });
30690
31015
  return `[${parts.join(", ")}]`;
30691
31016
  }
30692
- async function runAllSequences(fileContent, variables, cookieJar, workingDir, apiDefinitions, tagFilterOptions, sequenceSources) {
31017
+ async function runAllSequences(fileContent, variables, cookieJar, workingDir, apiDefinitions, tagFilterOptions, sequenceSources, executionContext) {
30693
31018
  const allSequences = extractSequences(fileContent);
30694
31019
  const results = [];
30695
31020
  const testSequences = allSequences.filter((seq) => seq.isTest);
@@ -30734,7 +31059,8 @@ async function runAllSequences(fileContent, variables, cookieJar, workingDir, ap
30734
31059
  caseArgs,
30735
31060
  apiDefinitions,
30736
31061
  tagFilterOptions,
30737
- sequenceSources
31062
+ sequenceSources,
31063
+ { ...executionContext, sequenceName: seq.name }
30738
31064
  );
30739
31065
  result.name = `${seq.name}${caseLabel}`;
30740
31066
  results.push(result);
@@ -30759,7 +31085,8 @@ async function runAllSequences(fileContent, variables, cookieJar, workingDir, ap
30759
31085
  defaultArgs,
30760
31086
  apiDefinitions,
30761
31087
  tagFilterOptions,
30762
- sequenceSources
31088
+ sequenceSources,
31089
+ { ...executionContext, sequenceName: seq.name }
30763
31090
  );
30764
31091
  result.name = seq.name;
30765
31092
  results.push(result);
@@ -30823,6 +31150,7 @@ async function main() {
30823
31150
  if (options.sequence || options.request) {
30824
31151
  const filePath = filesToRun[0];
30825
31152
  const resolvedEnv = resolveEnvironmentForPath(filePath, options.env);
31153
+ const envValidationContext = buildCliEnvironmentValidationContext(resolvedEnv, options.env);
30826
31154
  if (resolvedEnv.envNotFound) {
30827
31155
  console.error(`Error: Environment '${resolvedEnv.envNotFound}' not found in .nornenv`);
30828
31156
  console.error(`Available environments: ${resolvedEnv.availableEnvironments.join(", ") || "none"}`);
@@ -30876,7 +31204,7 @@ ${fileContent}` : fileContent;
30876
31204
  if (options.verbose) {
30877
31205
  console.log(colors.info(`Running request: ${options.request}`));
30878
31206
  }
30879
- const response = await runSingleRequest(targetReq.content, variables, cookieJar, apiDefinitions);
31207
+ const response = await runSingleRequest(targetReq.content, variables, cookieJar, apiDefinitions, filePath, envValidationContext);
30880
31208
  if (options.output === "json") {
30881
31209
  console.log(JSON.stringify({ success: response.status >= 200 && response.status < 400, results: [response] }, null, 2));
30882
31210
  } else {
@@ -30918,7 +31246,8 @@ ${fileContent}` : fileContent;
30918
31246
  defaultArgs,
30919
31247
  apiDefinitions,
30920
31248
  tagFilterOptions,
30921
- importResult.sequenceSources
31249
+ importResult.sequenceSources,
31250
+ { filePath, sequenceName: targetSeq.name, environment: envValidationContext }
30922
31251
  );
30923
31252
  seqResult.name = targetSeq.name;
30924
31253
  if (options.output === "json") {
@@ -30959,6 +31288,7 @@ ${fileContent}` : fileContent;
30959
31288
  }
30960
31289
  for (const filePath of filesToRun) {
30961
31290
  const resolvedEnv = resolveEnvironmentForPath(filePath, options.env);
31291
+ const envValidationContext = buildCliEnvironmentValidationContext(resolvedEnv, options.env);
30962
31292
  if (resolvedEnv.envNotFound) {
30963
31293
  console.error(`Error: Environment '${resolvedEnv.envNotFound}' not found in .nornenv`);
30964
31294
  console.error(`Available environments: ${resolvedEnv.availableEnvironments.join(", ") || "none"}`);
@@ -31011,7 +31341,16 @@ ${fileContent}` : fileContent;
31011
31341
  \u2501\u2501\u2501 ${relPath} \u2501\u2501\u2501`));
31012
31342
  }
31013
31343
  const apiDefinitions = (importResult.headerGroups?.length || 0) > 0 || (importResult.endpoints?.length || 0) > 0 ? { headerGroups: importResult.headerGroups || [], endpoints: importResult.endpoints || [] } : void 0;
31014
- const seqResults = await runAllSequences(fileContentWithImports, variables, cookieJar, workingDir, apiDefinitions, tagFilterOptions, importResult.sequenceSources);
31344
+ const seqResults = await runAllSequences(
31345
+ fileContentWithImports,
31346
+ variables,
31347
+ cookieJar,
31348
+ workingDir,
31349
+ apiDefinitions,
31350
+ tagFilterOptions,
31351
+ importResult.sequenceSources,
31352
+ { filePath, environment: envValidationContext }
31353
+ );
31015
31354
  for (const result2 of seqResults) {
31016
31355
  result2.sourceFile = filePath;
31017
31356
  allResults.push(result2);
@@ -31131,7 +31470,8 @@ ${colors.error("Errors:")}`);
31131
31470
  }
31132
31471
  }
31133
31472
  main().catch((error) => {
31134
- console.error("Fatal error:", error.message);
31473
+ const message = formatUserFacingError(error, { source: "cli" });
31474
+ console.error("Fatal error:", message);
31135
31475
  process.exit(1);
31136
31476
  });
31137
31477
  /*! Bundled license information:
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "norn-cli",
3
3
  "displayName": "Norn - REST Client",
4
4
  "description": "A powerful REST client for making HTTP requests with sequences, variables, scripts, and cookie support",
5
- "version": "1.4.3",
5
+ "version": "1.4.5",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"