norn-cli 2.2.2 → 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/settings.local.json +18 -0
- package/.claude/skills/norn-social-campaign/SKILL.md +70 -0
- package/CHANGELOG.md +22 -1
- package/LICENSE +20 -29
- package/README.md +32 -1
- package/demos/nornenv-region-refactor/README.md +64 -0
- package/demos/nornenv-showcase/README.md +62 -0
- package/demos/nornenv-showcase/norn.config.json +16 -0
- package/demos/nornenv-showcase/showcase.norn +70 -0
- package/demos/nornenv-showcase/showcase.nornapi +26 -0
- package/demos/nornenv-showcase/showcase.nornsql +20 -0
- package/dist/cli.js +564 -54
- 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 +38 -11
- package/.kanbn/index.md +0 -31
- package/.kanbn/tasks/book-first-mentor-session.md +0 -13
- package/.kanbn/tasks/decide-what-success-in-a-pilot-looks-like.md +0 -9
- package/.kanbn/tasks/do-5-customer-conversations.md +0 -9
- package/.kanbn/tasks/finalise-the-one-line-pitch.md +0 -11
- package/.kanbn/tasks/interview-script.md +0 -49
- package/.kanbn/tasks/make-a-list-of-10-people-to-speak-to.md +0 -11
- package/.kanbn/tasks/prepare-your-customer-interview-questions.md +0 -11
- package/.kanbn/tasks/recruit-2/342/200/2233-pilot-users.md +0 -9
- package/.kanbn/tasks/refine-your-pitch.md +0 -9
- package/.kanbn/tasks/use-the-shiplight-website-as-a-template-to-improve-norn-website.md +0 -9
- package/.kanbn/tasks/write-down-repeated-wording.md +0 -9
- package/.kanbn/tasks/write-the-one-pager.md +0 -27
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI reporters index - re-exports all reporter functions
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./junit"), exports);
|
|
21
|
+
__exportStar(require("./html"), exports);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JUnit XML reporter for CI/CD integration
|
|
4
|
+
*
|
|
5
|
+
* Generates JUnit XML format that's compatible with:
|
|
6
|
+
* - Azure DevOps (ADO)
|
|
7
|
+
* - Jenkins
|
|
8
|
+
* - GitHub Actions
|
|
9
|
+
* - GitLab CI
|
|
10
|
+
* - CircleCI
|
|
11
|
+
* - Most CI/CD platforms
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.generateJUnitReport = generateJUnitReport;
|
|
48
|
+
exports.generateJUnitReportFromResponse = generateJUnitReportFromResponse;
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const redaction_1 = require("../redaction");
|
|
51
|
+
/**
|
|
52
|
+
* Escape XML special characters
|
|
53
|
+
*/
|
|
54
|
+
function escapeXml(text) {
|
|
55
|
+
return text
|
|
56
|
+
.replace(/&/g, '&')
|
|
57
|
+
.replace(/</g, '<')
|
|
58
|
+
.replace(/>/g, '>')
|
|
59
|
+
.replace(/"/g, '"')
|
|
60
|
+
.replace(/'/g, ''');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Format duration as seconds with 3 decimal places
|
|
64
|
+
*/
|
|
65
|
+
function formatDurationSeconds(ms) {
|
|
66
|
+
return (ms / 1000).toFixed(3);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generate JUnit XML report from sequence results
|
|
70
|
+
*/
|
|
71
|
+
function generateJUnitReport(results, options) {
|
|
72
|
+
const { outputPath, redaction, suiteName = 'Norn Tests' } = options;
|
|
73
|
+
const totalTests = results.reduce((sum, r) => sum + Math.max(1, r.assertionResults?.length || 0), 0);
|
|
74
|
+
const totalFailures = results.reduce((sum, r) => sum + (r.assertionResults?.filter(a => !a.passed).length || (r.success ? 0 : 1)), 0);
|
|
75
|
+
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
|
|
76
|
+
const totalErrors = results.filter(r => r.errors.length > 0 && r.success === false).length;
|
|
77
|
+
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
78
|
+
xml += `<testsuites name="${escapeXml(suiteName)}" tests="${totalTests}" failures="${totalFailures}" errors="${totalErrors}" time="${formatDurationSeconds(totalDuration)}">\n`;
|
|
79
|
+
for (const result of results) {
|
|
80
|
+
xml += generateTestSuite(result, redaction);
|
|
81
|
+
}
|
|
82
|
+
xml += '</testsuites>\n';
|
|
83
|
+
fs.writeFileSync(outputPath, xml, 'utf-8');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Generate a testsuite element for a sequence
|
|
87
|
+
*/
|
|
88
|
+
function generateTestSuite(result, redaction) {
|
|
89
|
+
const assertions = result.assertionResults || [];
|
|
90
|
+
const testCount = Math.max(1, assertions.length);
|
|
91
|
+
const failures = assertions.filter(a => !a.passed).length + (result.success ? 0 : (assertions.length === 0 ? 1 : 0));
|
|
92
|
+
let xml = ` <testsuite name="${escapeXml(result.name)}" tests="${testCount}" failures="${failures}" errors="0" time="${formatDurationSeconds(result.duration)}">\n`;
|
|
93
|
+
if (assertions.length > 0) {
|
|
94
|
+
// Create a testcase for each assertion
|
|
95
|
+
for (const assertion of assertions) {
|
|
96
|
+
xml += generateTestCase(result.name, assertion, redaction);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// No assertions - create a single testcase for the sequence
|
|
101
|
+
xml += generateSequenceTestCase(result, redaction);
|
|
102
|
+
}
|
|
103
|
+
// Add system-out with request/response details
|
|
104
|
+
xml += generateSystemOut(result, redaction);
|
|
105
|
+
// Add system-err with errors
|
|
106
|
+
if (result.errors.length > 0) {
|
|
107
|
+
xml += ` <system-err><![CDATA[${result.errors.map(e => (0, redaction_1.redactString)(e, redaction)).join('\n')}]]></system-err>\n`;
|
|
108
|
+
}
|
|
109
|
+
xml += ' </testsuite>\n';
|
|
110
|
+
return xml;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Generate a testcase element for an assertion
|
|
114
|
+
*/
|
|
115
|
+
function generateTestCase(sequenceName, assertion, redaction) {
|
|
116
|
+
const testName = assertion.message || assertion.expression;
|
|
117
|
+
const className = sequenceName;
|
|
118
|
+
let xml = ` <testcase name="${escapeXml(testName)}" classname="${escapeXml(className)}">\n`;
|
|
119
|
+
if (!assertion.passed) {
|
|
120
|
+
const message = assertion.error || `Expected ${assertion.rightExpression || assertion.rightValue}, got ${JSON.stringify(assertion.leftValue)}`;
|
|
121
|
+
xml += ` <failure message="${escapeXml((0, redaction_1.redactString)(message, redaction))}" type="AssertionError">\n`;
|
|
122
|
+
xml += `<![CDATA[Expression: ${assertion.expression}\n`;
|
|
123
|
+
xml += `Expected: ${JSON.stringify(assertion.rightValue)}\n`;
|
|
124
|
+
xml += `Actual: ${JSON.stringify(assertion.leftValue)}]]>\n`;
|
|
125
|
+
xml += ' </failure>\n';
|
|
126
|
+
}
|
|
127
|
+
xml += ' </testcase>\n';
|
|
128
|
+
return xml;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Generate a testcase element for a sequence without assertions
|
|
132
|
+
*/
|
|
133
|
+
function generateSequenceTestCase(result, redaction) {
|
|
134
|
+
let xml = ` <testcase name="${escapeXml(result.name)}" classname="${escapeXml(result.name)}" time="${formatDurationSeconds(result.duration)}">\n`;
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
const errorMessages = result.errors.length > 0
|
|
137
|
+
? result.errors.map(e => (0, redaction_1.redactString)(e, redaction)).join('; ')
|
|
138
|
+
: 'Sequence failed';
|
|
139
|
+
xml += ` <failure message="${escapeXml(errorMessages)}" type="SequenceError">\n`;
|
|
140
|
+
xml += `<![CDATA[${errorMessages}]]>\n`;
|
|
141
|
+
xml += ' </failure>\n';
|
|
142
|
+
}
|
|
143
|
+
xml += ' </testcase>\n';
|
|
144
|
+
return xml;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Generate system-out with request/response details
|
|
148
|
+
*/
|
|
149
|
+
function generateSystemOut(result, redaction) {
|
|
150
|
+
const lines = [];
|
|
151
|
+
for (const step of result.steps) {
|
|
152
|
+
if (step.type === 'request' && step.response) {
|
|
153
|
+
const url = step.requestUrl ? (0, redaction_1.redactUrl)(step.requestUrl, redaction) : 'unknown';
|
|
154
|
+
lines.push(`[${step.requestMethod || 'REQUEST'}] ${url}`);
|
|
155
|
+
lines.push(` Status: ${step.response.status} ${step.response.statusText}`);
|
|
156
|
+
lines.push(` Duration: ${step.response.duration}ms`);
|
|
157
|
+
if (step.response.body) {
|
|
158
|
+
const bodyStr = typeof step.response.body === 'object'
|
|
159
|
+
? JSON.stringify((0, redaction_1.redactBody)(step.response.body, redaction), null, 2)
|
|
160
|
+
: (0, redaction_1.redactString)(String(step.response.body), redaction);
|
|
161
|
+
// Truncate long bodies
|
|
162
|
+
if (bodyStr.length > 1000) {
|
|
163
|
+
lines.push(` Body: ${bodyStr.substring(0, 1000)}... (truncated)`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
lines.push(` Body: ${bodyStr}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
lines.push('');
|
|
170
|
+
}
|
|
171
|
+
else if (step.type === 'print' && step.print) {
|
|
172
|
+
lines.push(`[PRINT] ${step.print.title}`);
|
|
173
|
+
if (step.print.body) {
|
|
174
|
+
lines.push(` ${step.print.body}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (step.type === 'mcp' && step.mcp) {
|
|
178
|
+
if (step.mcp.operation === 'list') {
|
|
179
|
+
lines.push(`[MCP LIST] ${step.mcp.serverAlias}`);
|
|
180
|
+
lines.push(` Tools: ${(step.mcp.tools || []).map(tool => tool.name).join(', ')}`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
lines.push(`[MCP CALL] ${step.mcp.serverAlias}.${step.mcp.toolName || ''}`);
|
|
184
|
+
if (step.mcp.result?.text) {
|
|
185
|
+
lines.push(` Text: ${(0, redaction_1.redactString)(step.mcp.result.text, redaction)}`);
|
|
186
|
+
}
|
|
187
|
+
if (step.mcp.result?.structuredContent) {
|
|
188
|
+
lines.push(` Structured: ${JSON.stringify(step.mcp.result.structuredContent)}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (lines.length === 0) {
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
return ` <system-out><![CDATA[${lines.join('\n')}]]></system-out>\n`;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Generate JUnit report from a single HTTP response (for non-sequence runs)
|
|
200
|
+
*/
|
|
201
|
+
function generateJUnitReportFromResponse(response, testName, options) {
|
|
202
|
+
const { outputPath, redaction, suiteName = 'Norn Tests' } = options;
|
|
203
|
+
const isSuccess = response.status >= 200 && response.status < 300;
|
|
204
|
+
const failures = isSuccess ? 0 : 1;
|
|
205
|
+
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
206
|
+
xml += `<testsuites name="${escapeXml(suiteName)}" tests="1" failures="${failures}" errors="0" time="${formatDurationSeconds(response.duration)}">\n`;
|
|
207
|
+
xml += ` <testsuite name="${escapeXml(testName)}" tests="1" failures="${failures}" errors="0" time="${formatDurationSeconds(response.duration)}">\n`;
|
|
208
|
+
xml += ` <testcase name="${escapeXml(testName)}" classname="${escapeXml(testName)}" time="${formatDurationSeconds(response.duration)}">\n`;
|
|
209
|
+
if (!isSuccess) {
|
|
210
|
+
xml += ` <failure message="HTTP ${response.status} ${response.statusText}" type="HttpError">\n`;
|
|
211
|
+
xml += `<![CDATA[Status: ${response.status} ${response.statusText}`;
|
|
212
|
+
if (response.body) {
|
|
213
|
+
const bodyStr = typeof response.body === 'object'
|
|
214
|
+
? JSON.stringify((0, redaction_1.redactBody)(response.body, redaction), null, 2)
|
|
215
|
+
: (0, redaction_1.redactString)(String(response.body), redaction);
|
|
216
|
+
xml += `\nResponse: ${bodyStr}`;
|
|
217
|
+
}
|
|
218
|
+
xml += ']]>\n';
|
|
219
|
+
xml += ' </failure>\n';
|
|
220
|
+
}
|
|
221
|
+
xml += ' </testcase>\n';
|
|
222
|
+
xml += ' </testsuite>\n';
|
|
223
|
+
xml += '</testsuites>\n';
|
|
224
|
+
fs.writeFileSync(outputPath, xml, 'utf-8');
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=junit.js.map
|
|
@@ -0,0 +1,351 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.HttpCodeLensProvider = void 0;
|
|
37
|
+
exports.updateCodeLensCoverage = updateCodeLensCoverage;
|
|
38
|
+
const vscode = __importStar(require("vscode"));
|
|
39
|
+
const environmentProvider_1 = require("./environmentProvider");
|
|
40
|
+
const envFileSecrets_1 = require("./secrets/envFileSecrets");
|
|
41
|
+
const crypto_1 = require("./secrets/crypto");
|
|
42
|
+
const environmentParser_1 = require("./environmentParser");
|
|
43
|
+
const nornenvRegionRefactor_1 = require("./nornenvRegionRefactor");
|
|
44
|
+
// Cached coverage percentage for CodeLens display
|
|
45
|
+
/**
|
|
46
|
+
* Update the cached coverage for CodeLens display
|
|
47
|
+
*/
|
|
48
|
+
function updateCodeLensCoverage(percentage, total, covered) {
|
|
49
|
+
void percentage;
|
|
50
|
+
void total;
|
|
51
|
+
void covered;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Checks if a sequence declaration has only optional parameters (all have defaults).
|
|
55
|
+
* Returns true if no parameters or all parameters have defaults.
|
|
56
|
+
*/
|
|
57
|
+
function canRunSequenceWithoutArgs(line) {
|
|
58
|
+
// Check if there are parameters
|
|
59
|
+
const parenMatch = line.match(/\(([^)]*)\)/);
|
|
60
|
+
if (!parenMatch) {
|
|
61
|
+
// No parameters - can run
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const paramsStr = parenMatch[1].trim();
|
|
65
|
+
if (!paramsStr) {
|
|
66
|
+
// Empty parens - can run
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
// Split by comma (simple split, not handling quoted strings with commas)
|
|
70
|
+
const params = paramsStr.split(',');
|
|
71
|
+
// Check each parameter has a default (contains =)
|
|
72
|
+
for (const param of params) {
|
|
73
|
+
const trimmed = param.trim();
|
|
74
|
+
if (!trimmed) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Must contain = for a default value
|
|
78
|
+
if (!trimmed.includes('=')) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Checks for @data(...) or @theory(...) annotations directly above a sequence declaration.
|
|
86
|
+
* This allows CodeLens execution for parameterized sequences with required parameters.
|
|
87
|
+
*/
|
|
88
|
+
function hasTheoryAnnotationsAbove(document, sequenceLine) {
|
|
89
|
+
const dataOrTheoryPattern = /^@(data|theory)\s*\(/;
|
|
90
|
+
const decoratorPattern = /^@/;
|
|
91
|
+
for (let i = sequenceLine - 1; i >= 0; i--) {
|
|
92
|
+
const line = document.lineAt(i).text.trim();
|
|
93
|
+
// Allow blank lines between decorators and sequence declaration
|
|
94
|
+
if (!line) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// Stop as soon as the decorator block ends
|
|
98
|
+
if (!decoratorPattern.test(line)) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
if (dataOrTheoryPattern.test(line)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
class HttpCodeLensProvider {
|
|
108
|
+
_onDidChangeCodeLenses = new vscode.EventEmitter();
|
|
109
|
+
onDidChangeCodeLenses = this._onDidChangeCodeLenses.event;
|
|
110
|
+
constructor() {
|
|
111
|
+
// Refresh code lenses when environment changes
|
|
112
|
+
vscode.commands.registerCommand('norn.refreshCodeLenses', () => {
|
|
113
|
+
this._onDidChangeCodeLenses.fire();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Gets the environment display text for CodeLens
|
|
118
|
+
* Returns 'add' if no env file exists, environment name if it does
|
|
119
|
+
*/
|
|
120
|
+
getEnvDisplay(documentPath) {
|
|
121
|
+
const envFile = (0, environmentProvider_1.findEnvFileFromPath)(documentPath);
|
|
122
|
+
if (!envFile) {
|
|
123
|
+
return { text: '+ Add env file', hasEnvFile: false };
|
|
124
|
+
}
|
|
125
|
+
const activeEnv = (0, environmentProvider_1.getActiveEnvironment)(documentPath);
|
|
126
|
+
const availableEnvs = (0, environmentProvider_1.getAvailableEnvironments)(envFile);
|
|
127
|
+
if (availableEnvs.length === 0) {
|
|
128
|
+
return { text: 'env: (empty)', hasEnvFile: true };
|
|
129
|
+
}
|
|
130
|
+
return { text: activeEnv ? `env: ${activeEnv}` : 'env: none', hasEnvFile: true };
|
|
131
|
+
}
|
|
132
|
+
provideCodeLenses(document) {
|
|
133
|
+
const codeLenses = [];
|
|
134
|
+
const methodRegex = /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+/;
|
|
135
|
+
// Match sequence (with optional test keyword) and optional parameters
|
|
136
|
+
const sequenceRegex = /^(?:test\s+)?sequence\s+([a-zA-Z_][a-zA-Z0-9_-]*)(?:\s*\([^)]*\))?$/;
|
|
137
|
+
// Match swagger statement with optional quotes around URL (only in .nornapi files)
|
|
138
|
+
const swaggerRegex = /^swagger\s+["']?(https?:\/\/[^\s"']+)["']?\s*$/;
|
|
139
|
+
// Match matchesSchema assertions: assert $1.body matchesSchema "./schema.json"
|
|
140
|
+
// Note: .+? is non-greedy to not consume matchesSchema
|
|
141
|
+
const matchesSchemaRegex = /^\s*assert\s+.+?\s+matchesSchema\s+["']?([^"']+)["']?/i;
|
|
142
|
+
const envInfo = this.getEnvDisplay(document.uri.fsPath);
|
|
143
|
+
const isNornApiFile = document.languageId === 'nornapi';
|
|
144
|
+
const isNornFile = document.languageId === 'norn';
|
|
145
|
+
const isNornenvFile = (0, envFileSecrets_1.isNornenvDocumentLike)(document.languageId, document.uri.fsPath);
|
|
146
|
+
const secretLinesByNumber = isNornenvFile
|
|
147
|
+
? new Map((0, envFileSecrets_1.extractSecretLines)(document.getText(), document.uri.fsPath).map(secret => [secret.lineNumber, secret]))
|
|
148
|
+
: new Map();
|
|
149
|
+
const nornenvConfig = isNornenvFile ? (0, environmentParser_1.parseEnvFile)(document.getText(), document.uri.fsPath) : undefined;
|
|
150
|
+
// Region-pattern refactor — single top-of-file lens when a flat S × R matrix is detected.
|
|
151
|
+
if (isNornenvFile && nornenvConfig) {
|
|
152
|
+
const pattern = (0, nornenvRegionRefactor_1.detectRegionPattern)(nornenvConfig, document.getText());
|
|
153
|
+
if (pattern) {
|
|
154
|
+
const { liftedToStage, liftedToRegion, leafSpecific } = pattern.summary;
|
|
155
|
+
const topRange = new vscode.Range(0, 0, 0, 0);
|
|
156
|
+
codeLenses.push(new vscode.CodeLens(topRange, {
|
|
157
|
+
title: `$(sparkle) Refactor ${pattern.cells.length} envs into ${pattern.stages.length}+${pattern.regions.length} templates`,
|
|
158
|
+
command: 'norn.nornenv.refactorRegionPattern',
|
|
159
|
+
arguments: [document.uri.fsPath],
|
|
160
|
+
tooltip: `Detected an S × R pattern (${pattern.stages.join(', ')} × ${pattern.regions.join(', ')}). `
|
|
161
|
+
+ `Refactor will lift ${liftedToStage} vars to stage templates, ${liftedToRegion} to region templates, and keep ${leafSpecific} as leaf-specific.`
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Track if we're inside a sequence
|
|
166
|
+
let insideSequence = false;
|
|
167
|
+
for (let i = 0; i < document.lineCount; i++) {
|
|
168
|
+
const line = document.lineAt(i);
|
|
169
|
+
const trimmedText = line.text.trim();
|
|
170
|
+
if (isNornenvFile) {
|
|
171
|
+
const headerMatch = trimmedText.match(/^\[env:([a-zA-Z_][a-zA-Z0-9_-]*)(?:\s+extends\s+([^\]]+))?\]\s*$/i);
|
|
172
|
+
if (headerMatch) {
|
|
173
|
+
const envName = headerMatch[1];
|
|
174
|
+
const parents = (headerMatch[2] ?? '')
|
|
175
|
+
.split(',')
|
|
176
|
+
.map(parent => parent.trim())
|
|
177
|
+
.filter(parent => parent.length > 0);
|
|
178
|
+
const range = new vscode.Range(i, 0, i, line.text.length);
|
|
179
|
+
const activeEnv = (0, environmentProvider_1.getActiveEnvironment)(document.uri.fsPath);
|
|
180
|
+
if (activeEnv !== envName) {
|
|
181
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
182
|
+
title: '$(debug-start) Activate',
|
|
183
|
+
command: 'norn.nornenv.activate',
|
|
184
|
+
arguments: [envName, document.uri.fsPath],
|
|
185
|
+
tooltip: `Use '${envName}' as the active Norn environment`
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
if (parents.length > 0) {
|
|
189
|
+
const inheritedCount = nornenvConfig
|
|
190
|
+
? (0, environmentParser_1.resolveInheritedVariableDetails)(envName, nornenvConfig).size
|
|
191
|
+
: 0;
|
|
192
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
193
|
+
title: `$(list-tree) Peek inherited (${inheritedCount})`,
|
|
194
|
+
command: 'norn.nornenv.peekInherited',
|
|
195
|
+
arguments: [envName, document.uri.fsPath],
|
|
196
|
+
tooltip: `Show variables inherited by '${envName}'`
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const secret = secretLinesByNumber.get(i);
|
|
202
|
+
if (!secret) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const range = new vscode.Range(i, 0, i, line.text.length);
|
|
206
|
+
if (!secret.encrypted) {
|
|
207
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
208
|
+
title: '$(lock) Encrypt Secret',
|
|
209
|
+
command: 'norn.encryptSecretAtLine',
|
|
210
|
+
arguments: [document.uri.fsPath, i],
|
|
211
|
+
tooltip: 'Encrypt this secret in-place'
|
|
212
|
+
}));
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const parsed = (0, crypto_1.parseEncryptedSecretValue)(secret.value);
|
|
216
|
+
const hasValidCiphertext = parsed.ok;
|
|
217
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
218
|
+
title: '$(eye) View Decrypted',
|
|
219
|
+
command: 'norn.viewDecryptedSecretAtLine',
|
|
220
|
+
arguments: [document.uri.fsPath, i],
|
|
221
|
+
tooltip: 'Reveal decrypted value in a temporary editor'
|
|
222
|
+
}));
|
|
223
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
224
|
+
title: '$(sync) Rotate Secret',
|
|
225
|
+
command: 'norn.rotateSecretAtLine',
|
|
226
|
+
arguments: [document.uri.fsPath, i],
|
|
227
|
+
tooltip: hasValidCiphertext
|
|
228
|
+
? 'Replace with a new encrypted value'
|
|
229
|
+
: 'Replace malformed secret with a new encrypted value'
|
|
230
|
+
}));
|
|
231
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
232
|
+
title: '$(trash) Delete Secret',
|
|
233
|
+
command: 'norn.deleteSecretAtLine',
|
|
234
|
+
arguments: [document.uri.fsPath, i],
|
|
235
|
+
tooltip: 'Remove this secret declaration line'
|
|
236
|
+
}));
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
// Check for swagger statement (only in .nornapi files)
|
|
240
|
+
if (isNornApiFile) {
|
|
241
|
+
const swaggerMatch = trimmedText.match(swaggerRegex);
|
|
242
|
+
if (swaggerMatch) {
|
|
243
|
+
const range = new vscode.Range(i, 0, i, line.text.length);
|
|
244
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
245
|
+
title: '$(debug-start) Import Endpoints',
|
|
246
|
+
command: 'norn.importSwagger',
|
|
247
|
+
arguments: [swaggerMatch[1], i],
|
|
248
|
+
tooltip: `Parse OpenAPI spec and generate endpoints`
|
|
249
|
+
}));
|
|
250
|
+
// Add Generate Schemas CodeLens
|
|
251
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
252
|
+
title: '$(file-code) Generate Schemas',
|
|
253
|
+
command: 'norn.generateSchemasFromSwagger',
|
|
254
|
+
arguments: [swaggerMatch[1], document.uri.fsPath],
|
|
255
|
+
tooltip: 'Generate JSON Schema files from OpenAPI response definitions'
|
|
256
|
+
}));
|
|
257
|
+
// Add file-scoped coverage CodeLens
|
|
258
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
259
|
+
title: '$(graph) Show Coverage',
|
|
260
|
+
command: 'norn.showCoverage',
|
|
261
|
+
arguments: [document.uri.fsPath],
|
|
262
|
+
tooltip: 'Show API coverage for this .nornapi file (includes this folder and subfolders only).'
|
|
263
|
+
}));
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Check for matchesSchema assertions (in .norn files)
|
|
268
|
+
if (isNornFile) {
|
|
269
|
+
const schemaMatch = trimmedText.match(matchesSchemaRegex);
|
|
270
|
+
if (schemaMatch) {
|
|
271
|
+
const schemaPath = schemaMatch[1];
|
|
272
|
+
const range = new vscode.Range(i, 0, i, line.text.length);
|
|
273
|
+
// Open Contract lens
|
|
274
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
275
|
+
title: '$(file-code) Open Contract',
|
|
276
|
+
command: 'norn.openSchemaFile',
|
|
277
|
+
arguments: [schemaPath, document.uri.fsPath],
|
|
278
|
+
tooltip: `Open schema file: ${schemaPath}`
|
|
279
|
+
}));
|
|
280
|
+
// View Contract Report lens
|
|
281
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
282
|
+
title: '$(checklist) View Contract',
|
|
283
|
+
command: 'norn.viewContractReport',
|
|
284
|
+
arguments: [i, document.uri.fsPath, schemaPath],
|
|
285
|
+
tooltip: 'Run validation and show contract comparison report'
|
|
286
|
+
}));
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Check for sequence start
|
|
291
|
+
const sequenceMatch = trimmedText.match(sequenceRegex);
|
|
292
|
+
if (sequenceMatch) {
|
|
293
|
+
insideSequence = true;
|
|
294
|
+
const range = new vscode.Range(i, 0, i, line.text.length);
|
|
295
|
+
// Show Run Sequence if parameters are all optional, or values are provided via @data/@theory.
|
|
296
|
+
const canRunWithoutArgs = canRunSequenceWithoutArgs(trimmedText);
|
|
297
|
+
const hasTheoryAnnotations = hasTheoryAnnotationsAbove(document, i);
|
|
298
|
+
if (canRunWithoutArgs || hasTheoryAnnotations) {
|
|
299
|
+
// Run Sequence lens
|
|
300
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
301
|
+
title: '▶ Run Sequence',
|
|
302
|
+
command: 'norn.runSequence',
|
|
303
|
+
arguments: [i],
|
|
304
|
+
tooltip: `Run all requests in sequence "${sequenceMatch[1]}"`
|
|
305
|
+
}));
|
|
306
|
+
// Debug Sequence lens
|
|
307
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
308
|
+
title: '▷ Debug Sequence',
|
|
309
|
+
command: 'norn.debugSequence',
|
|
310
|
+
arguments: [i, document.uri.fsPath, sequenceMatch[1]],
|
|
311
|
+
tooltip: `Debug sequence "${sequenceMatch[1]}" with breakpoints and stepping`
|
|
312
|
+
}));
|
|
313
|
+
// Environment lens
|
|
314
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
315
|
+
title: envInfo.text,
|
|
316
|
+
command: envInfo.hasEnvFile ? 'norn.selectEnvironmentAndRefresh' : 'norn.createEnvFile',
|
|
317
|
+
arguments: envInfo.hasEnvFile ? [document.uri.fsPath] : undefined,
|
|
318
|
+
tooltip: envInfo.hasEnvFile ? 'Click to change environment' : 'Click to create .nornenv file'
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
// Check for sequence end
|
|
324
|
+
if (trimmedText === 'end sequence') {
|
|
325
|
+
insideSequence = false;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
// Only show "Send Request" for requests OUTSIDE of sequences
|
|
329
|
+
if (!insideSequence && methodRegex.test(line.text)) {
|
|
330
|
+
const range = new vscode.Range(i, 0, i, line.text.length);
|
|
331
|
+
// Send Request lens
|
|
332
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
333
|
+
title: '▶ Send Request',
|
|
334
|
+
command: 'norn.sendRequest',
|
|
335
|
+
arguments: [i],
|
|
336
|
+
tooltip: 'Send this HTTP request'
|
|
337
|
+
}));
|
|
338
|
+
// Environment lens
|
|
339
|
+
codeLenses.push(new vscode.CodeLens(range, {
|
|
340
|
+
title: envInfo.text,
|
|
341
|
+
command: envInfo.hasEnvFile ? 'norn.selectEnvironmentAndRefresh' : 'norn.createEnvFile',
|
|
342
|
+
arguments: envInfo.hasEnvFile ? [document.uri.fsPath] : undefined,
|
|
343
|
+
tooltip: envInfo.hasEnvFile ? 'Click to change environment' : 'Click to create .nornenv file'
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return codeLenses;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
exports.HttpCodeLensProvider = HttpCodeLensProvider;
|
|
351
|
+
//# sourceMappingURL=codeLensProvider.js.map
|