norn-cli 1.4.0 → 1.4.2

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.
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * Swagger Body IntelliSense Cache
4
+ *
5
+ * Persists request-body schemas extracted from Swagger/OpenAPI specs.
6
+ * The cache is used by completionProvider to provide endpoint request-body
7
+ * templates and inline key suggestions without fetching remote specs.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.loadSwaggerBodyCache = loadSwaggerBodyCache;
44
+ exports.getCachedRequestBodySchemasForUrl = getCachedRequestBodySchemasForUrl;
45
+ exports.saveRequestBodySchemasForUrl = saveRequestBodySchemasForUrl;
46
+ exports.invalidateSwaggerBodyCache = invalidateSwaggerBodyCache;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const vscode = __importStar(require("vscode"));
50
+ const CACHE_VERSION = 1;
51
+ const CACHE_FOLDER = '.norn-cache';
52
+ const CACHE_FILE = 'swagger-body-intellisense.json';
53
+ function getWorkspaceRoot() {
54
+ const workspaceFolders = vscode.workspace.workspaceFolders;
55
+ if (workspaceFolders && workspaceFolders.length > 0) {
56
+ return workspaceFolders[0].uri.fsPath;
57
+ }
58
+ return undefined;
59
+ }
60
+ function getCachePath() {
61
+ const root = getWorkspaceRoot();
62
+ if (!root) {
63
+ return undefined;
64
+ }
65
+ return path.join(root, CACHE_FOLDER, CACHE_FILE);
66
+ }
67
+ function ensureCacheDir() {
68
+ const root = getWorkspaceRoot();
69
+ if (!root) {
70
+ return false;
71
+ }
72
+ const cacheDir = path.join(root, CACHE_FOLDER);
73
+ if (!fs.existsSync(cacheDir)) {
74
+ try {
75
+ fs.mkdirSync(cacheDir, { recursive: true });
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ return true;
82
+ }
83
+ function loadSwaggerBodyCache() {
84
+ const cachePath = getCachePath();
85
+ if (!cachePath || !fs.existsSync(cachePath)) {
86
+ return { version: CACHE_VERSION, urls: {} };
87
+ }
88
+ try {
89
+ const content = fs.readFileSync(cachePath, 'utf-8');
90
+ const parsed = JSON.parse(content);
91
+ if (parsed.version !== CACHE_VERSION || !parsed.urls || typeof parsed.urls !== 'object') {
92
+ return { version: CACHE_VERSION, urls: {} };
93
+ }
94
+ return parsed;
95
+ }
96
+ catch {
97
+ return { version: CACHE_VERSION, urls: {} };
98
+ }
99
+ }
100
+ function saveSwaggerBodyCache(cache) {
101
+ if (!ensureCacheDir()) {
102
+ return false;
103
+ }
104
+ const cachePath = getCachePath();
105
+ if (!cachePath) {
106
+ return false;
107
+ }
108
+ try {
109
+ fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
110
+ return true;
111
+ }
112
+ catch {
113
+ return false;
114
+ }
115
+ }
116
+ function getCachedRequestBodySchemasForUrl(url) {
117
+ const cache = loadSwaggerBodyCache();
118
+ return cache.urls[url];
119
+ }
120
+ function saveRequestBodySchemasForUrl(url, baseUrl, schemas) {
121
+ const cache = loadSwaggerBodyCache();
122
+ cache.urls[url] = {
123
+ baseUrl,
124
+ fetchedAt: new Date().toISOString(),
125
+ schemas: schemas.map(schema => ({
126
+ operationId: schema.operationId,
127
+ method: schema.method.toUpperCase(),
128
+ path: schema.path,
129
+ required: Array.isArray(schema.required) ? schema.required : [],
130
+ schema: schema.schema
131
+ }))
132
+ };
133
+ saveSwaggerBodyCache(cache);
134
+ }
135
+ function invalidateSwaggerBodyCache(url) {
136
+ if (url) {
137
+ const cache = loadSwaggerBodyCache();
138
+ delete cache.urls[url];
139
+ saveSwaggerBodyCache(cache);
140
+ return;
141
+ }
142
+ saveSwaggerBodyCache({
143
+ version: CACHE_VERSION,
144
+ urls: {}
145
+ });
146
+ }
147
+ //# sourceMappingURL=swaggerBodyIntellisenseCache.js.map
@@ -9,12 +9,98 @@ exports.invalidateSwaggerCache = invalidateSwaggerCache;
9
9
  exports.parseSwaggerSpec = parseSwaggerSpec;
10
10
  exports.generateNornapiContent = generateNornapiContent;
11
11
  exports.extractResponseSchemas = extractResponseSchemas;
12
+ exports.extractRequestBodySchemas = extractRequestBodySchemas;
12
13
  exports.generateSchemaFilename = generateSchemaFilename;
13
14
  const axios_1 = __importDefault(require("axios"));
14
15
  /**
15
16
  * Cache for parsed swagger specs (cleared when extension deactivates)
16
17
  */
17
18
  const swaggerCache = new Map();
19
+ const rawSwaggerSpecCache = new Map();
20
+ async function getRawSwaggerSpec(url) {
21
+ if (rawSwaggerSpecCache.has(url)) {
22
+ return rawSwaggerSpecCache.get(url);
23
+ }
24
+ const response = await axios_1.default.get(url, {
25
+ headers: {
26
+ 'Accept': 'application/json'
27
+ },
28
+ timeout: 30000
29
+ });
30
+ rawSwaggerSpecCache.set(url, response.data);
31
+ return response.data;
32
+ }
33
+ function isOpenApi3Spec(spec) {
34
+ return typeof spec?.openapi === 'string' && spec.openapi.startsWith('3.');
35
+ }
36
+ function resolveRef(ref, root) {
37
+ if (!ref.startsWith('#/')) {
38
+ return undefined;
39
+ }
40
+ const parts = ref.slice(2).split('/');
41
+ let current = root;
42
+ for (const part of parts) {
43
+ if (current === undefined) {
44
+ return undefined;
45
+ }
46
+ current = current[part];
47
+ }
48
+ return current;
49
+ }
50
+ function resolveSchemaRefs(schema, root, visited = new Set()) {
51
+ if (!schema || typeof schema !== 'object') {
52
+ return schema;
53
+ }
54
+ if (schema.$ref && typeof schema.$ref === 'string') {
55
+ if (visited.has(schema.$ref)) {
56
+ return { type: 'object' };
57
+ }
58
+ visited.add(schema.$ref);
59
+ const resolved = resolveRef(schema.$ref, root);
60
+ if (resolved) {
61
+ return resolveSchemaRefs(resolved, root, visited);
62
+ }
63
+ return schema;
64
+ }
65
+ const result = Array.isArray(schema) ? [] : {};
66
+ for (const key of Object.keys(schema)) {
67
+ if (key === '$ref') {
68
+ continue;
69
+ }
70
+ const value = schema[key];
71
+ if (typeof value === 'object' && value !== null) {
72
+ result[key] = resolveSchemaRefs(value, root, new Set(visited));
73
+ }
74
+ else {
75
+ result[key] = value;
76
+ }
77
+ }
78
+ return result;
79
+ }
80
+ function getPreferredJsonSchema(content) {
81
+ if (!content) {
82
+ return undefined;
83
+ }
84
+ if (content['application/json']?.schema) {
85
+ return content['application/json'].schema;
86
+ }
87
+ const jsonKey = Object.keys(content).find(key => key.toLowerCase().includes('json') && content[key]?.schema);
88
+ if (jsonKey) {
89
+ return content[jsonKey].schema;
90
+ }
91
+ if (content['*/*']?.schema) {
92
+ return content['*/*'].schema;
93
+ }
94
+ const firstSchemaKey = Object.keys(content).find(key => content[key]?.schema);
95
+ return firstSchemaKey ? content[firstSchemaKey].schema : undefined;
96
+ }
97
+ function addJsonSchemaMeta(schema, title) {
98
+ return {
99
+ '$schema': 'http://json-schema.org/draft-07/schema#',
100
+ title,
101
+ ...schema
102
+ };
103
+ }
18
104
  /**
19
105
  * Get a cached swagger spec, or fetch and parse if not cached
20
106
  */
@@ -31,6 +117,7 @@ async function getCachedSwaggerSpec(url) {
31
117
  */
32
118
  function clearSwaggerCache() {
33
119
  swaggerCache.clear();
120
+ rawSwaggerSpecCache.clear();
34
121
  }
35
122
  /**
36
123
  * Invalidate a specific URL from the cache (for manual refresh)
@@ -38,25 +125,20 @@ function clearSwaggerCache() {
38
125
  function invalidateSwaggerCache(url) {
39
126
  if (url) {
40
127
  swaggerCache.delete(url);
128
+ rawSwaggerSpecCache.delete(url);
41
129
  }
42
130
  else {
43
131
  swaggerCache.clear();
132
+ rawSwaggerSpecCache.clear();
44
133
  }
45
134
  }
46
135
  /**
47
136
  * Fetches and parses an OpenAPI/Swagger specification from a URL.
48
137
  */
49
138
  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;
139
+ const spec = await getRawSwaggerSpec(url);
58
140
  // Determine if it's OpenAPI 3.x or Swagger 2.0
59
- const isOpenAPI3 = spec.openapi && spec.openapi.startsWith('3.');
141
+ const isOpenAPI3 = isOpenApi3Spec(spec);
60
142
  // Extract base URL
61
143
  let baseUrl = '';
62
144
  if (isOpenAPI3) {
@@ -220,61 +302,9 @@ function generateNornapiContent(spec, selectedSections, customBaseUrl) {
220
302
  * @returns Array of response schemas
221
303
  */
222
304
  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;
305
+ const spec = await getRawSwaggerSpec(url);
228
306
  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
- };
307
+ const isOpenAPI3 = isOpenApi3Spec(spec);
278
308
  // Parse paths
279
309
  const paths = spec.paths || {};
280
310
  for (const [pathStr, pathItem] of Object.entries(paths)) {
@@ -295,12 +325,7 @@ async function extractResponseSchemas(url) {
295
325
  if (isOpenAPI3) {
296
326
  // OpenAPI 3.x: responses[code].content["application/json"].schema
297
327
  const content = responseObj.content;
298
- if (content) {
299
- const jsonContent = content['application/json'] || content['*/*'];
300
- if (jsonContent?.schema) {
301
- schema = jsonContent.schema;
302
- }
303
- }
328
+ schema = getPreferredJsonSchema(content);
304
329
  }
305
330
  else {
306
331
  // Swagger 2.0: responses[code].schema
@@ -312,11 +337,7 @@ async function extractResponseSchemas(url) {
312
337
  // Resolve all $ref references
313
338
  const resolvedSchema = resolveSchemaRefs(schema, spec);
314
339
  // 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
- };
340
+ const jsonSchema = addJsonSchemaMeta(resolvedSchema, `${sanitizeOperationId(operationId)} ${statusCode} Response`);
320
341
  schemas.push({
321
342
  operationId: sanitizeOperationId(operationId),
322
343
  method: method.toUpperCase(),
@@ -330,6 +351,65 @@ async function extractResponseSchemas(url) {
330
351
  }
331
352
  return schemas;
332
353
  }
354
+ /**
355
+ * Extract request body schemas from an OpenAPI/Swagger spec
356
+ * Resolves $ref references to inline schemas
357
+ * @param url URL of the OpenAPI spec
358
+ * @returns Array of request body schemas
359
+ */
360
+ async function extractRequestBodySchemas(url) {
361
+ const spec = await getRawSwaggerSpec(url);
362
+ const schemas = [];
363
+ const isOpenAPI3 = isOpenApi3Spec(spec);
364
+ const paths = spec.paths || {};
365
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
366
+ const pathObj = pathItem;
367
+ for (const method of ['get', 'post', 'put', 'delete', 'patch', 'head', 'options']) {
368
+ const operation = pathObj[method];
369
+ if (!operation) {
370
+ continue;
371
+ }
372
+ let schema = null;
373
+ if (isOpenAPI3) {
374
+ // OpenAPI 3.x request body:
375
+ // operation.requestBody.content["application/json"].schema
376
+ let requestBody = operation.requestBody;
377
+ if (requestBody && typeof requestBody.$ref === 'string') {
378
+ requestBody = resolveRef(requestBody.$ref, spec);
379
+ }
380
+ const content = requestBody?.content;
381
+ schema = getPreferredJsonSchema(content);
382
+ }
383
+ else {
384
+ // Swagger 2.0 request body:
385
+ // operation.parameters[] with in: "body"
386
+ const pathParameters = Array.isArray(pathObj.parameters) ? pathObj.parameters : [];
387
+ const operationParameters = Array.isArray(operation.parameters) ? operation.parameters : [];
388
+ const allParameters = [...pathParameters, ...operationParameters];
389
+ const bodyParameter = allParameters.find((param) => param?.in === 'body' && param?.schema);
390
+ if (bodyParameter?.schema) {
391
+ schema = bodyParameter.schema;
392
+ }
393
+ }
394
+ if (!schema) {
395
+ continue;
396
+ }
397
+ const operationId = sanitizeOperationId(operation.operationId || generateOperationId(method, pathStr));
398
+ const resolvedSchema = resolveSchemaRefs(schema, spec);
399
+ const required = Array.isArray(resolvedSchema?.required)
400
+ ? resolvedSchema.required.filter((name) => typeof name === 'string')
401
+ : [];
402
+ schemas.push({
403
+ operationId,
404
+ method: method.toUpperCase(),
405
+ path: pathStr,
406
+ required,
407
+ schema: addJsonSchemaMeta(resolvedSchema, `${operationId} Request`)
408
+ });
409
+ }
410
+ }
411
+ return schemas;
412
+ }
333
413
  /**
334
414
  * Generate schema filename from operation details
335
415
  */
@@ -0,0 +1,100 @@
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
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const vscode = __importStar(require("vscode"));
40
+ const coverageCalculator_1 = require("../coverageCalculator");
41
+ const swaggerParser = __importStar(require("../swaggerParser"));
42
+ suite('Coverage Calculator Regression', () => {
43
+ test('maps {{baseUrl}}/{{version}} endpoint paths to swagger paths', async () => {
44
+ const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
45
+ assert.ok(workspaceRoot, 'Workspace root is required for this test');
46
+ const tempRoot = fs.mkdtempSync(path.join(workspaceRoot, '.coverage-template-regression-'));
47
+ const nornapiPath = path.join(tempRoot, 'pet.nornapi');
48
+ const nornPath = path.join(tempRoot, 'pet.norn');
49
+ const nornapiContent = `swagger "https://petstore.swagger.io/v2/swagger.json"
50
+
51
+ endpoints
52
+ GetPetById: GET {{baseUrl}}/{{version}}/pet/{petId}
53
+ end endpoints
54
+ `;
55
+ const nornContent = `test sequence GetPet
56
+ GET GetPetById(1)
57
+ assert $1.status == 200
58
+ end sequence
59
+ `;
60
+ fs.writeFileSync(nornapiPath, nornapiContent, 'utf-8');
61
+ fs.writeFileSync(nornPath, nornContent, 'utf-8');
62
+ const originalGetCachedSwaggerSpec = swaggerParser.getCachedSwaggerSpec;
63
+ try {
64
+ swaggerParser.getCachedSwaggerSpec = async () => ({
65
+ title: 'Petstore',
66
+ version: '1.0.0',
67
+ baseUrl: 'https://petstore.swagger.io/v2',
68
+ sections: [
69
+ {
70
+ name: 'pet',
71
+ endpoints: [
72
+ {
73
+ name: 'getPetById',
74
+ method: 'GET',
75
+ path: '/pet/{petId}',
76
+ parameters: ['petId'],
77
+ tag: 'pet',
78
+ responseCodes: ['200', '404']
79
+ }
80
+ ]
81
+ }
82
+ ]
83
+ });
84
+ (0, coverageCalculator_1.clearCoverageCache)();
85
+ const coverage = await (0, coverageCalculator_1.getCoverageForNornapiFile)(nornapiPath);
86
+ assert.strictEqual(coverage.hasSwagger, true);
87
+ assert.strictEqual(coverage.total, 2);
88
+ assert.strictEqual(coverage.covered, 1);
89
+ const endpoint = coverage.specs[0]?.endpoints.find(e => e.method === 'GET' && e.path === '/pet/{petId}');
90
+ assert.ok(endpoint, 'Expected /pet/{petId} endpoint to be present');
91
+ const status200 = endpoint.responseCodes.find(code => code.code === '200');
92
+ assert.ok(status200?.covered, 'Expected 200 status code to be covered');
93
+ }
94
+ finally {
95
+ swaggerParser.getCachedSwaggerSpec = originalGetCachedSwaggerSpec;
96
+ fs.rmSync(tempRoot, { recursive: true, force: true });
97
+ }
98
+ });
99
+ });
100
+ //# sourceMappingURL=coverageCalculator.test.js.map
@@ -395,7 +395,7 @@ class NornTestController {
395
395
  const content = await fs.readFile(data.uri.fsPath, 'utf-8');
396
396
  const fileVariables = (0, parser_1.extractVariables)(content);
397
397
  // Get environment variables
398
- const envVars = (0, environmentProvider_1.getEnvironmentVariables)();
398
+ const envVars = (0, environmentProvider_1.getEnvironmentVariables)(data.uri.fsPath);
399
399
  // Merge variables (env vars take precedence)
400
400
  const mergedVariables = { ...fileVariables, ...envVars };
401
401
  // If this is a theory case, add case params
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.0",
5
+ "version": "1.4.2",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"