norn-cli 2.3.0 → 2.4.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/.claude/skills/norn-social-campaign/SKILL.md +70 -0
- package/CHANGELOG.md +6 -0
- package/demos/nornenv-region-refactor/README.md +64 -0
- package/dist/cli.js +360 -1
- package/out/apiResponseIntellisenseCache.js +394 -0
- package/out/assertionRunner.js +567 -0
- package/out/cacheDir.js +136 -0
- package/out/chatParticipant.js +763 -0
- package/out/cli/colors.js +127 -0
- package/out/cli/formatters/assertion.js +102 -0
- package/out/cli/formatters/index.js +23 -0
- package/out/cli/formatters/response.js +106 -0
- package/out/cli/formatters/summary.js +246 -0
- package/out/cli/redaction.js +237 -0
- package/out/cli/reporters/html.js +689 -0
- package/out/cli/reporters/index.js +22 -0
- package/out/cli/reporters/junit.js +226 -0
- package/out/codeLensProvider.js +351 -0
- package/out/compareContentProvider.js +85 -0
- package/out/completionProvider.js +3739 -0
- package/out/contractAssertionSummary.js +225 -0
- package/out/contractDecorationProvider.js +243 -0
- package/out/coverageCalculator.js +879 -0
- package/out/coveragePanel.js +597 -0
- package/out/debug/breakpointResolver.js +84 -0
- package/out/debug/breakpoints.js +52 -0
- package/out/debug/nornDebugAdapter.js +166 -0
- package/out/debug/nornDebugSession.js +613 -0
- package/out/debug/sequenceLocationIndex.js +77 -0
- package/out/debug/types.js +3 -0
- package/out/deepClone.js +21 -0
- package/out/diagnosticProvider.js +2554 -0
- package/out/environmentParser.js +736 -0
- package/out/environmentProvider.js +544 -0
- package/out/environmentTemplates.js +146 -0
- package/out/errors/formatError.js +113 -0
- package/out/errors/nornError.js +29 -0
- package/out/formUrlEncoded.js +89 -0
- package/out/httpClient.js +348 -0
- package/out/httpRuntimeOptions.js +16 -0
- package/out/importErrors.js +31 -0
- package/out/inlayHintResolver.js +70 -0
- package/out/jsonFileReader.js +323 -0
- package/out/mcpClient.js +193 -0
- package/out/mcpConfig.js +184 -0
- package/out/mcpToolIntellisenseCache.js +96 -0
- package/out/mcpToolSchema.js +50 -0
- package/out/nornConfig.js +132 -0
- package/out/nornHoverProvider.js +124 -0
- package/out/nornInlayHintsProvider.js +191 -0
- package/out/nornPrompt.js +755 -0
- package/out/nornSqlParser.js +286 -0
- package/out/nornapiHoverProvider.js +135 -0
- package/out/nornapiInlayHintsProvider.js +94 -0
- package/out/nornapiParser.js +324 -0
- package/out/nornenvCodeActionProvider.js +101 -0
- package/out/nornenvDecorationProvider.js +239 -0
- package/out/nornenvFoldingProvider.js +63 -0
- package/out/nornenvHoverProvider.js +114 -0
- package/out/nornenvInlayHintsProvider.js +99 -0
- package/out/nornenvLanguageModel.js +187 -0
- package/out/nornenvRegionRefactor.js +267 -0
- package/out/nornsqlHoverProvider.js +95 -0
- package/out/nornsqlInlayHintsProvider.js +114 -0
- package/out/parser.js +839 -0
- package/out/pathAccess.js +28 -0
- package/out/postmanImportPanel.js +732 -0
- package/out/postmanImportPlanner.js +1155 -0
- package/out/postmanImportSidebarView.js +532 -0
- package/out/quotedString.js +35 -0
- package/out/requestPreparation.js +179 -0
- package/out/requestValidation.js +146 -0
- package/out/responsePanel.js +7754 -0
- package/out/schemaGenerator.js +562 -0
- package/out/scriptRunner.js +419 -0
- package/out/secrets/cliSecrets.js +415 -0
- package/out/secrets/crypto.js +105 -0
- package/out/secrets/envFileSecrets.js +177 -0
- package/out/secrets/keyStore.js +259 -0
- package/out/sequenceDeclaration.js +15 -0
- package/out/sequenceRunner.js +3590 -0
- package/out/sqlAdapterRunner.js +122 -0
- package/out/sqlBuiltInAdapters.js +604 -0
- package/out/sqlConfig.js +184 -0
- package/out/starterCatalog.js +554 -0
- package/out/stringUtils.js +25 -0
- package/out/swaggerBodyIntellisenseCache.js +114 -0
- package/out/swaggerParser.js +464 -0
- package/out/testProvider.js +767 -0
- package/out/theoryCaseLoader.js +113 -0
- package/out/validationCache.js +211 -0
- package/package.json +6 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatUserFacingError = formatUserFacingError;
|
|
4
|
+
const nornError_1 = require("./nornError");
|
|
5
|
+
function mergeContext(base, overrides) {
|
|
6
|
+
if (!base && !overrides) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
...(base || {}),
|
|
11
|
+
...(overrides || {}),
|
|
12
|
+
environment: {
|
|
13
|
+
...(base?.environment || {}),
|
|
14
|
+
...(overrides?.environment || {})
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function normalizeKnownError(error, context) {
|
|
19
|
+
if ((0, nornError_1.isNornError)(error)) {
|
|
20
|
+
if (!context) {
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
return new nornError_1.NornError({
|
|
24
|
+
category: error.category,
|
|
25
|
+
code: error.code,
|
|
26
|
+
message: error.message,
|
|
27
|
+
hint: error.hint,
|
|
28
|
+
details: error.details,
|
|
29
|
+
context: mergeContext(error.context, context),
|
|
30
|
+
cause: error.cause
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (error instanceof Error) {
|
|
34
|
+
const msg = error.message || String(error);
|
|
35
|
+
if (msg === 'No valid HTTP method found') {
|
|
36
|
+
return new nornError_1.NornError({
|
|
37
|
+
category: 'syntax',
|
|
38
|
+
code: 'request-missing-method',
|
|
39
|
+
message: 'Could not parse request: no valid HTTP method found.',
|
|
40
|
+
hint: 'Start the request with a valid HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS).',
|
|
41
|
+
context: mergeContext(undefined, context)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (msg.startsWith('Unknown endpoint: ')) {
|
|
45
|
+
const endpointName = msg.slice('Unknown endpoint: '.length).trim();
|
|
46
|
+
return new nornError_1.NornError({
|
|
47
|
+
category: 'validation',
|
|
48
|
+
code: 'unknown-endpoint',
|
|
49
|
+
message: `Unknown endpoint '${endpointName}'.`,
|
|
50
|
+
hint: 'Import a .nornapi file that defines this endpoint, or fix the endpoint name.',
|
|
51
|
+
context: mergeContext(undefined, context)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (/invalid url/i.test(msg) || /unsupported protocol/i.test(msg)) {
|
|
55
|
+
return new nornError_1.NornError({
|
|
56
|
+
category: 'url',
|
|
57
|
+
code: 'invalid-url',
|
|
58
|
+
message: `Invalid request URL.`,
|
|
59
|
+
details: msg,
|
|
60
|
+
hint: 'Check the request URL and any substituted variables (for example {{baseUrl}}).',
|
|
61
|
+
context: mergeContext(undefined, context)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return error;
|
|
65
|
+
}
|
|
66
|
+
return new Error(String(error));
|
|
67
|
+
}
|
|
68
|
+
function formatUserFacingError(error, context) {
|
|
69
|
+
const normalized = normalizeKnownError(error, context);
|
|
70
|
+
if (!(0, nornError_1.isNornError)(normalized)) {
|
|
71
|
+
if (normalized instanceof Error) {
|
|
72
|
+
return normalized.message;
|
|
73
|
+
}
|
|
74
|
+
return String(normalized);
|
|
75
|
+
}
|
|
76
|
+
const lines = [normalized.message];
|
|
77
|
+
const ctx = normalized.context;
|
|
78
|
+
if (ctx?.stepIndex !== undefined) {
|
|
79
|
+
lines.push(`Step: ${ctx.stepIndex}`);
|
|
80
|
+
}
|
|
81
|
+
if (ctx?.requestName) {
|
|
82
|
+
lines.push(`Request: ${ctx.requestName}`);
|
|
83
|
+
}
|
|
84
|
+
else if (ctx?.method || ctx?.url) {
|
|
85
|
+
const requestLine = [ctx.method, ctx.url].filter(Boolean).join(' ');
|
|
86
|
+
if (requestLine) {
|
|
87
|
+
lines.push(`Request: ${requestLine}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (ctx?.filePath) {
|
|
91
|
+
lines.push(`File: ${ctx.filePath}`);
|
|
92
|
+
}
|
|
93
|
+
if (ctx?.environment) {
|
|
94
|
+
const env = ctx.environment;
|
|
95
|
+
if (env.hasEnvFile) {
|
|
96
|
+
const active = env.activeEnvironment ?? 'none';
|
|
97
|
+
const available = env.availableEnvironments?.length
|
|
98
|
+
? env.availableEnvironments.join(', ')
|
|
99
|
+
: 'none';
|
|
100
|
+
lines.push(`Environment: ${active} (available: ${available})`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (normalized.details) {
|
|
104
|
+
for (const detail of normalized.details) {
|
|
105
|
+
lines.push(detail);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (normalized.hint) {
|
|
109
|
+
lines.push(`Hint: ${normalized.hint}`);
|
|
110
|
+
}
|
|
111
|
+
return lines.join('\n');
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=formatError.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NornError = void 0;
|
|
4
|
+
exports.isNornError = isNornError;
|
|
5
|
+
class NornError extends Error {
|
|
6
|
+
category;
|
|
7
|
+
code;
|
|
8
|
+
hint;
|
|
9
|
+
details;
|
|
10
|
+
context;
|
|
11
|
+
cause;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super(options.message);
|
|
14
|
+
this.name = 'NornError';
|
|
15
|
+
this.category = options.category;
|
|
16
|
+
this.code = options.code;
|
|
17
|
+
this.hint = options.hint;
|
|
18
|
+
this.details = Array.isArray(options.details)
|
|
19
|
+
? options.details
|
|
20
|
+
: options.details ? [options.details] : undefined;
|
|
21
|
+
this.context = options.context;
|
|
22
|
+
this.cause = options.cause;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.NornError = NornError;
|
|
26
|
+
function isNornError(error) {
|
|
27
|
+
return error instanceof NornError;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=nornError.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isFormUrlEncodedContentType = isFormUrlEncodedContentType;
|
|
4
|
+
exports.parseFormUrlEncodedLines = parseFormUrlEncodedLines;
|
|
5
|
+
exports.parseFormUrlEncodedBody = parseFormUrlEncodedBody;
|
|
6
|
+
exports.encodeFormUrlEncodedBody = encodeFormUrlEncodedBody;
|
|
7
|
+
function parseEqualsField(segment) {
|
|
8
|
+
const eqIndex = segment.indexOf('=');
|
|
9
|
+
if (eqIndex <= 0) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
key: segment.substring(0, eqIndex).trim(),
|
|
14
|
+
value: segment.substring(eqIndex + 1).trim()
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function isFormUrlEncodedContentType(contentType) {
|
|
18
|
+
return Boolean(contentType && contentType.toLowerCase().includes('application/x-www-form-urlencoded'));
|
|
19
|
+
}
|
|
20
|
+
function parseFormUrlEncodedLines(lines) {
|
|
21
|
+
const fields = [];
|
|
22
|
+
const errors = [];
|
|
23
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
24
|
+
const line = lines[lineIndex].trim();
|
|
25
|
+
if (!line) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const firstEqIndex = line.indexOf('=');
|
|
29
|
+
const firstColonIndex = line.indexOf(':');
|
|
30
|
+
if (firstEqIndex > 0 && (firstColonIndex === -1 || firstEqIndex < firstColonIndex)) {
|
|
31
|
+
if (line.includes('&')) {
|
|
32
|
+
const segments = line.split('&').map(segment => segment.trim());
|
|
33
|
+
const hasInvalidSegment = segments.some(segment => !parseEqualsField(segment));
|
|
34
|
+
if (hasInvalidSegment) {
|
|
35
|
+
errors.push({
|
|
36
|
+
lineIndex,
|
|
37
|
+
line,
|
|
38
|
+
message: 'Ampersand-delimited form body lines must use key=value for every segment.'
|
|
39
|
+
});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
for (const segment of segments) {
|
|
43
|
+
const field = parseEqualsField(segment);
|
|
44
|
+
if (field) {
|
|
45
|
+
fields.push(field);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const field = parseEqualsField(line);
|
|
51
|
+
if (field) {
|
|
52
|
+
fields.push(field);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (firstColonIndex > 0) {
|
|
57
|
+
fields.push({
|
|
58
|
+
key: line.substring(0, firstColonIndex).trim(),
|
|
59
|
+
value: line.substring(firstColonIndex + 1).trim()
|
|
60
|
+
});
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
errors.push({
|
|
64
|
+
lineIndex,
|
|
65
|
+
line,
|
|
66
|
+
message: 'Expected key=value, key: value, or ampersand-delimited key=value pairs.'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { fields, errors };
|
|
70
|
+
}
|
|
71
|
+
function parseFormUrlEncodedBody(body) {
|
|
72
|
+
if (!body) {
|
|
73
|
+
return { fields: [], errors: [] };
|
|
74
|
+
}
|
|
75
|
+
return parseFormUrlEncodedLines(body.split('\n'));
|
|
76
|
+
}
|
|
77
|
+
function encodeFormUrlEncodedBody(body) {
|
|
78
|
+
const { fields, errors } = parseFormUrlEncodedBody(body);
|
|
79
|
+
if (errors.length > 0) {
|
|
80
|
+
return { encodedBody: '', errors };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
encodedBody: fields
|
|
84
|
+
.map(field => `${encodeURIComponent(field.key)}=${encodeURIComponent(field.value)}`)
|
|
85
|
+
.join('&'),
|
|
86
|
+
errors: []
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=formUrlEncoded.js.map
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.createCookieJar = createCookieJar;
|
|
40
|
+
exports.getSharedCookieJar = getSharedCookieJar;
|
|
41
|
+
exports.clearCookies = clearCookies;
|
|
42
|
+
exports.getCookiesForUrl = getCookiesForUrl;
|
|
43
|
+
exports.getAllCookies = getAllCookies;
|
|
44
|
+
exports.sendRequest = sendRequest;
|
|
45
|
+
exports.sendRequestWithJar = sendRequestWithJar;
|
|
46
|
+
const axios_1 = __importDefault(require("axios"));
|
|
47
|
+
const https = __importStar(require("https"));
|
|
48
|
+
const tough_cookie_1 = require("tough-cookie");
|
|
49
|
+
const nornError_1 = require("./errors/nornError");
|
|
50
|
+
const httpRuntimeOptions_1 = require("./httpRuntimeOptions");
|
|
51
|
+
const formUrlEncoded_1 = require("./formUrlEncoded");
|
|
52
|
+
// Shared cookie jar for VS Code extension (persists across requests)
|
|
53
|
+
let sharedCookieJar = new tough_cookie_1.CookieJar();
|
|
54
|
+
const insecureHttpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
55
|
+
function getHttpsAgent() {
|
|
56
|
+
return (0, httpRuntimeOptions_1.getVerifyTlsCertificates)() ? undefined : insecureHttpsAgent;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Creates a fresh cookie jar for isolated execution (CLI, sequences)
|
|
60
|
+
*/
|
|
61
|
+
function createCookieJar() {
|
|
62
|
+
return new tough_cookie_1.CookieJar();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Gets the shared cookie jar
|
|
66
|
+
*/
|
|
67
|
+
function getSharedCookieJar() {
|
|
68
|
+
return sharedCookieJar;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clears all cookies from the shared jar
|
|
72
|
+
*/
|
|
73
|
+
function clearCookies() {
|
|
74
|
+
sharedCookieJar = new tough_cookie_1.CookieJar();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Gets all cookies from a jar for a specific URL
|
|
78
|
+
*/
|
|
79
|
+
async function getCookiesForUrl(url, jar) {
|
|
80
|
+
const targetJar = jar || sharedCookieJar;
|
|
81
|
+
try {
|
|
82
|
+
const cookies = await targetJar.getCookies(url);
|
|
83
|
+
return cookies.map(cookieToInfo);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Gets all cookies from a jar
|
|
91
|
+
*/
|
|
92
|
+
async function getAllCookies(jar) {
|
|
93
|
+
const targetJar = jar || sharedCookieJar;
|
|
94
|
+
const serialized = await targetJar.serialize();
|
|
95
|
+
return (serialized.cookies || []).map((c) => ({
|
|
96
|
+
name: String(c.key || ''),
|
|
97
|
+
value: String(c.value || ''),
|
|
98
|
+
domain: String(c.domain || ''),
|
|
99
|
+
path: String(c.path || '/'),
|
|
100
|
+
expires: c.expires ? String(c.expires) : undefined,
|
|
101
|
+
httpOnly: Boolean(c.httpOnly),
|
|
102
|
+
secure: Boolean(c.secure),
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
function cookieToInfo(cookie) {
|
|
106
|
+
return {
|
|
107
|
+
name: cookie.key,
|
|
108
|
+
value: cookie.value,
|
|
109
|
+
domain: cookie.domain || '',
|
|
110
|
+
path: cookie.path || '/',
|
|
111
|
+
expires: cookie.expires instanceof Date ? cookie.expires.toISOString() : undefined,
|
|
112
|
+
httpOnly: cookie.httpOnly || false,
|
|
113
|
+
secure: cookie.secure || false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Sends a request using the shared cookie jar (for VS Code extension)
|
|
118
|
+
*/
|
|
119
|
+
async function sendRequest(request, retryOptions) {
|
|
120
|
+
return sendRequestWithJar(request, sharedCookieJar, retryOptions);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Helper function to wait
|
|
124
|
+
*/
|
|
125
|
+
function sleep(ms) {
|
|
126
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if a response indicates failure (should retry)
|
|
130
|
+
* Retries on: network errors, 5xx server errors, 429 rate limit
|
|
131
|
+
*/
|
|
132
|
+
function shouldRetry(response, error) {
|
|
133
|
+
// Always retry on network errors
|
|
134
|
+
if (error) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
// Retry on server errors (5xx) or rate limiting (429)
|
|
138
|
+
if (response && (response.status >= 500 || response.status === 429)) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Sends a request using a specific cookie jar
|
|
145
|
+
* Manually follows redirects to properly capture Set-Cookie headers at each step
|
|
146
|
+
* Supports automatic retry with linear backoff
|
|
147
|
+
*/
|
|
148
|
+
async function sendRequestWithJar(request, jar, retryOptions) {
|
|
149
|
+
const totalAttempts = (retryOptions?.retryCount ?? 0) + 1; // +1 for initial attempt
|
|
150
|
+
const backoffMs = retryOptions?.backoffMs ?? 1000;
|
|
151
|
+
const retriedErrors = [];
|
|
152
|
+
let lastError = null;
|
|
153
|
+
let attemptsMade = 0;
|
|
154
|
+
for (let attempt = 1; attempt <= totalAttempts; attempt++) {
|
|
155
|
+
attemptsMade = attempt;
|
|
156
|
+
const startTime = Date.now();
|
|
157
|
+
const maxRedirects = 10;
|
|
158
|
+
let redirectCount = 0;
|
|
159
|
+
let currentUrl = request.url;
|
|
160
|
+
let currentMethod = request.method;
|
|
161
|
+
let currentBody = request.body;
|
|
162
|
+
let finalResponse = null;
|
|
163
|
+
let sentRequestBody = undefined;
|
|
164
|
+
let sentRequestHeaders = {};
|
|
165
|
+
try {
|
|
166
|
+
while (redirectCount <= maxRedirects) {
|
|
167
|
+
// Get cookies for current URL
|
|
168
|
+
const existingCookies = await jar.getCookies(currentUrl);
|
|
169
|
+
const cookieHeader = existingCookies.map(c => `${c.key}=${c.value}`).join('; ');
|
|
170
|
+
const headers = { ...request.headers };
|
|
171
|
+
if (cookieHeader) {
|
|
172
|
+
headers['Cookie'] = cookieHeader;
|
|
173
|
+
}
|
|
174
|
+
// Determine how to send the body based on Content-Type
|
|
175
|
+
let data = undefined;
|
|
176
|
+
if (currentBody) {
|
|
177
|
+
const contentType = Object.keys(headers).find((key) => key.toLowerCase() === 'content-type');
|
|
178
|
+
const contentTypeValue = contentType ? headers[contentType] : '';
|
|
179
|
+
if ((0, formUrlEncoded_1.isFormUrlEncodedContentType)(contentTypeValue)) {
|
|
180
|
+
const encoded = (0, formUrlEncoded_1.encodeFormUrlEncodedBody)(currentBody);
|
|
181
|
+
if (encoded.errors.length > 0) {
|
|
182
|
+
throw new nornError_1.NornError({
|
|
183
|
+
category: 'syntax',
|
|
184
|
+
code: 'invalid-form-urlencoded-body',
|
|
185
|
+
message: 'Request body is not valid application/x-www-form-urlencoded syntax.',
|
|
186
|
+
details: encoded.errors.map(error => `Body line ${error.lineIndex + 1}: ${error.message} (${error.line})`),
|
|
187
|
+
hint: 'Use key=value, key: value, or a single line like key1=value1&key2=value2.',
|
|
188
|
+
context: {
|
|
189
|
+
source: 'httpClient',
|
|
190
|
+
method: currentMethod,
|
|
191
|
+
url: currentUrl
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
data = encoded.encodedBody;
|
|
196
|
+
}
|
|
197
|
+
else if (contentTypeValue.includes('application/json')) {
|
|
198
|
+
try {
|
|
199
|
+
data = JSON.parse(currentBody);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
data = currentBody;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
try {
|
|
207
|
+
data = JSON.parse(currentBody);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
data = currentBody;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Capture what we're actually sending (for debugging)
|
|
215
|
+
sentRequestBody = data;
|
|
216
|
+
sentRequestHeaders = { ...headers };
|
|
217
|
+
const response = await (0, axios_1.default)({
|
|
218
|
+
method: currentMethod,
|
|
219
|
+
url: currentUrl,
|
|
220
|
+
adapter: 'http',
|
|
221
|
+
headers,
|
|
222
|
+
data,
|
|
223
|
+
timeout: 30000,
|
|
224
|
+
maxRedirects: 0,
|
|
225
|
+
validateStatus: () => true,
|
|
226
|
+
httpsAgent: getHttpsAgent(),
|
|
227
|
+
});
|
|
228
|
+
// Store any Set-Cookie headers in the jar
|
|
229
|
+
const setCookieHeader = response.headers['set-cookie'];
|
|
230
|
+
if (setCookieHeader) {
|
|
231
|
+
const cookies = Array.isArray(setCookieHeader) ? setCookieHeader : [setCookieHeader];
|
|
232
|
+
for (const cookieStr of cookies) {
|
|
233
|
+
try {
|
|
234
|
+
await jar.setCookie(cookieStr, currentUrl);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Ignore invalid cookies
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Check if this is a redirect
|
|
242
|
+
if (response.status >= 300 && response.status < 400 && response.headers['location']) {
|
|
243
|
+
redirectCount++;
|
|
244
|
+
const location = response.headers['location'];
|
|
245
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
246
|
+
if (currentMethod !== 'GET' && currentMethod !== 'HEAD') {
|
|
247
|
+
currentMethod = 'GET';
|
|
248
|
+
currentBody = undefined;
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
finalResponse = response;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
if (!finalResponse) {
|
|
256
|
+
throw new Error('Too many redirects');
|
|
257
|
+
}
|
|
258
|
+
// Check if we should retry on this response status
|
|
259
|
+
if (shouldRetry(finalResponse, null) && attempt < totalAttempts) {
|
|
260
|
+
const waitMs = backoffMs * attempt; // Linear backoff
|
|
261
|
+
const errorMessage = `HTTP ${finalResponse.status} ${finalResponse.statusText}`;
|
|
262
|
+
retriedErrors.push(`Attempt ${attempt}: ${errorMessage}`);
|
|
263
|
+
// Call retry callback if provided
|
|
264
|
+
if (retryOptions?.onRetry) {
|
|
265
|
+
retryOptions.onRetry(attempt, totalAttempts, errorMessage, waitMs);
|
|
266
|
+
}
|
|
267
|
+
await sleep(waitMs);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
// Get ALL cookies in the jar
|
|
271
|
+
const cookies = await getAllCookies(jar);
|
|
272
|
+
const result = {
|
|
273
|
+
status: finalResponse.status,
|
|
274
|
+
statusText: finalResponse.statusText,
|
|
275
|
+
headers: finalResponse.headers,
|
|
276
|
+
body: finalResponse.data,
|
|
277
|
+
duration: Date.now() - startTime,
|
|
278
|
+
cookies,
|
|
279
|
+
requestBody: sentRequestBody,
|
|
280
|
+
requestHeaders: sentRequestHeaders,
|
|
281
|
+
};
|
|
282
|
+
// Add retry info if we made retries
|
|
283
|
+
if (attemptsMade > 1 || totalAttempts > 1) {
|
|
284
|
+
result.retryInfo = {
|
|
285
|
+
attemptsMade,
|
|
286
|
+
totalAttempts,
|
|
287
|
+
retriedErrors,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
const errorMessage = error.message || String(error);
|
|
294
|
+
const effectiveUrl = currentUrl || request.url;
|
|
295
|
+
const isUrlError = /invalid url/i.test(errorMessage) || /unsupported protocol/i.test(errorMessage);
|
|
296
|
+
const isAxiosNetworkError = Boolean(error?.isAxiosError) && !error?.response;
|
|
297
|
+
if (isUrlError) {
|
|
298
|
+
lastError = new nornError_1.NornError({
|
|
299
|
+
category: 'url',
|
|
300
|
+
code: 'http-invalid-url',
|
|
301
|
+
message: `Invalid request URL: ${effectiveUrl}`,
|
|
302
|
+
details: errorMessage,
|
|
303
|
+
hint: 'Check the URL and any variables used to build it before sending the request.',
|
|
304
|
+
context: {
|
|
305
|
+
source: 'httpClient',
|
|
306
|
+
method: currentMethod,
|
|
307
|
+
url: effectiveUrl,
|
|
308
|
+
},
|
|
309
|
+
cause: error,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
else if (isAxiosNetworkError) {
|
|
313
|
+
lastError = new nornError_1.NornError({
|
|
314
|
+
category: 'network',
|
|
315
|
+
code: 'http-network-error',
|
|
316
|
+
message: `Network request failed.`,
|
|
317
|
+
details: [`${errorMessage}`, `URL used: ${effectiveUrl}`],
|
|
318
|
+
hint: 'Check connectivity, DNS/host availability, TLS settings, and the request URL.',
|
|
319
|
+
context: {
|
|
320
|
+
source: 'httpClient',
|
|
321
|
+
method: currentMethod,
|
|
322
|
+
url: effectiveUrl,
|
|
323
|
+
},
|
|
324
|
+
cause: error,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
lastError = new Error(`${errorMessage}\nURL used: ${effectiveUrl}`);
|
|
329
|
+
}
|
|
330
|
+
// If we have more attempts, wait and retry
|
|
331
|
+
if (attempt < totalAttempts) {
|
|
332
|
+
const waitMs = backoffMs * attempt; // Linear backoff
|
|
333
|
+
retriedErrors.push(`Attempt ${attempt}: ${errorMessage}`);
|
|
334
|
+
// Call retry callback if provided
|
|
335
|
+
if (retryOptions?.onRetry) {
|
|
336
|
+
retryOptions.onRetry(attempt, totalAttempts, errorMessage, waitMs);
|
|
337
|
+
}
|
|
338
|
+
await sleep(waitMs);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
// No more retries, throw the error
|
|
342
|
+
throw lastError;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Should never reach here, but TypeScript needs this
|
|
346
|
+
throw lastError || new Error('Request failed');
|
|
347
|
+
}
|
|
348
|
+
//# sourceMappingURL=httpClient.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared runtime HTTP options for extension + CLI execution paths.
|
|
4
|
+
* Keep this module Node-only (no VS Code imports).
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.setVerifyTlsCertificates = setVerifyTlsCertificates;
|
|
8
|
+
exports.getVerifyTlsCertificates = getVerifyTlsCertificates;
|
|
9
|
+
let verifyTlsCertificates = true;
|
|
10
|
+
function setVerifyTlsCertificates(enabled) {
|
|
11
|
+
verifyTlsCertificates = enabled;
|
|
12
|
+
}
|
|
13
|
+
function getVerifyTlsCertificates() {
|
|
14
|
+
return verifyTlsCertificates;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=httpRuntimeOptions.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isBlockingImportError = isBlockingImportError;
|
|
4
|
+
exports.getBlockingImportErrors = getBlockingImportErrors;
|
|
5
|
+
exports.splitImportResolutionErrors = splitImportResolutionErrors;
|
|
6
|
+
const BLOCKING_IMPORT_ERROR_MARKERS = [
|
|
7
|
+
'Duplicate header group',
|
|
8
|
+
'Duplicate endpoint',
|
|
9
|
+
'Duplicate named request',
|
|
10
|
+
'Duplicate sequence',
|
|
11
|
+
];
|
|
12
|
+
function isBlockingImportError(error) {
|
|
13
|
+
return Boolean(error.blocking) || BLOCKING_IMPORT_ERROR_MARKERS.some(marker => error.error.includes(marker));
|
|
14
|
+
}
|
|
15
|
+
function getBlockingImportErrors(errors) {
|
|
16
|
+
return errors.filter(isBlockingImportError);
|
|
17
|
+
}
|
|
18
|
+
function splitImportResolutionErrors(errors) {
|
|
19
|
+
const blockingErrors = [];
|
|
20
|
+
const warningErrors = [];
|
|
21
|
+
for (const error of errors) {
|
|
22
|
+
if (isBlockingImportError(error)) {
|
|
23
|
+
blockingErrors.push(error);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
warningErrors.push(error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { blockingErrors, warningErrors };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=importErrors.js.map
|