norn-cli 1.3.16 → 1.3.18
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/AGENTS.md +72 -0
- package/CHANGELOG.md +34 -1
- package/README.md +4 -2
- package/dist/cli.js +135 -63
- package/out/assertionRunner.js +537 -0
- package/out/cli/colors.js +129 -0
- package/out/cli/formatters/assertion.js +75 -0
- package/out/cli/formatters/index.js +23 -0
- package/out/cli/formatters/response.js +106 -0
- package/out/cli/formatters/summary.js +187 -0
- package/out/cli/redaction.js +237 -0
- package/out/cli/reporters/html.js +634 -0
- package/out/cli/reporters/index.js +22 -0
- package/out/cli/reporters/junit.js +211 -0
- package/out/cli.js +926 -0
- package/out/codeLensProvider.js +254 -0
- package/out/compareContentProvider.js +85 -0
- package/out/completionProvider.js +1886 -0
- package/out/contractDecorationProvider.js +243 -0
- package/out/coverageCalculator.js +756 -0
- package/out/coveragePanel.js +542 -0
- package/out/diagnosticProvider.js +980 -0
- package/out/environmentProvider.js +373 -0
- package/out/extension.js +1025 -0
- package/out/httpClient.js +269 -0
- package/out/jsonFileReader.js +320 -0
- package/out/nornapiParser.js +326 -0
- package/out/parser.js +725 -0
- package/out/responsePanel.js +4674 -0
- package/out/schemaGenerator.js +393 -0
- package/out/scriptRunner.js +419 -0
- package/out/sequenceRunner.js +3046 -0
- package/out/swaggerParser.js +339 -0
- package/out/test/extension.test.js +48 -0
- package/out/testProvider.js +658 -0
- package/out/validationCache.js +245 -0
- package/package.json +1 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getCachedSwaggerSpec = getCachedSwaggerSpec;
|
|
7
|
+
exports.clearSwaggerCache = clearSwaggerCache;
|
|
8
|
+
exports.invalidateSwaggerCache = invalidateSwaggerCache;
|
|
9
|
+
exports.parseSwaggerSpec = parseSwaggerSpec;
|
|
10
|
+
exports.generateNornapiContent = generateNornapiContent;
|
|
11
|
+
exports.extractResponseSchemas = extractResponseSchemas;
|
|
12
|
+
exports.generateSchemaFilename = generateSchemaFilename;
|
|
13
|
+
const axios_1 = __importDefault(require("axios"));
|
|
14
|
+
/**
|
|
15
|
+
* Cache for parsed swagger specs (cleared when extension deactivates)
|
|
16
|
+
*/
|
|
17
|
+
const swaggerCache = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Get a cached swagger spec, or fetch and parse if not cached
|
|
20
|
+
*/
|
|
21
|
+
async function getCachedSwaggerSpec(url) {
|
|
22
|
+
if (swaggerCache.has(url)) {
|
|
23
|
+
return swaggerCache.get(url);
|
|
24
|
+
}
|
|
25
|
+
const spec = await parseSwaggerSpec(url);
|
|
26
|
+
swaggerCache.set(url, spec);
|
|
27
|
+
return spec;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Clear the swagger cache (call on extension deactivation)
|
|
31
|
+
*/
|
|
32
|
+
function clearSwaggerCache() {
|
|
33
|
+
swaggerCache.clear();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Invalidate a specific URL from the cache (for manual refresh)
|
|
37
|
+
*/
|
|
38
|
+
function invalidateSwaggerCache(url) {
|
|
39
|
+
if (url) {
|
|
40
|
+
swaggerCache.delete(url);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
swaggerCache.clear();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Fetches and parses an OpenAPI/Swagger specification from a URL.
|
|
48
|
+
*/
|
|
49
|
+
async function parseSwaggerSpec(url) {
|
|
50
|
+
// Fetch the spec
|
|
51
|
+
const response = await axios_1.default.get(url, {
|
|
52
|
+
headers: {
|
|
53
|
+
'Accept': 'application/json'
|
|
54
|
+
},
|
|
55
|
+
timeout: 30000
|
|
56
|
+
});
|
|
57
|
+
const spec = response.data;
|
|
58
|
+
// Determine if it's OpenAPI 3.x or Swagger 2.0
|
|
59
|
+
const isOpenAPI3 = spec.openapi && spec.openapi.startsWith('3.');
|
|
60
|
+
// Extract base URL
|
|
61
|
+
let baseUrl = '';
|
|
62
|
+
if (isOpenAPI3) {
|
|
63
|
+
// OpenAPI 3.x uses servers array
|
|
64
|
+
if (spec.servers && spec.servers.length > 0) {
|
|
65
|
+
baseUrl = spec.servers[0].url;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Swagger 2.0 uses host + basePath + schemes
|
|
70
|
+
const scheme = spec.schemes?.[0] || 'https';
|
|
71
|
+
const host = spec.host || '';
|
|
72
|
+
const basePath = spec.basePath || '';
|
|
73
|
+
if (host) {
|
|
74
|
+
baseUrl = `${scheme}://${host}${basePath}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Extract title and version
|
|
78
|
+
const title = spec.info?.title || 'API';
|
|
79
|
+
const version = spec.info?.version || '1.0.0';
|
|
80
|
+
// Group endpoints by tag
|
|
81
|
+
const sectionMap = new Map();
|
|
82
|
+
// Parse paths
|
|
83
|
+
const paths = spec.paths || {};
|
|
84
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
85
|
+
const pathObj = pathItem;
|
|
86
|
+
for (const method of ['get', 'post', 'put', 'delete', 'patch', 'head', 'options']) {
|
|
87
|
+
const operation = pathObj[method];
|
|
88
|
+
if (!operation)
|
|
89
|
+
continue;
|
|
90
|
+
// Get operation details
|
|
91
|
+
const operationId = operation.operationId || generateOperationId(method, path);
|
|
92
|
+
const tags = operation.tags || ['default'];
|
|
93
|
+
const summary = operation.summary;
|
|
94
|
+
// Extract path parameters
|
|
95
|
+
const parameters = [];
|
|
96
|
+
const pathParams = path.match(/\{([^}]+)\}/g);
|
|
97
|
+
if (pathParams) {
|
|
98
|
+
for (const param of pathParams) {
|
|
99
|
+
parameters.push(param.replace(/[{}]/g, ''));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Also check query parameters that are required
|
|
103
|
+
const paramDefs = operation.parameters;
|
|
104
|
+
if (paramDefs) {
|
|
105
|
+
for (const param of paramDefs) {
|
|
106
|
+
if (param.in === 'query' && param.required) {
|
|
107
|
+
parameters.push(param.name);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Extract response codes from operation.responses
|
|
112
|
+
const responseCodes = [];
|
|
113
|
+
const responses = operation.responses;
|
|
114
|
+
if (responses) {
|
|
115
|
+
for (const code of Object.keys(responses)) {
|
|
116
|
+
// Include numeric status codes (skip 'default')
|
|
117
|
+
if (/^\d{3}$/.test(code)) {
|
|
118
|
+
responseCodes.push(code);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Sort numerically
|
|
122
|
+
responseCodes.sort((a, b) => parseInt(a) - parseInt(b));
|
|
123
|
+
}
|
|
124
|
+
const endpoint = {
|
|
125
|
+
name: sanitizeOperationId(operationId),
|
|
126
|
+
method: method.toUpperCase(),
|
|
127
|
+
path,
|
|
128
|
+
parameters,
|
|
129
|
+
tag: tags[0] || 'default',
|
|
130
|
+
summary,
|
|
131
|
+
responseCodes: responseCodes.length > 0 ? responseCodes : ['200'] // Default to 200 if no responses defined
|
|
132
|
+
};
|
|
133
|
+
// Add to section
|
|
134
|
+
const tag = endpoint.tag;
|
|
135
|
+
if (!sectionMap.has(tag)) {
|
|
136
|
+
sectionMap.set(tag, []);
|
|
137
|
+
}
|
|
138
|
+
sectionMap.get(tag).push(endpoint);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Convert to sections array
|
|
142
|
+
const sections = [];
|
|
143
|
+
// Get tag descriptions if available
|
|
144
|
+
const tagDescriptions = new Map();
|
|
145
|
+
if (spec.tags) {
|
|
146
|
+
for (const tag of spec.tags) {
|
|
147
|
+
if (tag.description) {
|
|
148
|
+
tagDescriptions.set(tag.name, tag.description);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const [name, endpoints] of sectionMap) {
|
|
153
|
+
sections.push({
|
|
154
|
+
name,
|
|
155
|
+
description: tagDescriptions.get(name),
|
|
156
|
+
endpoints
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Sort sections alphabetically
|
|
160
|
+
sections.sort((a, b) => a.name.localeCompare(b.name));
|
|
161
|
+
return {
|
|
162
|
+
title,
|
|
163
|
+
version,
|
|
164
|
+
baseUrl,
|
|
165
|
+
sections
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Generate an operation ID from method and path if not provided
|
|
170
|
+
*/
|
|
171
|
+
function generateOperationId(method, path) {
|
|
172
|
+
// Convert /pet/{petId} to getPetByPetId
|
|
173
|
+
const parts = path.split('/').filter(p => p && !p.startsWith('{'));
|
|
174
|
+
const pathName = parts.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join('');
|
|
175
|
+
return method.toLowerCase() + pathName;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Sanitize operation ID to be a valid identifier
|
|
179
|
+
*/
|
|
180
|
+
function sanitizeOperationId(operationId) {
|
|
181
|
+
// Remove invalid characters and ensure it starts with a letter
|
|
182
|
+
let sanitized = operationId
|
|
183
|
+
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
184
|
+
.replace(/_+/g, '_')
|
|
185
|
+
.replace(/^_+|_+$/g, '');
|
|
186
|
+
// Ensure it starts with a capital letter
|
|
187
|
+
if (sanitized && /^[a-z]/.test(sanitized)) {
|
|
188
|
+
sanitized = sanitized.charAt(0).toUpperCase() + sanitized.slice(1);
|
|
189
|
+
}
|
|
190
|
+
return sanitized || 'Endpoint';
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Generate .nornapi content for selected sections
|
|
194
|
+
*/
|
|
195
|
+
function generateNornapiContent(spec, selectedSections, customBaseUrl) {
|
|
196
|
+
const lines = [];
|
|
197
|
+
const baseUrl = customBaseUrl || spec.baseUrl;
|
|
198
|
+
// Add endpoints section
|
|
199
|
+
lines.push('endpoints');
|
|
200
|
+
const sectionsToInclude = selectedSections.includes('All')
|
|
201
|
+
? spec.sections
|
|
202
|
+
: spec.sections.filter(s => selectedSections.includes(s.name));
|
|
203
|
+
for (const section of sectionsToInclude) {
|
|
204
|
+
// Add section comment
|
|
205
|
+
lines.push(` # ${section.name}${section.description ? ` - ${section.description}` : ''}`);
|
|
206
|
+
for (const endpoint of section.endpoints) {
|
|
207
|
+
// Build the endpoint line with explicit method for full parser/editor support
|
|
208
|
+
const path = endpoint.path;
|
|
209
|
+
lines.push(` ${endpoint.name}: ${endpoint.method} ${baseUrl}${path}`);
|
|
210
|
+
}
|
|
211
|
+
lines.push('');
|
|
212
|
+
}
|
|
213
|
+
lines.push('end endpoints');
|
|
214
|
+
return lines.join('\n');
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Extract response schemas from an OpenAPI/Swagger spec
|
|
218
|
+
* Resolves $ref references to inline schemas
|
|
219
|
+
* @param url URL of the OpenAPI spec
|
|
220
|
+
* @returns Array of response schemas
|
|
221
|
+
*/
|
|
222
|
+
async function extractResponseSchemas(url) {
|
|
223
|
+
const response = await axios_1.default.get(url, {
|
|
224
|
+
headers: { 'Accept': 'application/json' },
|
|
225
|
+
timeout: 30000
|
|
226
|
+
});
|
|
227
|
+
const spec = response.data;
|
|
228
|
+
const schemas = [];
|
|
229
|
+
const isOpenAPI3 = spec.openapi && spec.openapi.startsWith('3.');
|
|
230
|
+
// Helper to resolve $ref references
|
|
231
|
+
const resolveRef = (ref, root) => {
|
|
232
|
+
// $ref format: "#/components/schemas/Pet" or "#/definitions/Pet"
|
|
233
|
+
if (!ref.startsWith('#/')) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const parts = ref.slice(2).split('/');
|
|
237
|
+
let current = root;
|
|
238
|
+
for (const part of parts) {
|
|
239
|
+
if (current === undefined)
|
|
240
|
+
return undefined;
|
|
241
|
+
current = current[part];
|
|
242
|
+
}
|
|
243
|
+
return current;
|
|
244
|
+
};
|
|
245
|
+
// Recursively resolve all $ref in a schema
|
|
246
|
+
const resolveSchemaRefs = (schema, root, visited = new Set()) => {
|
|
247
|
+
if (!schema || typeof schema !== 'object') {
|
|
248
|
+
return schema;
|
|
249
|
+
}
|
|
250
|
+
// Handle $ref
|
|
251
|
+
if (schema.$ref && typeof schema.$ref === 'string') {
|
|
252
|
+
if (visited.has(schema.$ref)) {
|
|
253
|
+
// Circular reference - return simple object type
|
|
254
|
+
return { type: 'object' };
|
|
255
|
+
}
|
|
256
|
+
visited.add(schema.$ref);
|
|
257
|
+
const resolved = resolveRef(schema.$ref, root);
|
|
258
|
+
if (resolved) {
|
|
259
|
+
return resolveSchemaRefs(resolved, root, visited);
|
|
260
|
+
}
|
|
261
|
+
return schema;
|
|
262
|
+
}
|
|
263
|
+
// Recursively resolve properties, items, etc.
|
|
264
|
+
const result = Array.isArray(schema) ? [] : {};
|
|
265
|
+
for (const key of Object.keys(schema)) {
|
|
266
|
+
if (key === '$ref')
|
|
267
|
+
continue;
|
|
268
|
+
const value = schema[key];
|
|
269
|
+
if (typeof value === 'object' && value !== null) {
|
|
270
|
+
result[key] = resolveSchemaRefs(value, root, new Set(visited));
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
result[key] = value;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
};
|
|
278
|
+
// Parse paths
|
|
279
|
+
const paths = spec.paths || {};
|
|
280
|
+
for (const [pathStr, pathItem] of Object.entries(paths)) {
|
|
281
|
+
const pathObj = pathItem;
|
|
282
|
+
for (const method of ['get', 'post', 'put', 'delete', 'patch', 'head', 'options']) {
|
|
283
|
+
const operation = pathObj[method];
|
|
284
|
+
if (!operation)
|
|
285
|
+
continue;
|
|
286
|
+
const operationId = operation.operationId || generateOperationId(method, pathStr);
|
|
287
|
+
const responses = operation.responses;
|
|
288
|
+
if (!responses)
|
|
289
|
+
continue;
|
|
290
|
+
for (const [statusCode, responseObj] of Object.entries(responses)) {
|
|
291
|
+
// Skip non-numeric status codes like 'default'
|
|
292
|
+
if (!/^\d{3}$/.test(statusCode))
|
|
293
|
+
continue;
|
|
294
|
+
let schema = null;
|
|
295
|
+
if (isOpenAPI3) {
|
|
296
|
+
// OpenAPI 3.x: responses[code].content["application/json"].schema
|
|
297
|
+
const content = responseObj.content;
|
|
298
|
+
if (content) {
|
|
299
|
+
const jsonContent = content['application/json'] || content['*/*'];
|
|
300
|
+
if (jsonContent?.schema) {
|
|
301
|
+
schema = jsonContent.schema;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
// Swagger 2.0: responses[code].schema
|
|
307
|
+
if (responseObj.schema) {
|
|
308
|
+
schema = responseObj.schema;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (schema) {
|
|
312
|
+
// Resolve all $ref references
|
|
313
|
+
const resolvedSchema = resolveSchemaRefs(schema, spec);
|
|
314
|
+
// Add JSON Schema meta properties
|
|
315
|
+
const jsonSchema = {
|
|
316
|
+
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
317
|
+
title: `${sanitizeOperationId(operationId)} ${statusCode} Response`,
|
|
318
|
+
...resolvedSchema
|
|
319
|
+
};
|
|
320
|
+
schemas.push({
|
|
321
|
+
operationId: sanitizeOperationId(operationId),
|
|
322
|
+
method: method.toUpperCase(),
|
|
323
|
+
path: pathStr,
|
|
324
|
+
statusCode,
|
|
325
|
+
schema: jsonSchema
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return schemas;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Generate schema filename from operation details
|
|
335
|
+
*/
|
|
336
|
+
function generateSchemaFilename(operationId, statusCode) {
|
|
337
|
+
return `${operationId}-${statusCode}.schema.json`;
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=swaggerParser.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
const assert = __importStar(require("assert"));
|
|
37
|
+
// You can import and use all API from the 'vscode' module
|
|
38
|
+
// as well as import your extension to test it
|
|
39
|
+
const vscode = __importStar(require("vscode"));
|
|
40
|
+
// import * as myExtension from '../../extension';
|
|
41
|
+
suite('Extension Test Suite', () => {
|
|
42
|
+
vscode.window.showInformationMessage('Start all tests.');
|
|
43
|
+
test('Sample test', () => {
|
|
44
|
+
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
|
45
|
+
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=extension.test.js.map
|