norn-cli 1.1.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.
Files changed (5) hide show
  1. package/CHANGELOG.md +556 -0
  2. package/LICENSE +94 -0
  3. package/README.md +938 -0
  4. package/dist/cli.js +23453 -0
  5. package/package.json +194 -0
package/README.md ADDED
@@ -0,0 +1,938 @@
1
+ # Norn - REST Client
2
+
3
+ A powerful REST client extension for VS Code with sequences, assertions, environments, script execution, and CLI support for CI/CD pipelines.
4
+
5
+ ![VS Code Version](https://img.shields.io/badge/VS%20Code-1.108.1+-blue)
6
+ ![License](https://img.shields.io/badge/license-EULA-orange)
7
+
8
+ ## Features
9
+
10
+ - **HTTP Requests**: Send GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS requests from `.norn` files
11
+ - **API Definitions**: Define reusable headers and endpoints in `.nornapi` files
12
+ - **Variables**: Define and reference variables with `var name = value` and `{{name}}`
13
+ - **Environments**: Manage dev/staging/prod configurations with `.nornenv` files
14
+ - **Sequences**: Chain multiple requests with response capture using `$N.path`
15
+ - **Test Sequences**: Mark sequences as tests with `test sequence` for CLI execution
16
+ - **Sequence Tags**: Tag sequences with `@smoke`, `@team(CustomerExp)` for filtering in CI/CD
17
+ - **Secret Variables**: Mark sensitive environment variables with `secret` for automatic redaction
18
+ - **Assertions**: Validate responses with `assert` statements supporting comparison, type checking, and existence
19
+ - **Named Requests**: Define reusable requests with `[RequestName]` and call them with `run RequestName`
20
+ - **Conditionals**: Control flow with `if/end if` blocks based on response data
21
+ - **Wait Commands**: Add delays between requests with `wait 1s` or `wait 500ms`
22
+ - **JSON File Loading**: Load test data from JSON files with `run readJson ./file.json`
23
+ - **Property Updates**: Modify loaded JSON data inline with `config.property = value`
24
+ - **Script Execution**: Run bash, PowerShell, or JavaScript scripts within sequences
25
+ - **Print Statements**: Debug and log messages during sequence execution
26
+ - **Cookie Support**: Automatic cookie jar with persistence across requests
27
+ - **Syntax Highlighting**: Full syntax highlighting for requests, headers, JSON bodies
28
+ - **IntelliSense**: Autocomplete for HTTP methods, headers, variables, and keywords
29
+ - **Diagnostics**: Error highlighting for undefined variables
30
+ - **CLI**: Run tests from terminal with JUnit/HTML reports for CI/CD automation
31
+
32
+ ## Quick Start
33
+
34
+ 1. Create a file with `.norn` extension
35
+ 2. Write your HTTP request
36
+ 3. Click "Send Request" above the request line
37
+
38
+ ## Usage
39
+
40
+ ### Basic Request
41
+
42
+ ```bash
43
+ GET https://api.example.com/users
44
+ Authorization: Bearer my-token
45
+ ```
46
+
47
+ ### Variables
48
+
49
+ Variables can hold literal strings or be assigned from expressions:
50
+
51
+ ```bash
52
+ # Literal string (quotes optional for simple values)
53
+ var baseUrl = https://api.example.com
54
+ var name = "John Doe"
55
+
56
+ # Use variables with {{}}
57
+ GET {{baseUrl}}/users
58
+ Authorization: Bearer {{token}}
59
+ ```
60
+
61
+ #### Variable Assignment in Sequences
62
+
63
+ Inside sequences, you can assign variables from expressions:
64
+
65
+ ```bash
66
+ sequence Example
67
+ var data = run readJson ./config.json
68
+
69
+ # Extract a value from JSON variable (evaluated)
70
+ var userId = data.users[0].id
71
+
72
+ # Literal string with interpolation
73
+ var message = "User ID is {{userId}}"
74
+
75
+ # Response capture
76
+ GET https://api.example.com/users/{{userId}}
77
+ var userName = $1.body.name
78
+
79
+ print Result | {{message}}, Name: {{userName}}
80
+ end sequence
81
+ ```
82
+
83
+ | Syntax | Type | Example |
84
+ |--------|------|---------|
85
+ | `var x = "text"` | Literal string | `var name = "John"` |
86
+ | `var x = "Hi {{y}}"` | String with interpolation | `var msg = "Hello {{name}}"` |
87
+ | `var x = someVar.path` | Expression (evaluated) | `var id = data.users[0].id` |
88
+ | `var x = $1.body.id` | Response capture | `var token = $1.body.token` |
89
+ | `var x = run ...` | Script/JSON result | `var data = run readJson ./file.json` |
90
+
91
+ ### Request with Body
92
+
93
+ ```bash
94
+ POST https://api.example.com/users
95
+ Content-Type: application/json
96
+ {
97
+ "name": "John Doe",
98
+ "email": "john@example.com"
99
+ }
100
+ ```
101
+
102
+ ### Sequences (Chained Requests)
103
+
104
+ Chain multiple requests and capture response data:
105
+
106
+ ```bash
107
+ sequence AuthFlow
108
+ POST https://api.example.com/login
109
+ Content-Type: application/json
110
+
111
+ {"username": "admin", "password": "secret"}
112
+
113
+ var token = $1.accessToken
114
+
115
+ GET https://api.example.com/profile
116
+ Authorization: Bearer {{token}}
117
+ end sequence
118
+ ```
119
+
120
+ Click "▶ Run Sequence" above the `sequence` line to execute all requests in order.
121
+
122
+ ### Sequence Composition
123
+
124
+ Sequences can call other sequences, enabling modular test design and natural setup/teardown patterns:
125
+
126
+ ```bash
127
+ # Define reusable setup sequence
128
+ sequence Login
129
+ POST {{baseUrl}}/auth/login
130
+ Content-Type: application/json
131
+ {"username": "admin", "password": "secret"}
132
+ var token = $1.body.accessToken
133
+ print Logged in | Token: {{token}}
134
+ end sequence
135
+
136
+ # Define reusable teardown sequence
137
+ sequence Logout
138
+ POST {{baseUrl}}/auth/logout
139
+ Authorization: Bearer {{token}}
140
+ print Logged out
141
+ end sequence
142
+
143
+ # Main test sequence uses setup/teardown
144
+ sequence UserTests
145
+ # Run setup - token variable becomes available
146
+ run Login
147
+
148
+ # Run actual tests
149
+ GET {{baseUrl}}/users/me
150
+ Authorization: Bearer {{token}}
151
+ assert $1.status == 200
152
+
153
+ # Run teardown
154
+ run Logout
155
+ end sequence
156
+ ```
157
+
158
+ **Key behaviors:**
159
+ - Variables set in called sequences are available to the caller (`token` from `Login`)
160
+ - Sequences can be nested to any depth
161
+ - Circular references are detected and reported as errors
162
+ - If a called sequence fails, the parent sequence stops
163
+
164
+ ### Sequence Parameters
165
+
166
+ Sequences can accept parameters with optional default values:
167
+
168
+ ```bash
169
+ # Required parameter
170
+ sequence Greet(name)
171
+ print Hello, {{name}}!
172
+ end sequence
173
+
174
+ # Optional parameter with default
175
+ sequence Login(username, password = "secret123")
176
+ POST {{baseUrl}}/auth/login
177
+ Content-Type: application/json
178
+ {"username": "{{username}}", "password": "{{password}}"}
179
+
180
+ var token = $1.body.accessToken
181
+ return token
182
+ end sequence
183
+
184
+ # Call with positional arguments
185
+ run Greet("World")
186
+ run Login("admin", "mypassword")
187
+
188
+ # Call with named arguments (any order)
189
+ run Login(password: "pass123", username: "admin")
190
+
191
+ # Use defaults - only provide required params
192
+ run Login("admin") # uses default password
193
+ ```
194
+
195
+ **Rules:**
196
+ - Required parameters (no default) must come before optional parameters
197
+ - Positional arguments are bound in order
198
+ - Named arguments can be in any order
199
+ - Cannot mix positional arguments after named arguments
200
+
201
+ ### Sequence Return Values
202
+
203
+ Sequences can return values that the caller can capture and use:
204
+
205
+ ```bash
206
+ sequence FetchUser(userId)
207
+ GET {{baseUrl}}/users/{{userId}}
208
+ var name = $1.body.name
209
+ var email = $1.body.email
210
+ return name, email
211
+ end sequence
212
+
213
+ sequence MyTests
214
+ # Capture return values
215
+ var user = run FetchUser("123")
216
+
217
+ # Access individual fields
218
+ print User name: {{user.name}}
219
+ print User email: {{user.email}}
220
+
221
+ # Use in requests
222
+ POST {{baseUrl}}/messages
223
+ Content-Type: application/json
224
+ {"to": "{{user.email}}", "subject": "Hello {{user.name}}"}
225
+ end sequence
226
+ ```
227
+
228
+ **IntelliSense:** When you type `{{user.`, you'll see `name` and `email` as completions based on the sequence's return statement.
229
+
230
+ ### Sequence Tags
231
+
232
+ Tag sequences for filtering during test execution. Tags support simple names and key-value pairs:
233
+
234
+ ```bash
235
+ # Simple tags
236
+ @smoke @regression
237
+ sequence AuthFlow
238
+ POST {{baseUrl}}/auth/login
239
+ Content-Type: application/json
240
+ {"username": "admin", "password": "secret"}
241
+ assert $1.status == 200
242
+ end sequence
243
+
244
+ # Key-value tags for categorization
245
+ @team(CustomerExp)
246
+ @priority(high)
247
+ @jira(NORN-123)
248
+ sequence CheckoutFlow
249
+ GET {{baseUrl}}/checkout
250
+ assert $1.status == 200
251
+ end sequence
252
+
253
+ # Mixed tags on multiple lines
254
+ @smoke
255
+ @team(Platform)
256
+ @feature(payments)
257
+ sequence PaymentValidation
258
+ POST {{baseUrl}}/payments
259
+ assert $1.status == 201
260
+ end sequence
261
+ ```
262
+
263
+ **Tag Syntax:**
264
+ - Simple tags: `@smoke`, `@regression`, `@wip`, `@slow`
265
+ - Key-value tags: `@team(CustomerExp)`, `@priority(high)`, `@jira(NORN-123)`
266
+ - Multiple tags can be on the same line or separate lines
267
+ - Tag names follow the pattern: `[a-zA-Z_][a-zA-Z0-9_-]*`
268
+ - Tag matching is case-insensitive
269
+
270
+ **CLI Filtering:**
271
+
272
+ ```bash
273
+ # Run sequences tagged @smoke
274
+ npx norn tests/ --tag smoke
275
+
276
+ # AND logic: must have BOTH tags
277
+ npx norn tests/ --tag smoke --tag auth
278
+
279
+ # OR logic: match ANY tag
280
+ npx norn tests/ --tags smoke,regression
281
+
282
+ # Key-value exact match
283
+ npx norn tests/ --tag team(CustomerExp)
284
+
285
+ # Combine with environment
286
+ npx norn tests/ --env staging --tag smoke
287
+ ```
288
+
289
+ **Behavior:**
290
+ - When a sequence calls `run OtherSequence` and tag filtering is active, non-matching sequences are silently skipped
291
+ - Untagged sequences run when no tag filter is specified
292
+ - Tags can only be applied to sequences (diagnostics warn on misplaced tags)
293
+
294
+ ### Imports
295
+
296
+ Organize your tests by importing named requests and sequences from other files:
297
+
298
+ ```bash
299
+ # common.norn - shared utilities
300
+ var baseUrl = https://api.example.com
301
+
302
+ [AuthRequest]
303
+ POST {{baseUrl}}/auth/login
304
+ Content-Type: application/json
305
+ {"username": "admin", "password": "secret"}
306
+
307
+ sequence SharedSetup
308
+ run AuthRequest
309
+ var token = $1.body.accessToken
310
+ end sequence
311
+ ```
312
+
313
+ ```bash
314
+ # main-test.norn - imports shared utilities
315
+ import "./common.norn"
316
+
317
+ sequence MyTests
318
+ # Use imported sequence
319
+ run SharedSetup
320
+
321
+ # Use imported request
322
+ run AuthRequest
323
+
324
+ # Token from SharedSetup is available
325
+ GET {{baseUrl}}/users/me
326
+ Authorization: Bearer {{token}}
327
+ assert $1.status == 200
328
+ end sequence
329
+ ```
330
+
331
+ **Key behaviors:**
332
+ - Import paths are relative to the current file
333
+ - Nested imports are supported (imported files can import other files)
334
+ - Circular imports are detected and reported as errors
335
+ - Only named requests `[Name]` and sequences are imported
336
+ - Variables are resolved at import time (baked in), not exported
337
+ - `.nornenv` environment variables are shared across all files
338
+
339
+ ### API Definition Files (.nornapi)
340
+
341
+ Define reusable API configurations with header groups and named endpoints in `.nornapi` files.
342
+
343
+ #### Header Groups
344
+
345
+ Header groups define reusable sets of HTTP headers:
346
+
347
+ ```bash
348
+ # api.nornapi
349
+
350
+ headers Json
351
+ Content-Type: application/json
352
+ Accept: application/json
353
+ end headers
354
+
355
+ headers Auth
356
+ Authorization: Bearer {{token}}
357
+ X-API-Key: {{apiKey}}
358
+ end headers
359
+
360
+ headers Form
361
+ Content-Type: application/x-www-form-urlencoded
362
+ end headers
363
+ ```
364
+
365
+ - Header values can include `{{variables}}` that are resolved at runtime
366
+ - Multiple header groups can be applied to a single request
367
+
368
+ #### Named Endpoints
369
+
370
+ Define named endpoints with path parameters using `{param}` syntax:
371
+
372
+ ```bash
373
+ endpoints
374
+ # Basic CRUD operations
375
+ GetUser: GET {{baseUrl}}/users/{id}
376
+ GetAllUsers: GET {{baseUrl}}/users
377
+ CreateUser: POST {{baseUrl}}/users
378
+ UpdateUser: PUT {{baseUrl}}/users/{id}
379
+ DeleteUser: DELETE {{baseUrl}}/users/{id}
380
+
381
+ # Multiple path parameters
382
+ GetUserPosts: GET {{baseUrl}}/users/{userId}/posts
383
+ GetUserPost: GET {{baseUrl}}/users/{userId}/posts/{postId}
384
+
385
+ # All HTTP methods supported
386
+ CheckHealth: HEAD {{baseUrl}}/health
387
+ GetOptions: OPTIONS {{baseUrl}}/api
388
+ end endpoints
389
+ ```
390
+
391
+ - Endpoint names are case-sensitive (e.g., `GetUser`, `CreateUser`)
392
+ - Path parameters use single braces: `{id}`, `{userId}`
393
+ - Environment variables use double braces: `{{baseUrl}}`
394
+
395
+ #### Swagger/OpenAPI Import
396
+
397
+ Import endpoints directly from OpenAPI/Swagger specifications:
398
+
399
+ ```bash
400
+ # api.nornapi
401
+
402
+ swagger https://petstore.swagger.io/v2/swagger.json
403
+ ```
404
+
405
+ Click "Import Swagger" CodeLens above the statement to fetch and generate endpoint definitions from the spec.
406
+
407
+ #### Using .nornapi in .norn Files
408
+
409
+ Import your API definitions and use them in requests:
410
+
411
+ ```bash
412
+ import "./api.nornapi"
413
+
414
+ # Standalone request with endpoint and header group
415
+ GET GetUser(1) Json
416
+
417
+ # Inside a sequence
418
+ sequence UserTests
419
+ # Basic endpoint call with parameter
420
+ GET GetTodo(1) Json
421
+ assert $1.status == 200
422
+
423
+ # Multiple header groups (space-separated)
424
+ GET GetUser(1) Json Auth
425
+
426
+ # Header groups on separate lines
427
+ POST CreateUser
428
+ Json
429
+ Auth
430
+ {"name": "John", "email": "john@example.com"}
431
+
432
+ # Capture response to variable
433
+ var user = GET GetUser(1) Json
434
+ print User | {{user.body.name}}
435
+
436
+ # Use variables in endpoint parameters
437
+ var userId = 5
438
+ GET GetUser({{userId}}) Json
439
+
440
+ # Quoted string parameters
441
+ GET GetUser("123") Json
442
+ end sequence
443
+ ```
444
+
445
+ #### Endpoint Syntax Reference
446
+
447
+ | Syntax | Description |
448
+ |--------|-------------|
449
+ | `GET EndpointName(param)` | Call endpoint with positional parameter |
450
+ | `GET EndpointName(param1, param2)` | Multiple positional parameters |
451
+ | `GET EndpointName(id: "123")` | Named parameter |
452
+ | `GET EndpointName(1) Json` | With header group |
453
+ | `GET EndpointName(1) Json Auth` | Multiple header groups |
454
+ | `var x = GET EndpointName(1) Json` | Capture response to variable |
455
+
456
+ ### Assertions
457
+
458
+ Validate response data with powerful assertion operators:
459
+
460
+ ```bash
461
+ sequence ValidateAPI
462
+ GET https://api.example.com/users/1
463
+
464
+ # Status and comparison assertions
465
+ assert $1.status == 200
466
+ assert $1.status < 300
467
+
468
+ # Body value assertions
469
+ assert $1.body.id == 1
470
+ assert $1.body.name == "John"
471
+ assert $1.body.age >= 18
472
+
473
+ # String assertions
474
+ assert $1.body.email contains "@"
475
+ assert $1.body.name startsWith "J"
476
+ assert $1.body.name endsWith "ohn"
477
+ assert $1.body.email matches "[a-z]+@[a-z]+\.[a-z]+"
478
+
479
+ # Timing assertions
480
+ assert $1.duration < 5000
481
+
482
+ # Existence checks
483
+ assert $1.body.id exists
484
+ assert $1.body.deletedAt !exists
485
+
486
+ # Type assertions
487
+ assert $1.body.id isType number
488
+ assert $1.body.name isType string
489
+ assert $1.body.active isType boolean
490
+ assert $1.body.tags isType array
491
+ assert $1.body.address isType object
492
+
493
+ # Header assertions
494
+ assert $1.headers.Content-Type contains "application/json"
495
+
496
+ # Custom failure messages
497
+ assert $1.status == 200 | "API should return success status"
498
+ end sequence
499
+ ```
500
+
501
+ ### Environments
502
+
503
+ Create a `.nornenv` file in your workspace root to manage environment-specific variables:
504
+
505
+ ```bash
506
+ # .nornenv file
507
+ # Common variables (always available)
508
+ var timeout = 30000
509
+ var version = v1
510
+
511
+ [env:dev]
512
+ var baseUrl = https://dev-api.example.com
513
+ var apiKey = dev-key-123
514
+
515
+ [env:staging]
516
+ var baseUrl = https://staging-api.example.com
517
+ var apiKey = staging-key-456
518
+
519
+ [env:prod]
520
+ var baseUrl = https://api.example.com
521
+ var apiKey = prod-key-789
522
+ ```
523
+
524
+ Select the active environment from the VS Code status bar. Environment variables override common variables.
525
+
526
+ ### Named Requests
527
+
528
+ Define reusable requests and call them from sequences:
529
+
530
+ ```bash
531
+ # Define a reusable login request
532
+ [Login]
533
+ POST {{baseUrl}}/auth/login
534
+ Content-Type: application/json
535
+ {"username": "admin", "password": "secret"}
536
+
537
+ # Define a profile request
538
+ [GetProfile]
539
+ GET {{baseUrl}}/users/me
540
+ Authorization: Bearer {{token}}
541
+
542
+ ###
543
+
544
+ sequence AuthFlow
545
+ # Run the named request
546
+ run Login
547
+ var token = $1.body.accessToken
548
+
549
+ # Run another named request
550
+ run GetProfile
551
+ print Profile | Welcome, {{$2.body.name}}!
552
+ end sequence
553
+ ```
554
+
555
+ ### Conditionals (if/end if)
556
+
557
+ Control execution flow based on response data:
558
+
559
+ ```bash
560
+ sequence ConditionalFlow
561
+ GET https://api.example.com/users/1
562
+
563
+ # Execute block only if condition is true
564
+ if $1.status == 200
565
+ print Success | User found!
566
+
567
+ GET https://api.example.com/users/1/orders
568
+ assert $2.status == 200
569
+ end if
570
+
571
+ # Check for errors
572
+ if $1.status == 404
573
+ print Error | User not found
574
+ end if
575
+
576
+ # Conditions support all assertion operators
577
+ if $1.body.role == "admin"
578
+ print Admin | User has admin privileges
579
+ end if
580
+ end sequence
581
+ ```
582
+
583
+ ### Wait Commands
584
+
585
+ Add delays between requests (useful for rate limiting or async operations):
586
+
587
+ ```bash
588
+ sequence RateLimitedFlow
589
+ POST https://api.example.com/jobs
590
+ var jobId = $1.body.id
591
+
592
+ print Waiting | Job submitted, waiting for completion...
593
+
594
+ # Wait 2 seconds
595
+ wait 2s
596
+
597
+ GET https://api.example.com/jobs/{{jobId}}
598
+
599
+ # Wait 500 milliseconds
600
+ wait 500ms
601
+
602
+ GET https://api.example.com/jobs/{{jobId}}/result
603
+ end sequence
604
+ ```
605
+
606
+ ### JSON File Loading
607
+
608
+ Load test data from external JSON files:
609
+
610
+ ```bash
611
+ sequence DataDrivenTest
612
+ # Load JSON configuration
613
+ var config = run readJson ./test-config.json
614
+
615
+ # Access properties
616
+ print Config | Using API: {{config.baseUrl}}
617
+
618
+ # Use in requests
619
+ GET {{config.baseUrl}}/users/{{config.testUser.id}}
620
+
621
+ # Access nested values and arrays
622
+ print First Role | {{config.testUser.roles[0]}}
623
+
624
+ # Modify loaded data inline
625
+ config.baseUrl = https://api.updated.com
626
+ config.testUser.name = Updated Name
627
+
628
+ print Updated | New URL: {{config.baseUrl}}
629
+ end sequence
630
+ ```
631
+
632
+ Example `test-config.json`:
633
+ ```json
634
+ {
635
+ "baseUrl": "https://api.example.com",
636
+ "testUser": {
637
+ "id": 1,
638
+ "name": "Test User",
639
+ "roles": ["admin", "user"]
640
+ }
641
+ }
642
+ ```
643
+
644
+ ### Script Execution
645
+
646
+ Run scripts within sequences for setup, data generation, or validation:
647
+
648
+ ```bash
649
+ sequence TestWithScripts
650
+ # Run a setup script
651
+ run bash ./scripts/seed-db.sh
652
+
653
+ # Generate a signature and capture output
654
+ var signature = run js ./scripts/sign.js {{payload}}
655
+
656
+ # Use the signature in a request
657
+ POST https://api.example.com/verify
658
+ X-Signature: {{signature}}
659
+ end sequence
660
+ ```
661
+
662
+ Scripts receive variables as environment variables with `NORN_` prefix (e.g., `NORN_TOKEN`).
663
+
664
+ #### Capturing Structured Data from Scripts
665
+
666
+ When scripts output JSON, you can access properties directly:
667
+
668
+ ```bash
669
+ sequence DatabaseQuery
670
+ # PowerShell script that outputs JSON
671
+ var dbResult = run powershell ./scripts/query-db.ps1
672
+
673
+ # Access properties from the JSON output
674
+ print User Found | ID: {{dbResult.id}}, Name: {{dbResult.name}}
675
+
676
+ # Use in requests
677
+ GET https://api.example.com/users/{{dbResult.id}}
678
+ end sequence
679
+ ```
680
+
681
+ **Automatic Format Detection**: Norn automatically parses multiple output formats:
682
+
683
+ | Format | Example | Access |
684
+ |--------|---------|--------|
685
+ | JSON | `{"Id": 123, "Name": "John"}` | `{{result.Id}}` |
686
+ | PowerShell Table | `Id Name`<br>`-- ----`<br>`123 John` | `{{result.Id}}` |
687
+ | PowerShell List | `Id : 123`<br>`Name : John` | `{{result.Id}}` |
688
+
689
+ Norn also strips ANSI color codes automatically, so formatted terminal output won't corrupt your data.
690
+
691
+ ### Print Statements
692
+
693
+ Add debug output to your sequences:
694
+
695
+ ```bash
696
+ sequence DebugFlow
697
+ print Starting authentication...
698
+
699
+ POST https://api.example.com/login
700
+ Content-Type: application/json
701
+ {"user": "admin"}
702
+
703
+ var token = $1.token
704
+ print Token received | Value: {{token}}
705
+ end sequence
706
+ ```
707
+
708
+ Use `print Title | Body content` for expandable messages in the result view.
709
+
710
+ ## CLI Usage
711
+
712
+ Run tests from the command line for CI/CD pipelines. Only sequences marked with `test` are executed.
713
+
714
+ ```bash
715
+ # Run all test sequences in a file
716
+ npx norn api-tests.norn
717
+
718
+ # Run all test sequences in a directory (recursive)
719
+ npx norn tests/
720
+
721
+ # Run a specific sequence
722
+ npx norn api-tests.norn --sequence AuthFlow
723
+
724
+ # Run with a specific environment
725
+ npx norn api-tests.norn --env staging
726
+
727
+ # Generate JUnit XML report for CI/CD
728
+ npx norn tests/ --junit --output-dir ./reports
729
+
730
+ # Generate HTML report
731
+ npx norn tests/ --html --output-dir ./reports
732
+
733
+ # Verbose output with colors
734
+ npx norn api-tests.norn -v
735
+
736
+ # Show help
737
+ npx norn --help
738
+ ```
739
+
740
+ ### CLI Options
741
+
742
+ | Option | Description |
743
+ |--------|-------------|
744
+ | `-s, --sequence <name>` | Run a specific sequence by name |
745
+ | `-e, --env <name>` | Use a specific environment from .nornenv |
746
+ | `--tag <name>` | Filter by tag (AND logic, can repeat) |
747
+ | `--tags <list>` | Filter by comma-separated tags (OR logic) |
748
+ | `-j, --json` | Output results as JSON |
749
+ | `--junit` | Generate JUnit XML report |
750
+ | `--html` | Generate HTML report |
751
+ | `--output-dir <path>` | Save reports to directory (auto-timestamps filenames) |
752
+ | `-v, --verbose` | Show detailed output with colors |
753
+ | `--no-fail` | Don't exit with error code on failed tests |
754
+ | `-h, --help` | Show help message |
755
+
756
+ ### CI/CD Example (GitHub Actions)
757
+
758
+ ```yaml
759
+ jobs:
760
+ api-tests:
761
+ runs-on: ubuntu-latest
762
+ steps:
763
+ - uses: actions/checkout@v4
764
+
765
+ - name: Setup Node.js
766
+ uses: actions/setup-node@v4
767
+ with:
768
+ node-version: '20'
769
+
770
+ - name: Run API Tests
771
+ run: npx norn ./tests/ --junit --output-dir ./reports
772
+
773
+ - name: Upload Test Results
774
+ uses: actions/upload-artifact@v4
775
+ if: always()
776
+ with:
777
+ name: test-results
778
+ path: ./reports/*.xml
779
+ ```
780
+
781
+ ## Syntax Reference
782
+
783
+ | Syntax | Description |
784
+ |--------|-------------|
785
+ | `var name = value` | Declare a variable (literal) |
786
+ | `var name = "text"` | Declare a string variable |
787
+ | `var x = other.path` | Assign from expression (in sequences) |
788
+ | `{{name}}` | Reference a variable |
789
+ | `{{name.path}}` | Access nested property of JSON variable |
790
+ | `{{name[0].prop}}` | Access array element in JSON variable |
791
+ | `###` | Optional request separator |
792
+ | `[RequestName]` | Define a named reusable request |
793
+ | `run RequestName` | Execute a named request |
794
+ | `sequence Name` | Start a helper sequence block |
795
+ | `test sequence Name` | Start a test sequence (runs from CLI) |
796
+ | `@tagname` | Simple tag on a sequence |
797
+ | `@key(value)` | Key-value tag on a sequence |
798
+ | `end sequence` | End a sequence block |
799
+ | `var x = $1.path` | Capture value from response 1 |
800
+ | `$N.status` | Access status code of response N |
801
+ | `$N.body.path` | Access body property of response N |
802
+ | `$N.headers.Name` | Access header of response N |
803
+ | `$N.duration` | Access request duration in ms |
804
+ | `$N.cookies` | Access cookies from response N |
805
+
806
+ ### Assertions
807
+
808
+ | Syntax | Description |
809
+ |--------|-------------|
810
+ | `assert $1.status == 200` | Equality check |
811
+ | `assert $1.body.count != 0` | Inequality check |
812
+ | `assert $1.body.age > 18` | Greater than |
813
+ | `assert $1.body.age >= 18` | Greater than or equal |
814
+ | `assert $1.body.age < 100` | Less than |
815
+ | `assert $1.body.age <= 100` | Less than or equal |
816
+ | `assert $1.body.email contains "@"` | String contains |
817
+ | `assert $1.body.name startsWith "J"` | String starts with |
818
+ | `assert $1.body.name endsWith "n"` | String ends with |
819
+ | `assert $1.body.email matches "regex"` | Regex match |
820
+ | `assert $1.body.id exists` | Property exists |
821
+ | `assert $1.body.deleted !exists` | Property does not exist |
822
+ | `assert $1.body.id isType number` | Type check (number, string, boolean, array, object, null) |
823
+ | `assert $1.duration < 5000` | Duration/timing check |
824
+ | `assert ... \| "message"` | Custom failure message |
825
+
826
+ ### Control Flow
827
+
828
+ | Syntax | Description |
829
+ |--------|-------------|
830
+ | `if <condition>` | Start conditional block (uses assertion operators) |
831
+ | `end if` | End conditional block |
832
+ | `wait 2s` | Wait 2 seconds |
833
+ | `wait 500ms` | Wait 500 milliseconds |
834
+
835
+ ### Scripts & Data
836
+
837
+ | Syntax | Description |
838
+ |--------|-------------|
839
+ | `run bash ./script.sh` | Run a bash script |
840
+ | `run powershell ./script.ps1` | Run a PowerShell script |
841
+ | `run js ./script.js` | Run a Node.js script |
842
+ | `var x = run js ./script.js` | Run script and capture output |
843
+ | `var data = run readJson ./file.json` | Load JSON file into variable |
844
+ | `data.property = value` | Update loaded JSON property |
845
+ | `print Message` | Print a message |
846
+ | `print Title \| Body` | Print with expandable body |
847
+
848
+ ### Environments (.nornenv)
849
+
850
+ | Syntax | Description |
851
+ |--------|-------------|
852
+ | `var name = value` | Common variable (all environments) |
853
+ | `[env:name]` | Start environment section |
854
+ | `# comment` | Comment line |
855
+
856
+ ### API Definitions (.nornapi)
857
+
858
+ | Syntax | Description |
859
+ |--------|-------------|
860
+ | `headers Name` | Start header group definition |
861
+ | `end headers` | End header group definition |
862
+ | `HeaderName: value` | Define a header (inside headers block) |
863
+ | `endpoints` | Start endpoints block |
864
+ | `end endpoints` | End endpoints block |
865
+ | `Name: METHOD path` | Define named endpoint (e.g., `GetUser: GET /users/{id}`) |
866
+ | `{param}` | Path parameter placeholder |
867
+ | `swagger https://...` | Import from OpenAPI/Swagger URL |
868
+ | `import "./file.nornapi"` | Import .nornapi file |
869
+
870
+ ## Keyboard Shortcuts
871
+
872
+ | Shortcut | Command |
873
+ |----------|---------|
874
+ | `Ctrl+Alt+R` / `Cmd+Alt+R` | Send Request |
875
+
876
+ ## Extension Commands
877
+
878
+ - `Norn: Send Request` - Send the HTTP request at cursor
879
+ - `Norn: Run Sequence` - Run the sequence at cursor
880
+ - `Norn: Select Environment` - Choose the active environment from .nornenv
881
+ - `Norn: Clear Cookies` - Clear all stored cookies
882
+ - `Norn: Show Stored Cookies` - Display cookies in output
883
+
884
+ ## Requirements
885
+
886
+ - VS Code 1.108.1 or higher
887
+ - Node.js 22+ (for CLI and script execution)
888
+
889
+ ## Release Notes
890
+
891
+ ### 1.0.14
892
+
893
+ - **Variable Expression Assignment**: `var id = data.users[0].id` - extract values directly
894
+ - **String Literals**: `var name = "John"` - use quotes for string values
895
+ - **PowerShell Auto-Parsing**: Table and list output automatically converted to JSON
896
+ - **ANSI Code Stripping**: Clean PowerShell output without color code corruption
897
+ - **Improved Syntax Highlighting**: Different colors for strings, numbers, variables, booleans
898
+ - **IntelliSense in Assignments**: Shows defined variables after `var x = `
899
+ - **Invalid Assignment Diagnostics**: Red squiggly for unquoted strings with spaces
900
+
901
+ ### 1.0.13
902
+
903
+ - **Assertions**: Full assertion system with comparison, type checking, existence, and regex operators
904
+ - **Environments**: `.nornenv` file support with dev/staging/prod configurations
905
+ - **Named Requests**: Define reusable requests with `[RequestName]` syntax
906
+ - **Conditionals**: `if/end if` blocks for conditional execution
907
+ - **Wait Commands**: `wait 2s` / `wait 500ms` for delays
908
+ - **JSON File Loading**: `run readJson` to load test data from JSON files
909
+ - **Property Updates**: Modify loaded JSON data inline
910
+ - **CLI Environments**: `--env` flag to select environment in CLI
911
+
912
+ ### 1.0.0
913
+
914
+ - HTTP request support (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
915
+ - Variables with `var` and `{{}}`
916
+ - Sequences with response capture (`$N.path`)
917
+ - Script execution (`run bash/powershell/js`)
918
+ - Print statements with title and body
919
+ - Cookie jar with automatic persistence
920
+ - Full syntax highlighting
921
+ - IntelliSense for methods, headers, variables, keywords
922
+ - Diagnostic errors for undefined variables
923
+ - CLI for CI/CD pipelines
924
+ - JSON output mode for automation
925
+
926
+ ## License
927
+
928
+ **Free for Personal Use** - You may use Norn for personal projects, learning, education, and non-commercial open-source projects at no cost.
929
+
930
+ **30-Day Commercial Evaluation** - Businesses may evaluate Norn free for 30 days before purchasing a license.
931
+
932
+ **Commercial Use Requires a License** - After the evaluation period, use within a business, by employees during work, or in CI/CD pipelines for commercial projects requires a license. Contact us for commercial licensing options.
933
+
934
+ See the [LICENSE](LICENSE) file for full terms.
935
+
936
+ ---
937
+
938
+ **Enjoy using Norn!**