From df0a09be52e200a1a086c3f466195fc0b096b7a3 Mon Sep 17 00:00:00 2001 From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:48:50 +1100 Subject: [PATCH 1/4] Fixes --- README.md | 1 + .../OpenApiGeneratorTests.fs | 37 +++++++++++++++++++ .../src/test/e2e/explorer.e2e.test.ts | 36 ++++++++++++++++++ src/Napper.VsCode/src/test/helpers/helpers.ts | 7 ++++ website/src/blog/introducing-napper.md | 1 + website/src/docs/cli-reference.md | 8 ++++ website/src/docs/installation.md | 7 ++-- website/src/docs/vs-bruno.md | 1 + website/src/docs/vs-postman.md | 1 + website/src/index.njk | 16 ++++++++ 10 files changed, 112 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2e0547d..ed72d84 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,7 @@ Options: --var Variable override (repeatable) (cli-var) --output Output: pretty, junit, json, ndjson (cli-output) --output-dir Output directory for generate command (cli-output-dir) + --version Print the installed CLI version --verbose Enable debug-level logging (cli-verbose) ``` diff --git a/src/Napper.Core.Tests/OpenApiGeneratorTests.fs b/src/Napper.Core.Tests/OpenApiGeneratorTests.fs index 3f3309d..acbd15f 100644 --- a/src/Napper.Core.Tests/OpenApiGeneratorTests.fs +++ b/src/Napper.Core.Tests/OpenApiGeneratorTests.fs @@ -5,6 +5,7 @@ module OpenApiGeneratorTests // openapi-meta-flag, nap-meta, nap-request, nap-headers, nap-body, nap-vars, nap-assert open Xunit +open Napper.Core open Napper.Core.OpenApiGenerator // --- Helpers --- @@ -716,6 +717,42 @@ let ``Environment file has baseUrl key-value pair`` () = // --- Base URL fallback --- Spec: openapi-baseurl +// --- Generated files must be parseable --- Spec: openapi-nap-gen, nap-file + +[] +let ``Generated nap files are parseable by the nap parser`` () = + let gen = unwrap minimalOas3 + + for f in gen.NapFiles do + match Napper.Core.Parser.parseNapFile f.Content with + | Ok parsed -> + Assert.Equal(GET, parsed.Request.Method) + Assert.Contains("{{baseUrl}}/users", parsed.Request.Url) + | Error e -> failwith $"Generated file '{f.FileName}' failed to parse: {e}" + +[] +let ``Generated POST nap file is parseable with correct method and body`` () = + let gen = unwrap multiMethodSpec + let postFile = gen.NapFiles |> List.find (fun f -> f.Content.Contains("Create pet")) + + match Napper.Core.Parser.parseNapFile postFile.Content with + | Ok parsed -> + Assert.Equal(POST, parsed.Request.Method) + Assert.Contains("{{baseUrl}}/pets", parsed.Request.Url) + Assert.True(parsed.Request.Body.IsSome, "POST must have a request body") + | Error e -> failwith $"Generated POST file failed to parse: {e}" + +[] +let ``Generated nap file with path params is parseable`` () = + let gen = unwrap multiMethodSpec + let petFile = gen.NapFiles |> List.find (fun f -> f.Content.Contains("getPetById")) + + match Napper.Core.Parser.parseNapFile petFile.Content with + | Ok parsed -> + Assert.Contains("{{petId}}", parsed.Request.Url) + Assert.True(parsed.Vars.ContainsKey("petId"), "Must have petId var") + | Error e -> failwith $"Generated file with path params failed to parse: {e}" + [] let ``Falls back to default URL when no servers or host`` () = let spec = diff --git a/src/Napper.VsCode/src/test/e2e/explorer.e2e.test.ts b/src/Napper.VsCode/src/test/e2e/explorer.e2e.test.ts index 885dd0a..715b48d 100644 --- a/src/Napper.VsCode/src/test/e2e/explorer.e2e.test.ts +++ b/src/Napper.VsCode/src/test/e2e/explorer.e2e.test.ts @@ -5,6 +5,7 @@ import * as fs from 'fs'; import { activateExtension, closeAllEditors, + deleteFixtureDir, deleteFixtureFile, getFixturePath, openDocument, @@ -157,6 +158,41 @@ suite('Explorer Tree View', () => { ); }); + test('folder deletion triggers tree refresh via watcher', async function () { + this.timeout(20000); + const testDir = 'watcher-folder-test', + testFile = `${testDir}/probe.nap`, + napContent = '[request]\nmethod = "GET"\nurl = "https://httpbin.org/get"\n'; + + // Create a folder with a .nap file so it appears in the tree + writeFixtureFile(testFile, napContent); + await sleep(3000); + + const provider = getExplorerProvider(), + nodesAfterCreate = provider.getChildren(), + folderNode = findNodeByLabel(nodesAfterCreate, testDir); + assert.ok(folderNode, `Folder "${testDir}" must appear in explorer after creation`); + + // Listen for onDidChangeTreeData — this is what VS Code uses to know + // it should call getChildren() again and repaint the tree + let refreshFired = false; + const disposable = provider.onDidChangeTreeData(() => { + refreshFired = true; + }); + + // Delete the entire folder from disk (not individual files) + deleteFixtureDir(testDir); + + // Wait for the watcher to fire a refresh + await sleep(5000); + disposable.dispose(); + + assert.ok( + refreshFired, + 'onDidChangeTreeData must fire after a folder is deleted — the tree must react to folder removal', + ); + }); + test('nested playlist in file tree also expands with children', function () { this.timeout(10000); const provider = getExplorerProvider(), diff --git a/src/Napper.VsCode/src/test/helpers/helpers.ts b/src/Napper.VsCode/src/test/helpers/helpers.ts index 9b3c78f..f1c5715 100644 --- a/src/Napper.VsCode/src/test/helpers/helpers.ts +++ b/src/Napper.VsCode/src/test/helpers/helpers.ts @@ -82,6 +82,13 @@ export const deleteFixtureFile = (relativePath: string): void => { } }; +export const deleteFixtureDir = (relativePath: string): void => { + const fullPath = getFixturePath(relativePath); + if (fs.existsSync(fullPath)) { + fs.rmSync(fullPath, { recursive: true }); + } +}; + export const waitForCondition = async ( condition: () => boolean | Promise, timeout = 10000, diff --git a/website/src/blog/introducing-napper.md b/website/src/blog/introducing-napper.md index 8d5861c..038bdac 100644 --- a/website/src/blog/introducing-napper.md +++ b/website/src/blog/introducing-napper.md @@ -292,6 +292,7 @@ code --install-extension nimblesite.napper | Scripting language | **C# + F# (.NET)** | Sandboxed JS | Sandboxed JS | None | | CI/CD output | JUnit, JSON, NDJSON | Via Newman | Via CLI | None | | Test Explorer | Native | No | No | No | +| OpenAPI import | URL + file + AI | Import only | Import only | No | | .http file migration | Built-in converter | Import only | No | N/A | | Account required | No | Yes | No | No | | Price | Free (MIT) | Freemium | Free (MIT) | Free | diff --git a/website/src/docs/cli-reference.md b/website/src/docs/cli-reference.md index 9309d97..faacc7d 100644 --- a/website/src/docs/cli-reference.md +++ b/website/src/docs/cli-reference.md @@ -36,6 +36,14 @@ napper run ./tests/ | `--output ` | Output format | `--output junit` | (spec: cli-output) | | `--verbose` | Enable verbose output with detailed request/response info | `--verbose` | (spec: cli-verbose) | +### `napper --version` + +Print the installed CLI version. + +```bash +napper --version +``` + #### Output formats (spec: cli-output) | Format | Description | Spec | diff --git a/website/src/docs/installation.md b/website/src/docs/installation.md index 55b06b5..b39092a 100644 --- a/website/src/docs/installation.md +++ b/website/src/docs/installation.md @@ -12,7 +12,7 @@ eleventyNavigation: ## Download from GitHub Releases (spec: cli-run) -The fastest way to get Napper is to download the CLI binary from [GitHub Releases](https://github.com/MelbourneDeveloper/napper/releases). The current release is **v0.9.0**. +The fastest way to get Napper is to download the CLI binary from [GitHub Releases](https://github.com/MelbourneDeveloper/napper/releases). The current release is **v0.10.0**. | Platform | Binary | |----------|--------| @@ -26,6 +26,7 @@ Download the binary, make it executable (`chmod +x` on macOS/Linux), and move it ### Verify installation ```bash +napper --version napper --help ``` @@ -42,7 +43,7 @@ curl -fsSL https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scri Or install a specific version: ```bash -curl -fsSL https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scripts/install.sh | bash -s 0.9.0 +curl -fsSL https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scripts/install.sh | bash -s 0.10.0 ``` ### Windows @@ -54,7 +55,7 @@ irm https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scripts/ins Or install a specific version: ```powershell -.\scripts\install.ps1 -Version 0.9.0 +.\scripts\install.ps1 -Version 0.10.0 ``` ## Build from source diff --git a/website/src/docs/vs-bruno.md b/website/src/docs/vs-bruno.md index c227716..3e5ee51 100644 --- a/website/src/docs/vs-bruno.md +++ b/website/src/docs/vs-bruno.md @@ -44,6 +44,7 @@ Bruno provides a CLI for running collections from the terminal. Napper is design | Editor integration | Native VS Code extension | Standalone Electron app | | Test Explorer | Native VS Code support | No | | CI/CD output | JUnit, JSON, NDJSON | JSON via CLI | +| OpenAPI import | URL + file + AI | Import only | | .http file migration | Built-in converter | No | | Pricing | Free, MIT license | Free, MIT license | diff --git a/website/src/docs/vs-postman.md b/website/src/docs/vs-postman.md index 6308fd1..17f59e1 100644 --- a/website/src/docs/vs-postman.md +++ b/website/src/docs/vs-postman.md @@ -44,6 +44,7 @@ Postman requires Newman (a separate npm package) for running collections from th | CI/CD output | JUnit, JSON, NDJSON | Via Newman | | Test Explorer | Native VS Code support | No | | Account required | No | Yes | +| OpenAPI import | URL + file + AI | Import only | | .http file migration | Built-in converter | Import only | | Pricing | Free, MIT license | Freemium with paid tiers | diff --git a/website/src/index.njk b/website/src/index.njk index aafeb74..4470120 100644 --- a/website/src/index.njk +++ b/website/src/index.njk @@ -114,6 +114,14 @@ permalink: /

Every request is a .nap file. Every environment is a .napenv file. Version control everything. No binary blobs, no lock-in.

+
+
+ +
+

OpenAPI Import

+

Generate .nap test files from any OpenAPI spec. Import from a URL or a local file. Optionally enhance with AI via GitHub Copilot for smarter assertions and realistic test data.

+
+
@@ -200,6 +208,13 @@ permalink: / Yes Yes + + OpenAPI import + URL + file + AI + Import only + Import only + No + .http file migration Built-in converter @@ -479,6 +494,7 @@ permalink: / "Environment variable management with .napenv files", "JUnit XML, JSON, NDJSON output for CI/CD", "Native VS Code Test Explorer integration", + "OpenAPI import from URL or file with optional AI enhancement", "Built-in .http file converter for Microsoft and JetBrains formats" ] } From fddd560011cb02805041a59f3d528c3e4f33a016 Mon Sep 17 00:00:00 2001 From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:48:46 +1100 Subject: [PATCH 2/4] Fixes: OpenAPI improvements, website docs update, watcher tweaks Co-Authored-By: Claude Sonnet 4.6 --- README.md | 101 ++++-- src/Napper.Core.Tests/OpenApiE2eTests.fs | 8 +- .../OpenApiGeneratorTests.fs | 6 +- src/Napper.Core/OpenApiGenerator.fs | 5 +- src/Napper.Core/OpenApiTypes.fs | 6 + src/Napper.VsCode/src/constants.ts | 1 + src/Napper.VsCode/src/watchers.ts | 6 +- website/src/docs/index.md | 5 + website/src/docs/installation.md | 221 ++++++++++-- website/src/docs/openapi-import.md | 327 ++++++++++++++++++ website/src/docs/quick-start.md | 5 + 11 files changed, 624 insertions(+), 67 deletions(-) create mode 100644 website/src/docs/openapi-import.md diff --git a/README.md b/README.md index ed72d84..e7e74a3 100644 --- a/README.md +++ b/README.md @@ -38,17 +38,60 @@ Everything you need for API testing. Nothing you don't. - **OpenAPI Import** (`openapi-generate`) — Generate test files from any OpenAPI spec. Point it at a file, and Napper creates `.nap` files with requests, headers, bodies, and assertions. Optionally enhance with AI via GitHub Copilot (`vscode-openapi-ai`). - **Plain Text, Git Friendly** (`nap-file`) — Every request is a `.nap` file. Every environment is a `.napenv` file (`env-file`). Version control everything. No binary blobs, no lock-in. -## Quick Start +## Installation + +### VS Code Extension -### Install the VS Code Extension +Install from the marketplace in one command: ```sh code --install-extension nimblesite.napper ``` -### Or grab the CLI binary +Or search **"Napper"** in the VS Code Extensions panel (`Ctrl+Shift+X` / `Cmd+Shift+X`) and click Install. + +To install a specific `.vsix` manually: open the Extensions panel → `...` menu → **Install from VSIX...**. + +> **Requirements:** VS Code 1.95.0 or later. The extension shells out to the CLI, so install the CLI binary too. + +### CLI Binary + +The CLI is a self-contained binary with **no runtime dependencies**. + +| Platform | Download | +|----------|----------| +| macOS (Apple Silicon) | [`napper-osx-arm64`](https://github.com/MelbourneDeveloper/napper/releases/latest/download/napper-osx-arm64) | +| macOS (Intel) | [`napper-osx-x64`](https://github.com/MelbourneDeveloper/napper/releases/latest/download/napper-osx-x64) | +| Linux (x64) | [`napper-linux-x64`](https://github.com/MelbourneDeveloper/napper/releases/latest/download/napper-linux-x64) | +| Windows (x64) | [`napper-win-x64.exe`](https://github.com/MelbourneDeveloper/napper/releases/latest/download/napper-win-x64.exe) | + +**macOS / Linux:** +```sh +chmod +x napper-osx-arm64 +mv napper-osx-arm64 /usr/local/bin/napper +napper --version +``` + +**Install script (macOS / Linux):** +```sh +curl -fsSL https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scripts/install.sh | bash +``` -Download from the [latest release](https://github.com/MelbourneDeveloper/napper/releases). +**Install script (Windows PowerShell):** +```powershell +irm https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scripts/install.ps1 | iex +``` + +**Build from source** (requires .NET SDK + `make`): +```sh +git clone https://github.com/MelbourneDeveloper/napper.git && cd napper && make install-binaries +``` + +> **Note:** F# (`.fsx`) and C# (`.csx`) script hooks require the [.NET 10 SDK](https://dotnet.microsoft.com/download). Plain `.nap` and `.naplist` files need nothing extra. + +See the [full installation guide](https://napperapi.dev/docs/installation/) for VSIX manual install, troubleshooting, and macOS Gatekeeper notes. + +## Quick Start ## How do you use Napper? @@ -188,7 +231,9 @@ Variable priority (highest wins): ## OpenAPI Import -Generate `.nap` test files automatically from any OpenAPI specification. Available from the CLI and the VS Code extension. +Generate `.nap` test files automatically from any OpenAPI or Swagger spec. Napper creates one file per operation, a `.naplist` playlist, and a `.napenv` environment file — giving you a working test suite in seconds. + +**Supported formats:** OpenAPI 3.0.x, OpenAPI 3.1.x, Swagger 2.0 (JSON input). ### From the CLI @@ -196,36 +241,48 @@ Generate `.nap` test files automatically from any OpenAPI specification. Availab # Generate from a local spec file napper generate openapi ./petstore.json --output-dir ./tests -# Output as JSON (for programmatic use) -napper generate openapi ./spec.yaml --output-dir ./tests --output json +# Output a JSON summary for scripting +napper generate openapi ./spec.json --output-dir ./tests --output json ``` ### From VS Code -The extension provides two commands (accessible via the Command Palette): +Open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) and choose: -- **Napper: Import OpenAPI from URL** — Enter a URL to an OpenAPI spec (e.g. `https://petstore3.swagger.io/api/v3/openapi.json`). The extension downloads the spec, generates `.nap` files, and creates a `.naplist` playlist. -- **Napper: Import OpenAPI from File** — Select a local OpenAPI spec file (JSON or YAML) and an output folder. +- **Napper: Import OpenAPI from URL** — paste a URL (e.g. `https://petstore3.swagger.io/api/v3/openapi.json`). Napper downloads the spec and generates files. +- **Napper: Import OpenAPI from File** — browse to a local `.json` spec file. -Both commands prompt you to choose between basic generation or AI-enhanced generation (requires GitHub Copilot). AI enhancement adds smarter assertions, realistic test data, and reorders the playlist for logical test flow. +Both commands prompt for an output folder and offer basic or AI-enhanced generation. ### What gets generated -| File | Purpose | -|------|---------| -| `01_get-users.nap`, `02_post-users.nap`, ... | One `.nap` file per API endpoint with request, headers, body, and assertions | -| `api-name.naplist` | Playlist referencing all generated files in order | -| `.napenv` | Environment file with the API base URL | +Endpoints are grouped into subdirectories by API tag: + +``` +tests/ +├── pets/ +│ ├── get-pets.nap +│ ├── post-pets.nap +│ └── get-pets-petId.nap +├── store/ +│ └── get-store-inventory.nap +├── petstore.naplist +└── .napenv +``` + +Each `.nap` file includes the method, URL (with path params as `{{variables}}`), auth headers, request body (from schema), and status code assertions. The `.napenv` file contains the base URL from the spec's `servers` field and variable placeholders for auth tokens. + +### AI Enhancement (optional) -### AI Enhancement (Optional) +With GitHub Copilot available, choose AI-enhanced generation to get: -When GitHub Copilot is available, you can opt for AI-enhanced generation which: +- Semantic assertions beyond status codes (e.g. `body.email contains @`) +- Realistic test data in request bodies instead of placeholder values +- Logical playlist ordering (auth first, then CRUD in dependency order) -- Adds semantic assertions beyond basic status checks (e.g. `body.email contains @`) -- Generates realistic test data for request bodies -- Reorders the playlist for logical flow (auth first, then CRUD operations) +Falls back to basic generation automatically if Copilot is unavailable. -If Copilot is not available, a warning is shown and basic generation proceeds normally. +See the [full OpenAPI import guide](https://napperapi.dev/docs/openapi-import/) for authentication handling, `$ref` resolution, customisation tips, and troubleshooting. ## CLI Reference diff --git a/src/Napper.Core.Tests/OpenApiE2eTests.fs b/src/Napper.Core.Tests/OpenApiE2eTests.fs index edf924d..f591ad4 100644 --- a/src/Napper.Core.Tests/OpenApiE2eTests.fs +++ b/src/Napper.Core.Tests/OpenApiE2eTests.fs @@ -184,7 +184,7 @@ let ``Petstore POST endpoints have request body`` () = allNaps |> Array.filter (fun f -> let content = File.ReadAllText(f) - content.Contains("POST {{baseUrl}}")) + content.Contains("method = POST")) Assert.True(postFiles.Length >= 1, "Must have at least one POST endpoint") @@ -397,7 +397,7 @@ let ``Beeceptor POST endpoints have request body and headers`` () = allNaps |> Array.filter (fun f -> let content = File.ReadAllText(f) - content.Contains "POST {{baseUrl}}") + content.Contains "method = POST") // auth/register, auth/login, cart/items, checkout, addresses POST = 5 Assert.True(postFiles.Length >= 5, $"Must have at least 5 POST endpoints, got {postFiles.Length}") @@ -483,7 +483,7 @@ let ``Beeceptor checkout endpoint asserts 201 status`` () = allNaps |> Array.filter (fun f -> let content = File.ReadAllText(f) - content.Contains("POST {{baseUrl}}/checkout")) + content.Contains("method = POST") && content.Contains("url = {{baseUrl}}/checkout")) Assert.True(checkoutFiles.Length >= 1, "Must have checkout endpoint") let content = File.ReadAllText(checkoutFiles[0]) @@ -525,7 +525,7 @@ let ``Petstore POST endpoints include actual JSON body content`` () = allNaps |> Array.filter (fun f -> let content = File.ReadAllText(f) - content.Contains("POST {{baseUrl}}") && content.Contains("[request.body]")) + content.Contains("method = POST") && content.Contains("[request.body]")) Assert.True(postFilesWithBody.Length >= 1, "Must have POST endpoints with body") diff --git a/src/Napper.Core.Tests/OpenApiGeneratorTests.fs b/src/Napper.Core.Tests/OpenApiGeneratorTests.fs index acbd15f..5d6e293 100644 --- a/src/Napper.Core.Tests/OpenApiGeneratorTests.fs +++ b/src/Napper.Core.Tests/OpenApiGeneratorTests.fs @@ -154,7 +154,8 @@ let ``OAS3 nap file contains meta section with name`` () = let ``OAS3 nap file contains request section`` () = let content = (unwrap minimalOas3 |> firstFile).Content Assert.Contains("[request]", content) - Assert.Contains("GET {{baseUrl}}/users", content) + Assert.Contains("method = GET", content) + Assert.Contains("url = {{baseUrl}}/users", content) [] let ``OAS3 nap file contains assert section`` () = @@ -201,7 +202,8 @@ let ``Swagger2 generates nap file`` () = let gen = unwrap minimalSwagger2 Assert.Equal(1, gen.NapFiles.Length) let content = (firstFile gen).Content - Assert.Contains("GET {{baseUrl}}/items", content) + Assert.Contains("method = GET", content) + Assert.Contains("url = {{baseUrl}}/items", content) // --- Multiple endpoints --- Spec: openapi-nap-gen, openapi-params, openapi-assert-gen diff --git a/src/Napper.Core/OpenApiGenerator.fs b/src/Napper.Core/OpenApiGenerator.fs index 449197c..2e897e9 100644 --- a/src/Napper.Core/OpenApiGenerator.fs +++ b/src/Napper.Core/OpenApiGenerator.fs @@ -344,7 +344,10 @@ let private buildRequest (ep: EndpointInfo) : string list = let url = sprintf "%s%s%s" BaseUrlVar (convertPathParams ep.UrlPath) (buildQuery ep.QueryParams) - [ SectionRequest; sprintf "%s %s" (ep.Method.ToUpperInvariant()) url; "" ] + [ SectionRequest + sprintf "%s = %s" KeyMethod (ep.Method.ToUpperInvariant()) + sprintf "%s = %s" KeyUrl url + "" ] let private buildHeaders (ep: EndpointInfo) : string list = let hasBody = methodHasBody ep.Method diff --git a/src/Napper.Core/OpenApiTypes.fs b/src/Napper.Core/OpenApiTypes.fs index 4d15b87..739406f 100644 --- a/src/Napper.Core/OpenApiTypes.fs +++ b/src/Napper.Core/OpenApiTypes.fs @@ -60,6 +60,12 @@ let KeyName = "name" [] let KeyDescription = "description" +[] +let KeyMethod = "method" + +[] +let KeyUrl = "url" + [] let KeyGenerated = "generated" diff --git a/src/Napper.VsCode/src/constants.ts b/src/Napper.VsCode/src/constants.ts index a43335e..d928298 100644 --- a/src/Napper.VsCode/src/constants.ts +++ b/src/Napper.VsCode/src/constants.ts @@ -12,6 +12,7 @@ export const CSX_EXTENSION = '.csx'; export const NAP_GLOB = '**/*.nap'; export const NAPLIST_GLOB = '**/*.naplist'; export const NAPENV_GLOB = '**/.napenv*'; +export const DIRECTORY_GLOB = '**/'; // View IDs export const VIEW_EXPLORER = 'napperExplorer'; diff --git a/src/Napper.VsCode/src/watchers.ts b/src/Napper.VsCode/src/watchers.ts index e8ca91d..491a79c 100644 --- a/src/Napper.VsCode/src/watchers.ts +++ b/src/Napper.VsCode/src/watchers.ts @@ -7,6 +7,7 @@ import type { Logger } from './logger'; import { CONFIG_AUTO_RUN, CONFIG_SECTION, + DIRECTORY_GLOB, LOG_MSG_TREE_REFRESH, NAPLIST_EXTENSION, NAPLIST_GLOB, @@ -24,6 +25,7 @@ export const registerWatchers = ( ): void => { const napWatcher = vscode.workspace.createFileSystemWatcher(NAP_GLOB), naplistWatcher = vscode.workspace.createFileSystemWatcher(NAPLIST_GLOB), + dirWatcher = vscode.workspace.createFileSystemWatcher(DIRECTORY_GLOB), refreshExplorer = (): void => { log.debug(LOG_MSG_TREE_REFRESH); explorer.refresh(); @@ -34,7 +36,9 @@ export const registerWatchers = ( naplistWatcher.onDidCreate(refreshExplorer); naplistWatcher.onDidDelete(refreshExplorer); naplistWatcher.onDidChange(refreshExplorer); - context.subscriptions.push(napWatcher, naplistWatcher); + dirWatcher.onDidCreate(refreshExplorer); + dirWatcher.onDidDelete(refreshExplorer); + context.subscriptions.push(napWatcher, naplistWatcher, dirWatcher); }; export const registerAutoRun = ( diff --git a/website/src/docs/index.md b/website/src/docs/index.md index 9b034c2..007aa43 100644 --- a/website/src/docs/index.md +++ b/website/src/docs/index.md @@ -10,6 +10,8 @@ eleventyNavigation: # Introduction +![Screenshot: Napper VS Code extension showing the request explorer panel, syntax-highlighted .nap file, and response viewer with JSON body and assertion results](introduction-overview.png) + **Napper** is a free, open-source, CLI-first API testing tool that integrates natively with VS Code. It is a modern alternative to Postman, Bruno, `.http` files, and curl. Napper is built for developers who want: @@ -81,9 +83,12 @@ The converter maps variables to `.napenv` files, preserves request names, and co Napper is not a GUI-first tool with a CLI bolted on. The CLI is the primary interface. The VS Code extension operates on the same files and provides the same features in your editor. This means your API tests work the same way locally and in CI/CD, with no import/export step. +![Screenshot: Napper CLI running a .naplist test suite, displaying coloured pass/fail output for each assertion across multiple endpoints](introduction-cli-output.png) + ## Next steps - [Install Napper](/docs/installation/) to get started - Follow the [Quick Start](/docs/quick-start/) guide - Learn about [.nap file format](/docs/nap-files/) +- [Import an OpenAPI spec](/docs/openapi-import/) to generate tests automatically - [Migrate from .http files](/docs/vs-http-files/) with the built-in converter diff --git a/website/src/docs/installation.md b/website/src/docs/installation.md index b39092a..7b8ff94 100644 --- a/website/src/docs/installation.md +++ b/website/src/docs/installation.md @@ -1,8 +1,8 @@ --- layout: layouts/docs.njk title: Installation -description: "Install Napper CLI and VS Code extension. Available for macOS, Linux, and Windows." -keywords: "install napper, VS Code extension, CLI binary, macOS, Linux, Windows" +description: "Install Napper CLI and VS Code extension. Available for macOS, Linux, and Windows. No account required, no runtime dependencies." +keywords: "install napper, VS Code extension, VSIX install, CLI binary, macOS, Linux, Windows, dotnet tool" eleventyNavigation: key: Installation order: 2 @@ -10,9 +10,76 @@ eleventyNavigation: # Installation -## Download from GitHub Releases (spec: cli-run) +![Screenshot: Napper VS Code extension installed and active in the VS Code Activity Bar, showing the Napper panel icon](installation-vscode-activity-bar.png) -The fastest way to get Napper is to download the CLI binary from [GitHub Releases](https://github.com/MelbourneDeveloper/napper/releases). The current release is **v0.10.0**. +Napper has two components: the **CLI binary** and the **VS Code extension**. The CLI is standalone with no runtime dependencies. The extension shells out to the CLI, so you need both for full VS Code integration. + +--- + +## VS Code Extension + +![Screenshot: Napper extension listing on the VS Code Marketplace, showing install button, ratings, and feature highlights](installation-marketplace-listing.png) + +### Install from the Marketplace + +The easiest way to get Napper in VS Code is from the marketplace: + +**Option 1 — Marketplace UI:** +1. Open VS Code +2. Click the **Extensions** icon in the Activity Bar (or press `Ctrl+Shift+X` / `Cmd+Shift+X`) +3. Search for **Napper** +4. Click **Install** on the result published by **Nimblesite** + +**Option 2 — Command line:** +```bash +code --install-extension nimblesite.napper +``` + +**Option 3 — Quick Open:** +Press `Ctrl+P` / `Cmd+P` and run: +``` +ext install nimblesite.napper +``` + +### Install a VSIX manually + +If you need a specific version or are working in an air-gapped environment, download the `.vsix` file from [GitHub Releases](https://github.com/MelbourneDeveloper/napper/releases) and install it manually. + +**Via the VS Code UI:** +1. Download `napper-.vsix` from the [Releases page](https://github.com/MelbourneDeveloper/napper/releases) +2. Open the Extensions panel (`Ctrl+Shift+X` / `Cmd+Shift+X`) +3. Click the `...` menu (top-right of the panel) +4. Select **Install from VSIX...** +5. Browse to the downloaded `.vsix` file and click **Install** + +**Via the command line:** +```bash +code --install-extension napper-0.10.0.vsix +``` + +### What the extension provides + +Once installed, Napper adds: + +- **Syntax highlighting** for `.nap`, `.naplist`, and `.napenv` files +- **Napper panel** in the Activity Bar with a request and playlist explorer +- **Test Explorer integration** — run and inspect results without leaving VS Code +- **Environment switcher** in the status bar +- **CodeLens actions** — click **Run** or **Copy as curl** above any request +- **OpenAPI import** — generate test files from any OpenAPI/Swagger spec +- **AI enrichment** — optional GitHub Copilot integration for smarter assertions + +--- + +## CLI Binary + +![Screenshot: Napper CLI running a test suite in a terminal, showing coloured pass/fail output for each assertion](installation-cli-terminal.png) + +The CLI is a self-contained binary with **no runtime dependencies** — no .NET, no Node, no Python required. + +### Download from GitHub Releases + +Download the binary for your platform from [GitHub Releases](https://github.com/MelbourneDeveloper/napper/releases). The current release is **v0.10.0**. | Platform | Binary | |----------|--------| @@ -21,46 +88,44 @@ The fastest way to get Napper is to download the CLI binary from [GitHub Release | Linux (x64) | [`napper-linux-x64`](https://github.com/MelbourneDeveloper/napper/releases/latest/download/napper-linux-x64) | | Windows (x64) | [`napper-win-x64.exe`](https://github.com/MelbourneDeveloper/napper/releases/latest/download/napper-win-x64.exe) | -Download the binary, make it executable (`chmod +x` on macOS/Linux), and move it somewhere on your PATH. - -### Verify installation - +**macOS / Linux — make it executable and move to PATH:** ```bash -napper --version -napper --help +# Example for macOS Apple Silicon +chmod +x napper-osx-arm64 +mv napper-osx-arm64 /usr/local/bin/napper ``` -## Install script +**Windows — add to PATH:** + +Move `napper-win-x64.exe` to a folder on your `PATH`, or rename it to `napper.exe` and add its directory to your system PATH via System Properties → Environment Variables. -Alternatively, use the install script which auto-detects your platform and verifies the SHA256 checksum. +### Install script (macOS / Linux) -### macOS / Linux +The install script auto-detects your platform and verifies the SHA256 checksum: ```bash curl -fsSL https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scripts/install.sh | bash ``` -Or install a specific version: - +Install a specific version: ```bash curl -fsSL https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scripts/install.sh | bash -s 0.10.0 ``` -### Windows +### Install script (Windows) ```powershell irm https://raw.githubusercontent.com/MelbourneDeveloper/napper/main/scripts/install.ps1 | iex ``` -Or install a specific version: - +Install a specific version: ```powershell .\scripts\install.ps1 -Version 0.10.0 ``` -## Build from source +### Build from source -If you have the .NET SDK and `make` installed, you can build and install the CLI from source: +If you have the .NET SDK and `make` installed, you can build from source: ```bash git clone https://github.com/MelbourneDeveloper/napper.git @@ -68,34 +133,116 @@ cd napper make install-binaries ``` -This builds the CLI for your platform and installs it to `~/.local/bin/napper`. +This builds a self-contained, trimmed, single-file binary for your platform and installs it to `~/.local/bin/napper`. -## VS Code Extension +### Verify the installation -The extension provides editor integration but relies on the CLI binary to run requests. Install the CLI first (see above), then install the extension. +```bash +napper --version +napper --help +``` -Install from the marketplace: +You should see the version number and the list of available commands. + +--- +## Prerequisites + +| Scenario | Requirement | +|----------|-------------| +| Running `.nap` / `.naplist` files | None — the CLI binary is self-contained | +| VS Code extension | VS Code 1.95.0 or later | +| F# script hooks (`.fsx`) | [.NET 10 SDK](https://dotnet.microsoft.com/download) | +| C# script hooks (`.csx`) | [.NET 10 SDK](https://dotnet.microsoft.com/download) | +| Building from source | .NET 10 SDK + `make` | + +No account is required. Napper is entirely open source and free. + +--- + +## First-time setup + +![Screenshot: VS Code workspace with a .nap file open, CodeLens Run button visible above the request line, and the Napper Explorer panel populated with discovered requests](installation-first-time-setup.png) + +After installing both components: + +**1. Verify the CLI is on your PATH** + +Open a terminal and run: ```bash -code --install-extension nimblesite.napper +napper --version +``` + +If VS Code cannot find the CLI, set the path explicitly in VS Code settings: +```json +{ + "napper.cliPath": "/usr/local/bin/napper" +} +``` + +**2. Open a folder with `.nap` files** + +The Napper panel in the Activity Bar will automatically discover all `.nap` and `.naplist` files in your workspace. If you do not have any yet, create a simple one: + +``` +GET https://jsonplaceholder.typicode.com/posts/1 +``` + +Save it as `hello.nap`. + +**3. Run your first request** + +- **From VS Code**: Click the **Run** CodeLens link above the request line, or click the play button in the Napper Explorer panel. +- **From the CLI**: `napper run ./hello.nap` + +**4. Set up environments (optional)** + +Create a `.napenv` file in your project root with shared variables: + +``` +baseUrl = https://api.example.com +``` + +Create a `.napenv.local` file (add to `.gitignore`) for secrets: + +``` +token = your-secret-token +apiKey = your-api-key +``` + +Switch environments from the VS Code status bar or with `--env` on the CLI. + +--- + +## Troubleshooting + +**VS Code says it cannot find the `napper` CLI** + +Make sure the CLI binary is on your system PATH. Test by opening a terminal inside VS Code (`Ctrl+`` `) and running `napper --version`. If it works there but not in the extension, set `napper.cliPath` explicitly in your VS Code settings. + +**`chmod +x` is required on macOS / Linux** + +After downloading the binary, you must make it executable before running it: +```bash +chmod +x napper-osx-arm64 ``` -Or search for **"Napper"** in the VS Code Extensions panel. +**macOS Gatekeeper warning** -The extension provides: -- Syntax highlighting for `.nap`, `.naplist`, and `.napenv` files -- Request explorer in the activity bar -- Test Explorer integration -- Environment switching via status bar -- CodeLens actions (Run, Copy as curl) +On macOS, you may see a warning that the binary is from an unidentified developer. Right-click the binary and choose **Open**, or run: +```bash +xattr -dr com.apple.quarantine /usr/local/bin/napper +``` -## Requirements (spec: script-fsx, script-csx) +**Script hooks fail with "dotnet not found"** -- **CLI**: Self-contained binary, no runtime dependencies -- **VS Code Extension**: VS Code 1.100.0 or later -- **F# / C# Scripts**: .NET 10 SDK (only needed if using `.fsx` or `.csx` script hooks) +F# (`.fsx`) and C# (`.csx`) script hooks require the .NET 10 SDK. Download it from [dotnet.microsoft.com](https://dotnet.microsoft.com/download). Plain `.nap` and `.naplist` files do not need the SDK. + +--- ## Next steps -- Follow the [Quick Start](/docs/quick-start/) guide +- Follow the [Quick Start](/docs/quick-start/) guide to create your first request and test suite - Learn the [.nap file format](/docs/nap-files/) +- Import an existing API spec with [OpenAPI import](/docs/openapi-import/) +- Set up [environments](/docs/environments/) for local, staging, and production diff --git a/website/src/docs/openapi-import.md b/website/src/docs/openapi-import.md new file mode 100644 index 0000000..82e24a7 --- /dev/null +++ b/website/src/docs/openapi-import.md @@ -0,0 +1,327 @@ +--- +layout: layouts/docs.njk +title: OpenAPI Import +description: "Import OpenAPI and Swagger specs into Napper. Generate .nap test files, playlists, and environment files automatically from any OpenAPI 3.x or Swagger 2.0 spec." +keywords: "OpenAPI import, Swagger import, generate API tests, OpenAPI 3.0, OpenAPI 3.1, Swagger 2.0, test generation, nap files" +eleventyNavigation: + key: OpenAPI Import + order: 11 +--- + +# OpenAPI Import + +![Screenshot: VS Code Command Palette showing the Napper Import OpenAPI commands — Import from URL and Import from File](openapi-import-command-palette.png) + +Napper can generate `.nap` test files, a `.naplist` playlist, and a `.napenv` environment file directly from any OpenAPI or Swagger specification. This gives you a working test suite for an entire API in seconds. + +--- + +## Supported spec versions + +| Format | Versions | Input type | +|--------|----------|------------| +| OpenAPI | 3.0.x, 3.1.x | JSON | +| Swagger | 2.0 | JSON | + +YAML specs must be converted to JSON first (use [swagger2openapi](https://github.com/Mermade/oas-kit) or any online converter). The parser uses the official [Microsoft.OpenApi](https://github.com/microsoft/OpenAPI.NET) library — no regex, no custom parsing. + +--- + +## Import from VS Code + +![Screenshot: Napper OpenAPI import dialog in VS Code with a URL field pointing to the Petstore spec, and basic vs AI-enhanced generation options](openapi-import-url-dialog.png) + +Two commands are available from the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`): + +### Import from URL + +**Napper: Import OpenAPI from URL** + +1. Open the Command Palette +2. Type **Napper: Import OpenAPI from URL** +3. Enter the URL to the spec — for example: `https://petstore3.swagger.io/api/v3/openapi.json` +4. Choose an output folder where the generated files will be saved +5. Choose **Basic** or **AI-enhanced** generation (see [AI Enhancement](#ai-enhancement-optional)) + +Napper downloads the spec, parses it, and writes the generated files to the output folder. + +### Import from File + +**Napper: Import OpenAPI from File** + +1. Open the Command Palette +2. Type **Napper: Import OpenAPI from File** +3. Browse to a local `.json` spec file +4. Choose an output folder +5. Choose **Basic** or **AI-enhanced** generation + +--- + +## Import from the CLI + +```bash +# Generate from a local spec file +napper generate openapi ./petstore.json --output-dir ./tests + +# Output a JSON summary (useful for scripting) +napper generate openapi ./spec.json --output-dir ./tests --output json +``` + +The `--output-dir` flag specifies where generated files are written. The directory is created if it does not exist. + +--- + +## What gets generated + +![Screenshot: VS Code Explorer panel showing the generated folder structure — subdirectories per API tag, .nap files per endpoint, a .naplist playlist, and a .napenv environment file](openapi-import-generated-files.png) + +Given a spec for an API called "Petstore" with endpoints grouped under the tags `pets` and `store`, Napper generates: + +``` +tests/ +├── pets/ +│ ├── get-pets.nap +│ ├── post-pets.nap +│ ├── get-pets-petId.nap +│ ├── put-pets-petId.nap +│ └── delete-pets-petId.nap +├── store/ +│ ├── get-store-inventory.nap +│ └── post-store-order.nap +├── petstore.naplist +└── .napenv +``` + +### One `.nap` file per operation + +Each file contains a complete request with metadata, variables, headers, body (where applicable), and assertions: + +``` +[meta] +name = GET /pets/{petId} +description = Find pet by ID +generated = true + +[vars] +petId = 1 + +[request] +GET {{baseUrl}}/pets/{{petId}} + +[request.headers] +Accept = application/json +Authorization = Bearer {{bearerToken}} + +[assert] +status = 200 +body.id exists +body.name exists +``` + +Key details: +- Path parameters like `{petId}` are converted to Napper variables `{{petId}}` and declared in `[vars]` +- Query parameters are added to `[vars]` with placeholder values +- Auth headers (Bearer, Basic, API key) are added based on the spec's security schemes, with variables declared in `.napenv` +- Request bodies are generated from the schema with example values +- The `generated = true` flag in `[meta]` marks the file as auto-generated + +### The `.naplist` playlist + +A playlist referencing all generated files, ordered by HTTP method (GET → POST → PUT → PATCH → DELETE): + +``` +[meta] +name = Petstore API Tests + +[steps] +./pets/get-pets.nap +./pets/post-pets.nap +./pets/get-pets-petId.nap +./pets/put-pets-petId.nap +./pets/delete-pets-petId.nap +./store/get-store-inventory.nap +./store/post-store-order.nap +``` + +### The `.napenv` environment file + +Contains the base URL extracted from the spec and variable placeholders for auth: + +``` +baseUrl = https://petstore3.swagger.io/api/v3 +bearerToken = YOUR_BEARER_TOKEN +``` + +Fill in the auth values in `.napenv.local` (which should be gitignored): + +``` +# .napenv.local +bearerToken = eyJhbGci... +``` + +--- + +## How base URL is extracted + +Napper extracts the base URL from the spec in this order: + +| Spec version | Source | +|-------------|--------| +| OpenAPI 3.x | First entry in `servers[].url` | +| Swagger 2.0 | Constructed from `schemes[0]` + `host` + `basePath` | + +If neither is present, `baseUrl` is left empty in `.napenv` for you to fill in. + +--- + +## How authentication is handled + +Napper reads the spec's `securitySchemes` and adds the appropriate headers to each generated request: + +| Scheme | Generated header | Variable | +|--------|-----------------|----------| +| `http: bearer` | `Authorization = Bearer {{bearerToken}}` | `bearerToken` | +| `http: basic` | `Authorization = Basic {{basicCredentials}}` | `basicCredentials` | +| `apiKey: header` | Header named by the spec (e.g. `X-Api-Key = {{xApiKey}}`) | derived from header name | + +All auth variables are declared in `.napenv` with placeholder values. Replace them in `.napenv.local` to avoid committing secrets. + +--- + +## How request bodies are generated + +For operations with a request body, Napper inspects the schema and generates a JSON example: + +| Schema type | Generated example | +|------------|------------------| +| `string` | `"string"` | +| `integer` | `0` | +| `number` | `0.0` | +| `boolean` | `false` | +| `array` | `[]` | +| `object` | Recursively generated from properties | +| `$ref` | Resolved and inlined | + +`$ref` references (both inline and from `#/components/schemas`) are resolved before generation, so nested types work correctly. + +--- + +## Customising generated files + +The generated files are plain `.nap` files — edit them freely. Common customisations: + +**Replace placeholder values with real test data:** +``` +[vars] +petId = 42 # was: 1 +petName = Fluffy # add new variable +``` + +**Add more specific assertions:** +``` +[assert] +status = 200 +body.id = {{petId}} +body.name = Fluffy # add value check +body.status = available # add field value assertion +duration < 300ms # add timing assertion +``` + +**Remove operations you do not want to test:** + +Delete any `.nap` file and remove the corresponding line from the `.naplist`. + +**Add pre/post scripts:** +``` +[script] +pre = ./scripts/auth.fsx +post = ./scripts/validate-response.fsx +``` + +**Customise the environment:** + +Edit `.napenv` to point at a different environment: +``` +baseUrl = https://staging.petstore.example.com +``` + +--- + +## AI Enhancement (optional) + +![Screenshot: AI-enhanced generation option in VS Code, showing richer assertions and realistic test data generated via GitHub Copilot](openapi-import-ai-enhanced.png) + +When GitHub Copilot is available in VS Code, you can choose **AI-enhanced** generation. This enriches the basic output with: + +- **Semantic assertions** beyond simple existence checks (e.g. `body.email contains @`, `body.status = active`) +- **Realistic test data** in request bodies instead of placeholder values +- **Logical playlist ordering** — authentication requests first, then CRUD operations in dependency order + +If Copilot is not available or the AI step fails, Napper falls back to basic generation automatically and shows a notification. + +--- + +## Running generated tests + +Once generated, run the entire suite: + +```bash +napper run ./tests/petstore.naplist +``` + +Run a single file: +```bash +napper run ./tests/pets/get-pets.nap +``` + +Run with a specific environment: +```bash +napper run ./tests/petstore.naplist --env staging +``` + +Output JUnit XML for CI/CD: +```bash +napper run ./tests/petstore.naplist --output junit > results.xml +``` + +--- + +## Troubleshooting + +**"Failed to parse spec" error** + +- Verify the spec is valid JSON. YAML is not supported yet — convert it first. +- Check that the spec is valid OpenAPI 3.x or Swagger 2.0 using the [Swagger Editor](https://editor.swagger.io/). +- Some specs with complex `$ref` chains may not resolve correctly. Open an issue on [GitHub](https://github.com/MelbourneDeveloper/napper/issues) with the spec attached. + +**URL import fails with a network error** + +- Confirm the URL is publicly accessible. +- If behind a proxy, set `HTTPS_PROXY` in your environment. +- Download the spec locally and use **Import from File** instead. + +**Generated files have empty `baseUrl`** + +- The spec does not declare a `servers` entry (OpenAPI 3.x) or `host`/`basePath` (Swagger 2.0). +- Edit `.napenv` to set `baseUrl` manually. + +**Auth variables are missing** + +- Not all specs declare security schemes at the operation level. If your API requires auth but none was generated, add the header manually to the relevant `.nap` files. + +**Request bodies are empty or wrong** + +- Some specs use `$ref` chains that are deeply nested. If a body was not generated or looks wrong, fill it in manually — the file is plain text. + +**Output directory is not empty** + +- Napper writes files into the output directory without deleting existing content. If you re-run generation, existing files are overwritten. Move any custom files out of the generated directory, or use a subdirectory for generated output. + +--- + +## Next steps + +- [Run your generated tests](/docs/quick-start/) from the CLI or VS Code +- [Customise assertions](/docs/assertions/) to verify more than status codes +- [Set up environments](/docs/environments/) for staging and production +- Add [F# or C# scripts](/docs/fsharp-scripting/) for dynamic auth and complex flows diff --git a/website/src/docs/quick-start.md b/website/src/docs/quick-start.md index f01c0d5..40537f6 100644 --- a/website/src/docs/quick-start.md +++ b/website/src/docs/quick-start.md @@ -10,6 +10,8 @@ eleventyNavigation: # Quick Start +![Screenshot: VS Code with a newly created hello.nap file open, showing the GET request line and the CodeLens Run button above it](quick-start-first-request.png) + Get up and running with Napper in under 5 minutes. ## How do I create my first request? (spec: nap-minimal, nap-request) @@ -94,8 +96,11 @@ napper run ./smoke.naplist --output junit > results.xml Napper exits with code 0 when all assertions pass, 1 when any assertion fails, and 2 on runtime errors. This integrates naturally with any CI platform that fails on non-zero exit codes. +![Screenshot: Napper CLI output after running a .naplist suite, showing green checkmarks for all assertions and a summary pass/fail count](quick-start-suite-results.png) + ## Next steps - Learn the [.nap file format](/docs/nap-files/) in detail - Build test suites with [.naplist files](/docs/naplist-files/) - Set up [environments](/docs/environments/) for different targets +- [Import an OpenAPI spec](/docs/openapi-import/) to generate tests automatically From ceccdb600f9978ee29455539da94b84a3c1b1342 Mon Sep 17 00:00:00 2001 From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:52:10 +1100 Subject: [PATCH 3/4] Fix CI: extract onAllEvents helper to satisfy max-lines-per-function lint, format F# test file --- src/Napper.Core.Tests/OpenApiE2eTests.fs | 4 +++- src/Napper.VsCode/src/watchers.ts | 17 +++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Napper.Core.Tests/OpenApiE2eTests.fs b/src/Napper.Core.Tests/OpenApiE2eTests.fs index f591ad4..5c46e82 100644 --- a/src/Napper.Core.Tests/OpenApiE2eTests.fs +++ b/src/Napper.Core.Tests/OpenApiE2eTests.fs @@ -483,7 +483,9 @@ let ``Beeceptor checkout endpoint asserts 201 status`` () = allNaps |> Array.filter (fun f -> let content = File.ReadAllText(f) - content.Contains("method = POST") && content.Contains("url = {{baseUrl}}/checkout")) + + content.Contains("method = POST") + && content.Contains("url = {{baseUrl}}/checkout")) Assert.True(checkoutFiles.Length >= 1, "Must have checkout endpoint") let content = File.ReadAllText(checkoutFiles[0]) diff --git a/src/Napper.VsCode/src/watchers.ts b/src/Napper.VsCode/src/watchers.ts index 491a79c..9068dcf 100644 --- a/src/Napper.VsCode/src/watchers.ts +++ b/src/Napper.VsCode/src/watchers.ts @@ -18,6 +18,15 @@ import { const isNapperFile = (fileName: string): boolean => fileName.endsWith(NAP_EXTENSION) || fileName.endsWith(NAPLIST_EXTENSION); +const onAllEvents = ( + watcher: vscode.FileSystemWatcher, + handler: () => void, +): void => { + watcher.onDidCreate(handler); + watcher.onDidDelete(handler); + watcher.onDidChange(handler); +}; + export const registerWatchers = ( context: vscode.ExtensionContext, explorer: ExplorerAdapter, @@ -30,12 +39,8 @@ export const registerWatchers = ( log.debug(LOG_MSG_TREE_REFRESH); explorer.refresh(); }; - napWatcher.onDidCreate(refreshExplorer); - napWatcher.onDidDelete(refreshExplorer); - napWatcher.onDidChange(refreshExplorer); - naplistWatcher.onDidCreate(refreshExplorer); - naplistWatcher.onDidDelete(refreshExplorer); - naplistWatcher.onDidChange(refreshExplorer); + onAllEvents(napWatcher, refreshExplorer); + onAllEvents(naplistWatcher, refreshExplorer); dirWatcher.onDidCreate(refreshExplorer); dirWatcher.onDidDelete(refreshExplorer); context.subscriptions.push(napWatcher, naplistWatcher, dirWatcher); From 0ae5acb129373fc898fe9b5788d72c44bf13ed2b Mon Sep 17 00:00:00 2001 From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:55:58 +1100 Subject: [PATCH 4/4] Fix CI: apply prettier formatting to watchers.ts --- src/Napper.VsCode/src/watchers.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Napper.VsCode/src/watchers.ts b/src/Napper.VsCode/src/watchers.ts index 9068dcf..ed84ca0 100644 --- a/src/Napper.VsCode/src/watchers.ts +++ b/src/Napper.VsCode/src/watchers.ts @@ -18,10 +18,7 @@ import { const isNapperFile = (fileName: string): boolean => fileName.endsWith(NAP_EXTENSION) || fileName.endsWith(NAPLIST_EXTENSION); -const onAllEvents = ( - watcher: vscode.FileSystemWatcher, - handler: () => void, -): void => { +const onAllEvents = (watcher: vscode.FileSystemWatcher, handler: () => void): void => { watcher.onDidCreate(handler); watcher.onDidDelete(handler); watcher.onDidChange(handler);