Skip to content

feat: implement msw plugin#3570

Open
malcolm-kee wants to merge 22 commits intohey-api:mainfrom
malcolm-kee:feat/msw-plugin
Open

feat: implement msw plugin#3570
malcolm-kee wants to merge 22 commits intohey-api:mainfrom
malcolm-kee:feat/msw-plugin

Conversation

@malcolm-kee
Copy link
Copy Markdown
Contributor

@malcolm-kee malcolm-kee commented Mar 13, 2026

Closes #1486

Summary

Implement msw plugin that generates a msw.gen.ts file with type-safe mock handler factories from OpenAPI specs. Each operation is exported as a named handler creator (<operationId>Mock) with a wildcard base URL, plus a getAllMocks helper to generate handlers for all operations at once. A createMswHandlerFactory function is also exported for custom base URL binding.

Important

Even though many expect fake data generation is part of this plugin, that probably overlaps with faker plugin. The only mock data handled by this plugin at the moment is the example defined in the OpenAPI spec.

API Design

Configuration

export default {
  plugins: [
    {
      name: "msw",
      valueSources: ["example"], // set to [] to disable example embedding
    },
  ],
};

Usage

Individual handler exports (wildcard base URL)

import { HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { getPetByIdMock, updatePetMock, getInventoryMock, getAllMocks } from "./client/msw.gen";

const server = setupServer(
  // Static response — type-checked result, status defaults to dominant success code
  getPetByIdMock({ result: { id: 1, name: "Fido", photoUrls: [] } }),

  // Explicit status code
  getPetByIdMock({ status: 200, result: { id: 1, name: "Fido", photoUrls: [] } }),

  // Custom resolver — params and request body are typed
  updatePetMock(async ({ request, params }) => {
    const body = await request.json();
    return HttpResponse.json({ id: Number(params.petId), ...body }, { status: 200 });
  }),

  // Operations with spec examples — no args needed
  getInventoryMock(),
);

Handler options

MSW handler options can be passed as a second argument:

getPetByIdMock({ result: { id: 1, name: "Fido" } }, { once: true });

Custom base URL (createMswHandlerFactory)

import { createMswHandlerFactory } from "./client/msw.gen";

// Explicit base URL
const createMock = createMswHandlerFactory({
  baseUrl: "http://localhost:3000",
});

// No args — infers base URL from spec's servers field
const createMock2 = createMswHandlerFactory();

const server = setupServer(
  createMock.getPetByIdMock({ result: { id: 1, name: "Fido", photoUrls: [] } }),
);

All handlers (getAllMocks)

import { getAllMocks } from "./client/msw.gen";

// Quick setup — all operations with defaults
setupServer(...getAllMocks());

// Strict mode — missing mocks return 501
setupServer(...getAllMocks({ onMissingMock: "error" }));

// With overrides — keys are handler names (<operationId>Mock)
setupServer(
  ...getAllMocks({
    onMissingMock: "skip",
    overrides: {
      getPetByIdMock: {
        result: { id: 1, name: "Fido", photoUrls: [] },
      },
    },
  }),
);

Design decisions

Why <operationId>Mock naming? — Appending Mock avoids naming collisions with other generated artifacts (types, SDK functions) while keeping the handler clearly associated with its operation.

Why both individual exports and createMswHandlerFactory? — Individual exports use a wildcard (*) base URL for zero-config convenience. The factory function allows binding to a specific base URL when needed (e.g. integration tests against a specific server).

Why valueSources instead of example: boolean? — Extensible for future sources (e.g. ['example', 'faker'] when faker plugin is ready).

onMissingMock — Operations that require a response argument (no default example) are either skipped ('skip') or return a 501 ('error'). Overrides always take precedence.

Handler creator signatures

Operation has Parameter type Optional?
Response type with status codes { result, status? } | ToResponseUnion<Responses> | HttpResponseResolver<PathParams, Body> No*
Response type, void { result, status? } | ToResponseUnion<Responses> | HttpResponseResolver<PathParams, Body> Yes
No response type (no status code) HttpResponseResolver<PathParams, Body> Yes

* Optional if the spec defines an example for the dominant response.

Response method selection

Content type Method
application/json HttpResponse.json()
text/* HttpResponse.text()
binary/octet-stream new HttpResponse()
void / no content new HttpResponse(null)

When multiple 2xx responses exist, the dominant one is chosen by priority: json > text > binary > void.

Known limitations

  • Response type generic is omitted from HttpResponseResolver to avoid MSW's DefaultBodyType constraint issues with union/void response types
  • Query parameters are not typed in resolvers (MSW doesn't support typed query params natively)
  • Only 2xx responses are considered for the dominant response

@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pullfrog
Copy link
Copy Markdown

pullfrog bot commented Mar 13, 2026

Error

agent completed without reporting progress

Pullfrog  | Rerun failed job ➔View workflow run | Triggered by Pullfrogpullfrog.com𝕏

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 13, 2026

@malcolm-kee is attempting to deploy a commit to the Hey API Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 13, 2026

🦋 Changeset detected

Latest commit: cfda6a3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hey-api/openapi-ts Patch
@hey-api/shared Patch
@hey-api/openapi-python Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. feature 🚀 Feature request. labels Mar 13, 2026
@malcolm-kee malcolm-kee mentioned this pull request Mar 13, 2026
4 tasks
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 18.20128% with 382 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.79%. Comparing base (8d3167c) to head (cfda6a3).

Files with missing lines Patch % Lines
examples/openapi-ts-fetch/src/client/msw.gen.ts 29.83% 112 Missing and 55 partials ⚠️
...kages/openapi-ts/src/plugins/msw/shared/handler.ts 1.78% 47 Missing and 8 partials ⚠️
.../src/plugins/msw/shared/computeDominantResponse.ts 8.92% 33 Missing and 18 partials ⚠️
packages/openapi-ts/src/plugins/msw/v2/plugin.ts 2.00% 48 Missing and 1 partial ⚠️
packages/openapi-ts/src/plugins/msw/shared/sort.ts 0.00% 14 Missing and 3 partials ⚠️
...ages/openapi-ts/src/plugins/msw/shared/response.ts 0.00% 13 Missing and 1 partial ⚠️
packages/shared/src/utils/url.ts 0.00% 5 Missing and 9 partials ⚠️
...ackages/openapi-ts/src/plugins/msw/shared/types.ts 0.00% 7 Missing ⚠️
packages/openapi-ts/src/plugins/msw/shared/path.ts 0.00% 4 Missing and 1 partial ⚠️
...ges/openapi-ts/src/plugins/msw/shared/operation.ts 0.00% 2 Missing ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3570      +/-   ##
==========================================
- Coverage   39.39%   38.79%   -0.60%     
==========================================
  Files         520      542      +22     
  Lines       19279    20399    +1120     
  Branches     5714     6122     +408     
==========================================
+ Hits         7595     7914     +319     
- Misses       9445    10046     +601     
- Partials     2239     2439     +200     
Flag Coverage Δ
unittests 38.79% <18.20%> (-0.60%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Mar 13, 2026

@malcolm-kee Before I go into it, two questions:

  1. How much AI was used to create this pull request?
  2. How much are you willing to improve it? i.e. Is this the final version?

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

@mrlubos I come up with the API design and AI was doing most of the implementations while I watch.

Not final. I'm happy to iterate on this, just want some progress on this plugin.

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

The diff is big is mostly because of the tests and snapshots.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 13, 2026

Open in StackBlitz

@hey-api/codegen-core

npm i https://pkg.pr.new/@hey-api/codegen-core@3570

@hey-api/json-schema-ref-parser

npm i https://pkg.pr.new/@hey-api/json-schema-ref-parser@3570

@hey-api/nuxt

npm i https://pkg.pr.new/@hey-api/nuxt@3570

@hey-api/openapi-ts

npm i https://pkg.pr.new/@hey-api/openapi-ts@3570

@hey-api/shared

npm i https://pkg.pr.new/@hey-api/shared@3570

@hey-api/spec-types

npm i https://pkg.pr.new/@hey-api/spec-types@3570

@hey-api/types

npm i https://pkg.pr.new/@hey-api/types@3570

@hey-api/vite-plugin

npm i https://pkg.pr.new/@hey-api/vite-plugin@3570

commit: cfefe18

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

I did some refactoring/enhancements:

  • remove duplications of type definition and function param definition. It's only defined at type level and the implementation param is auto-inferred from that.
  • extract example from schema as default value.

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

malcolm-kee commented Mar 14, 2026

@mrlubos I manually validated the code and made some refactoring. It would be great if you can provide some feedbacks, especially on the API.

@malcolm-kee malcolm-kee force-pushed the feat/msw-plugin branch 2 times, most recently from ac3c300 to b34b5bd Compare March 15, 2026 04:36
@malcolm-kee
Copy link
Copy Markdown
Contributor Author

malcolm-kee commented Mar 15, 2026

More revision:

  • Redesign the API - the static parameter becomes { status: number; result: ResultType } instead of just ResultType. This design allows typescript to infer the types better with a stable object type with explicit properties instead of a more generic ResultType. This also make it easier to overwrite the status code without falling back to use the custom resolver approach.
  • Forward the handler options to msw, so no lost of capabilities.
  • Added resolveToNull helper function to remove duplicate fallbacks

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

Added examples options to the plugin.

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

Change plugin options from examples: boolean to valueSources: Array<'example'>, so that when fakerjs is ready, we just need to switch the default value to valueSources: ['example', 'faker'].

@malcolm-kee malcolm-kee force-pushed the feat/msw-plugin branch 2 times, most recently from e3e18f0 to 7bb2ae4 Compare March 16, 2026 09:20
@malcolm-kee
Copy link
Copy Markdown
Contributor Author

malcolm-kee commented Mar 16, 2026

Ideas on how to continue enhancing this PR, in case anyone want to take over this, since I might not be free to iterate on this:

Implement ofAll

We can actually implement ofAll helper without waiting for faker plugin, by providing options to customize its behavior:

const createMock = createMswHandlerFactory();

const allMockHandlers = createMock.ofAll({
  onMissingMock: 'skip', // 'skip' | 'error',
  overrides: {
    getPetById({
      status: 200,
      result: { id: 1, name: "Fido", photoUrls: [] },
    })
  }
});

const server = setupServer(...allMockHandlers);

onMissingMock option:

  • skip: we will not include the MSW handlers that requires argument in the returned array of requestHandlers. This is possible because we can infer if argument is required for a handler (I differentiate them by providing them different types - HttpHandlerFactory are those require argument while OptionalHttpHandlerFactory are those does not require argument)
  • error: we will include the MSW handlers for all, but for those require argument, we will return HttpResponse('[heyapi-msw] The mock of this request is not implemented.', { status: 501 })

overrides option API is similar to the API of single handler, but instead of calling individual helper, user can define all of them at once here.

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Mar 26, 2026

@malcolm-kee this is a really strong start! I found some errors with the OpenCode spec so I'm going to clean it up either way, here are the changes I've made so far:

  1. Fallback base URL to *. It's easier to get something going that way, otherwise I was stuck on the very first step and had to figure out why it's not matching mocks.
  2. Removed the _default mocks. It's easy to import and call createMswHandlerFactory() now to get the same result. I was bitten way too many times by the default HTTP clients we generate, not going to repeat the same mistake.
  3. Added baseUrl allowing people to define their own default value or pick one from OpenAPI servers.
  4. Smaller cleanup to the DSL usage.

I'll continue working on this, the main thing right now is the readability and organization of the generated code

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

malcolm-kee commented Mar 26, 2026

@mrlubos I'm thinking of adding SSE support if #3657 looks good. But probably after this PR is merged.

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Mar 27, 2026

@malcolm-kee New changes:

  1. Removed the onMissingMock feature. Let me know if this is essential to you, I wanted to reduce the complexity for now. The default MSW warning works imo, but we might want to bring this back later since MSW Source also has something similar.
  2. Moved single handler factories behind .one property to guarantee uniqueness. If we want to support more customization later, the original mocks spread could clash with getAllMocks. Speaking of...
  3. Renamed .getAllMocks() to .all().
  4. Major clean up of the output. The new structure exports every handler factory in addition to the umbrella wrapper. This unlocks a lot of goodies such as conditionally disabling the wrapper generation (if desired) or the ability to move individual exports to different files.

The last part is improving the actual handlers and fixing some bugs I found.

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

malcolm-kee commented Mar 27, 2026

Removed the onMissingMock feature.

I'm concerned that it introduces invalid mock. In my experience, invalid mocks are a nightmare to debug because the resulting errors usually pop up in the UI logic, far away from the network layer.

We should probably force overrides to be required or ensure those handlers are skipped. The former approach makes the all helpers harder to use, while the latter is misleading as it's not all handlers - neither is good DX IMO.

Moved single handler factories behind .one property

I think it could be confusing, given that MSW handler has once option. What about .mock?

Major clean up of the output

Could we add explicit return type for createMswHandlers? Else the output will break projects with isolatedDeclarations mode.


Other than that, LGTM!

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Mar 27, 2026

@malcolm-kee added explicit types back.

RE .one, this is the current result from the factory. It's closer to an ORM interface so I'd be surprised if people confuse it with .once – what would the assumption be? I can also add JSDoc comments to this interface later, I'm just being lazy right now.

Screenshot 2026-03-27 at 11 52 20 pm

RE onMissingMock, can you explain your concern? I assume you're saying that the mock could return data that doesn't match the expected shape which would explode your UI. That's a valid concern, but with your implementation I was using all mocks and MSW was not detecting them which felt weird too. It wasn't intuitive that I need to manually provide a response. Perhaps a middle ground would be to avoid handling operations if we can't guarantee the result is accurate. That way we're not forced to manually provide every response and .all() could be improved with a comment explaining how it works.

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

I want to make sure I understand your concern correctly — could you clarify a bit?

It wasn't intuitive that I need to manually provide a response

Were you using onMissingMock: 'skip' or 'error'? If the issue is that .all() silently skips mocking some requests, 'error' would surface which endpoints aren't being mocked. On the other hand, 'skip' allows relying on MSW's default unhandled request behavior to catch those.

I agree it feels odd that .all() doesn't actually mock all endpoints. The end goal is to provide sensible defaults for every endpoint (via the faker plugin when it's ready), so for now, skipping is the safer default to avoid returning invalid data.

If it's too confusing, we can ship this plugin without all() for now and add it once faker plugin is ready.

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

One more thing, not sure if it's intentional. I realize that in the latest implementation, all the first parameter becomes optional. It was made required previously as a signal to user that they must provide the mock response.

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Mar 28, 2026

@malcolm-kee good eye – that one is intentional. If we're skipping or throwing an error on missing mocks, there's no need to provide any arguments. More importantly, this is the base experience I want people to have:

const server = setupServer(
  ...createMswHandlers().all(),
);
server.listen();

or with single handler:

const server = setupServer(
  createMswHandlers().one.tuiPublish(),
);
server.listen();

As with the rest of the ecosystem, it should just work™. In the end, the behavior I'm steering towards with this v0 is mock what we can (based on the examples field), and skip everything else. The above examples feel good to me, just need to bring back the onMissingMock behavior from your last comment.

After playing with it more, I agree that fake data is very high on the priority list. A lot of specs simply don't have example fields, which would make the default experience basically do nothing haha. Did you have an interest in working on that feature?

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

malcolm-kee commented Mar 29, 2026

I agree with the principle of it should just work, but I want to push back a little, as I disagree that making the parameter optional is achieving that.

There are two scenarios when someone add a msw handlers:

  • scenario 1: a flow that fetch some data from backend and use it for subsequent processing, e.g. display in UI
  • scenario 2: a flow that make some API calls to backend, then doesn't care about its response as long it is returning successful status code

By making the parameter optional in all cases, we're making the life for scenario 2 easier but at the cost of scenario 1. Now that instead of letting the type guide them on which mock handlers can be used safely, they have to wait until the runtime error in the test to let them know if a manual mock data is required.

A middle ground that we can attempt is to apply different logic based on the method - get method should force the parameter as required when fallback is not available, and other methods can be always optional. I didn't do that because I tried to avoid making the logic even more complicated.

Last thing I want to point out is that this is a decision we had to make even with the faker plugin, because user might disable default value from faker with valueSources: [].

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

Yeah I'm interested in working on the faker plugin, didn't do anything yet cause I assumed you've started something?

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

@mrlubos is the plan to wait for faker plugin before merging this?

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Apr 2, 2026

@malcolm-kee Not necessarily, but it requires several more days to prepare the response modeling part, and I need to work on other things too so I will pick this up again later

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Apr 2, 2026

@malcolm-kee fyi, this is how far I am with it, and 1 of those files are docs

Screenshot 2026-04-02 at 8 43 03 pm

@malcolm-kee
Copy link
Copy Markdown
Contributor Author

WIP for faker plugin:

@pullfrog
Copy link
Copy Markdown

pullfrog bot commented Apr 4, 2026

TL;DR — Adds a new msw code-generation plugin that produces type-safe MSW v2 handler factories from OpenAPI specs. Each operation gets a handler function accepting either a static typed response or a custom HttpResponseResolver, and a createMswHandlers factory provides per-handler access (.pick) and bulk setup (.all()). As a side-effect, base URL resolution logic is extracted from the client-core plugin into a shared getBaseUrl() utility.

Key changes

  • New msw plugin — Complete code-generation plugin that iterates over all IR operations and emits a msw.gen.ts file with per-operation handler functions, a MswHandlerFactories type, and a createMswHandlers() factory.
  • Handler generation with dominant response analysis — Each handler picks the "dominant" 2xx response (json > text > binary > void), determines the HttpResponse construction method, and optionally embeds OpenAPI example values as default body fallbacks.
  • createMswHandlers factory pattern — Generated factory returns { pick, all } where pick provides individually wrapped handlers and all(options?) returns a sorted array of all handlers with optional per-operation overrides and an onMissingMock behavior.
  • Route specificity sortingall() orders handlers by depth-first, static-before-dynamic specificity to prevent parameterized routes from shadowing more specific static routes.
  • Path sanitization for MSW — Converts OpenAPI {param} templates to MSW :param syntax, with camelCase normalization for non-word characters and literal colon escaping.
  • Extract getBaseUrl() to @hey-api/shared — Base URL resolution logic (string/number/boolean config to resolved URL) is extracted from client-core into a shared utility, fixing a minor issue where OpenAPI 2.0 host-only server URLs were silently dropped.
  • Plugin configuration — Supports baseUrl (default '*'), responseFallback ('error' | 'passthrough'), valueSources (controls example embedding), plus standard plugin options.
  • Comprehensive tests and documentation — Snapshot tests (5 scenarios), runtime integration tests, type-level tests, two new OpenAPI test specs, and full documentation replacing the previous placeholder page.

Summary | 64 files | 22 commits | base: mainfeat/msw-plugin


MSW handler generation from OpenAPI operations

Before: No MSW integration existed; the docs page was a placeholder linking to issue #1486.
After: Adding 'msw' to the plugins array generates a msw.gen.ts file with fully typed handler factories for every operation.

Each operation produces a function like handleGetPetById(response?, options?) that returns an HttpHandler. The response parameter accepts either a static { body: T, status?: N } object or an HttpResponseResolver<Params, Body> function. When no response is provided, the handler falls back to a 501 error response or passthrough depending on configuration. When valueSources includes 'example' (the default), OpenAPI example values are embedded as fallback defaults on the body accessor (via response?.body ?? defaultValue) rather than as parameter defaults.

How does dominant response selection work?

The plugin examines all 2xx responses for an operation and selects the "dominant" one based on content type priority: json > text > binary > void. This determines: the TypeScript type for the body parameter, the default status code, and the HttpResponse construction method (.json(), .text(), new HttpResponse(), or Response with no body). The body existence check (body !== undefined) gates whether a response is constructed at all — when examples are present, the fallback ensures a response is always returned without needing the 501/passthrough logic.

shared/handler.ts · shared/computeDominantResponse.ts · shared/response.ts


createMswHandlers factory and route sorting

Before: N/A — no handler aggregation existed.
After: A generated createMswHandlers(config?) function returns { pick, all } for per-handler or bulk handler setup, with handlers sorted by route specificity.

The pick property exposes individually wrapped handler functions bound to shared config (e.g. baseUrl). The all(options?) method returns a ReadonlyArray<HttpHandler> sorted by specificity — deeper routes first, static segments before dynamic ones — preventing MSW from incorrectly matching parameterized routes before more specific static routes. Per-operation overrides are passed via all({ pick: { operationId: response } }).

Option Type Default Purpose
baseUrl string | number | boolean '*' Base URL for handler path matching
responseFallback 'error' | 'passthrough' 'error' Behavior when no response is provided
valueSources string[] ['example'] Controls OpenAPI example embedding

v2/plugin.ts · shared/sort.ts · types.ts · config.ts


Extract getBaseUrl() to shared package

Before: resolveBaseUrlString was a private function inside the client-core plugin. OpenAPI 2.0 specs with host-only server URLs (e.g. api.postmarkapp.com/) did not emit a baseUrl in generated client config.
After: Base URL resolution is a public getBaseUrl() utility in @hey-api/shared, consumed by both client-core and the new MSW plugin. Host-only URLs are now correctly emitted.

url.ts · client-core/client.ts


Tests, specs, and documentation

Before: No MSW test infrastructure; docs page was a feature-status placeholder.
After: Full test coverage across 5 snapshot scenarios, runtime integration tests, type-level tests, two new OpenAPI specs, and complete plugin documentation.

Snapshot tests in packages/openapi-ts-tests/msw/v2/ cover default, response-example, response-example-disabled, response-types, and servers scenarios. The openapi-ts-fetch example adds runtime tests verifying actual MSW behavior (static responses, custom resolvers, path params, status codes, void operations, handler overrides) and type tests validating type safety with expectTypeOf.

msw.test.ts · msw-types.test-d.ts · msw.md

Pullfrog  | View workflow run | Triggered by Pullfrog𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 🚀 Feature request. size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MSW plugin

3 participants