@hellaweb3/wiremock-testcontainers-nodejs 0.2.1 → 0.4.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.
package/dist/index.cjs CHANGED
@@ -1,194 +1 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- let node_fs = require("node:fs");
3
- let node_path = require("node:path");
4
- let testcontainers = require("testcontainers");
5
- let node_module = require("node:module");
6
- let node_url = require("node:url");
7
- // src/sources.ts
8
- const PACKAGE_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u;
9
- function fail(message) {
10
- throw new Error(message);
11
- }
12
- function within(context, fn) {
13
- try {
14
- return fn();
15
- } catch (error) {
16
- throw new Error(`${context}: ${error.message}`, { cause: error });
17
- }
18
- }
19
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20
- const show = (value) => value instanceof URL ? value.href : JSON.stringify(value) ?? String(value);
21
- const text = (value, field) => typeof value === "string" && value.trim() !== "" ? value : fail(`"${field}" must be a non-empty string, got ${show(value)}`);
22
- const toPath = (value, field) => {
23
- if (!(value instanceof URL) && !text(value, field).startsWith("file:")) return (0, node_path.resolve)(value);
24
- try {
25
- return (0, node_url.fileURLToPath)(value);
26
- } catch {
27
- return fail(`"${field}" must be a path or a file: URL, got ${show(value)}`);
28
- }
29
- };
30
- const existingFile = (path) => {
31
- const stats = (0, node_fs.statSync)(path, { throwIfNoEntry: false });
32
- return stats?.isFile() ? path : fail(`"${path}" ${stats ? "is not a file" : "does not exist"}`);
33
- };
34
- const packageRoot = (name) => {
35
- if (!PACKAGE_NAME.test(name)) fail(`"${name}" is not a valid npm package name`);
36
- return ((0, node_module.createRequire)((0, node_path.join)(process.cwd(), "noop.js")).resolve.paths(name) ?? []).map((dir) => (0, node_path.join)(dir, name)).find((dir) => (0, node_fs.existsSync)((0, node_path.join)(dir, "package.json"))) ?? fail(`package "${name}" is not installed (searched from "${process.cwd()}")`);
37
- };
38
- function resolveFile(source) {
39
- if (typeof source === "string" || source instanceof URL) return existingFile(toPath(source, "source"));
40
- if (!isRecord(source)) return fail(`unsupported source ${show(source)}; expected a path, a file: URL, { file }, { package, file } or { module, file }`);
41
- const { file, package: pkg, module } = source;
42
- if (pkg !== void 0 && module !== void 0) fail("a source may set \"package\" or \"module\", not both");
43
- if (pkg !== void 0) {
44
- const root = packageRoot(text(pkg, "package"));
45
- const path = (0, node_path.resolve)(root, text(file, "file"));
46
- return (0, node_path.relative)(root, path).split(node_path.sep)[0] === ".." ? fail(`"${String(file)}" must point to a file inside package "${pkg}"`) : existingFile(path);
47
- }
48
- if (module === void 0) return existingFile(toPath(file, "file"));
49
- const anchor = toPath(module, "module");
50
- return existingFile((0, node_path.resolve)((0, node_fs.statSync)(anchor, { throwIfNoEntry: false })?.isDirectory() ? anchor : (0, node_path.dirname)(anchor), text(file, "file")));
51
- }
52
- const containerName = (name) => {
53
- const value = text(name, "name");
54
- return value.split("/").some((part) => [
55
- "",
56
- ".",
57
- ".."
58
- ].includes(part) || /[\\\0]/u.test(part)) ? fail(`name "${value}" must be a relative path using "/" separators, without empty, "." or ".." segments`) : value;
59
- };
60
- const inlineJson = (json) => typeof json === "string" ? json : JSON.stringify(json, null, 2) ?? fail("\"json\" must be a JSON string or an object");
61
- const parseJson = (json) => {
62
- try {
63
- return JSON.parse(json);
64
- } catch (error) {
65
- return fail(`invalid JSON (${error.message})`);
66
- }
67
- };
68
- function stubIds(json) {
69
- const value = parseJson(json);
70
- const many = isRecord(value) && "mappings" in value;
71
- const stubs = many ? value.mappings : [value];
72
- if (!Array.isArray(stubs) || stubs.length === 0) fail("\"mappings\" must be a non-empty array of stub mappings");
73
- return stubs.flatMap((stub, index) => {
74
- if (!isRecord(stub) || !isRecord(stub.request) || !isRecord(stub.response)) fail(`${many ? `mappings[${index}]` : "the mapping"} is not a WireMock stub mapping ("request" and "response" objects are required)`);
75
- const id = stub.id ?? stub.uuid;
76
- return typeof id === "string" ? [id] : [];
77
- });
78
- }
79
- const stubIdsOf = (path) => {
80
- try {
81
- return path.endsWith(".json") ? stubIds((0, node_fs.readFileSync)(path, "utf8")) : [];
82
- } catch {
83
- return [];
84
- }
85
- };
86
- const listFiles = (dir) => (0, node_fs.readdirSync)(dir, {
87
- recursive: true,
88
- withFileTypes: true
89
- }).filter((entry) => !entry.isDirectory()).map((entry) => (0, node_path.relative)(dir, (0, node_path.join)(entry.parentPath, entry.name)).split(node_path.sep).join("/"));
90
- // src/index.ts
91
- const MAPPINGS = "/home/wiremock/mappings";
92
- const FILES = "/home/wiremock/__files";
93
- const INLINE = "inline JSON";
94
- var WiremockContainer = class extends testcontainers.GenericContainer {
95
- #origins = /* @__PURE__ */ new Map();
96
- #stubIds = /* @__PURE__ */ new Map();
97
- constructor(image = "wiremock/wiremock") {
98
- super(image);
99
- this.withExposedPorts(8080);
100
- this.withWaitStrategy(testcontainers.Wait.forListeningPorts());
101
- }
102
- withMappings(directory) {
103
- within("WiremockContainer.withMappings", () => {
104
- const mappings = (0, node_path.join)(directory, "mappings");
105
- if (!(0, node_fs.existsSync)(mappings)) fail(`"${mappings}" does not exist`);
106
- if (!(0, node_fs.statSync)(mappings).isDirectory()) fail(`"${mappings}" is not a directory`);
107
- const files = (0, node_path.join)(directory, "__files");
108
- const dirs = [{
109
- source: mappings,
110
- target: MAPPINGS
111
- }, ...(0, node_fs.existsSync)(files) ? [{
112
- source: files,
113
- target: FILES
114
- }] : []];
115
- this.#claim(dirs.flatMap(({ source, target }) => listFiles(source).map((name) => ({
116
- target: node_path.posix.join(target, name),
117
- origin: (0, node_path.resolve)(source, name),
118
- ids: target === MAPPINGS ? stubIdsOf((0, node_path.join)(source, name)) : []
119
- }))));
120
- this.withCopyDirectoriesToContainer(dirs);
121
- });
122
- return this;
123
- }
124
- withMapping(name, source) {
125
- within(`WiremockContainer.withMapping("${name}")`, () => {
126
- const file = containerName(name);
127
- const target = node_path.posix.join(MAPPINGS, file.endsWith(".json") ? file : `${file}.json`);
128
- const inline = isRecord(source) && "json" in source;
129
- const origin = inline ? INLINE : resolveFile(source);
130
- const content = inline ? inlineJson(source.json) : (0, node_fs.readFileSync)(origin);
131
- const ids = within(origin, () => stubIds(content.toString()));
132
- this.withCopyContentToContainer(this.#claim([{
133
- target,
134
- origin,
135
- ids
136
- }]).map(() => ({
137
- content,
138
- target
139
- })));
140
- });
141
- return this;
142
- }
143
- withFiles(...files) {
144
- within("WiremockContainer.withFiles", () => {
145
- const claims = files.map((file) => {
146
- const origin = resolveFile(file);
147
- const name = isRecord(file) && file.name !== void 0 ? containerName(file.name) : (0, node_path.basename)(origin);
148
- return {
149
- target: node_path.posix.join(FILES, name),
150
- origin
151
- };
152
- });
153
- this.withCopyFilesToContainer(this.#claim(claims).map(({ origin, target }) => ({
154
- source: origin,
155
- target
156
- })));
157
- });
158
- return this;
159
- }
160
- async start() {
161
- const startedContainer = await super.start();
162
- return new StartedWiremockContainer(startedContainer, `http://${startedContainer.getHost()}:${startedContainer.getMappedPort(8080)}`);
163
- }
164
- #claim(claims) {
165
- const origins = new Map(this.#origins);
166
- const owners = new Map(this.#stubIds);
167
- const fresh = claims.filter(({ target, origin, ids = [] }) => {
168
- const existing = origins.get(target);
169
- if (existing === origin && origin !== INLINE) return false;
170
- if (existing) fail(`"${target}" is already registered from ${existing}; registering ${origin} would overwrite it`);
171
- for (const id of ids) {
172
- const owner = owners.get(id);
173
- if (owner) fail(`stub id "${id}" in ${origin} is already used by "${owner}"; WireMock refuses to start with duplicate stub ids`);
174
- owners.set(id, target);
175
- }
176
- origins.set(target, origin);
177
- return true;
178
- });
179
- [this.#origins, this.#stubIds] = [origins, owners];
180
- return fresh;
181
- }
182
- };
183
- var StartedWiremockContainer = class extends testcontainers.AbstractStartedContainer {
184
- #rpcUrl;
185
- constructor(startedTestContainer, url) {
186
- super(startedTestContainer);
187
- this.#rpcUrl = url;
188
- }
189
- get rpcUrl() {
190
- return this.#rpcUrl;
191
- }
192
- };
193
- exports.StartedWiremockContainer = StartedWiremockContainer;
194
- exports.WiremockContainer = WiremockContainer;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("node:fs"),t=require("node:path"),n=require("testcontainers"),r=require("node:zlib"),i=require("node:module"),a=require("node:url"),o=require("node:child_process"),s=require("node:crypto"),c=require("node:events"),l=require("node:net"),u=require("node:os"),d=require("node:stream"),f=require("node:stream/consumers");const p=Buffer.from(`PK`,`latin1`);function jarEntries(e){let t=e.length<22?-1:e.lastIndexOf(p,e.length-22);if(t<0||t<e.length-22-65535)throw Error(`not a ZIP archive`);let n=e.readUInt16LE(t+10),r=e.readUInt32LE(t+16),i=t-20;if(i>=0&&e.readUInt32LE(i)===117853008){let t=Number(e.readBigUInt64LE(i+8));n=Number(e.readBigUInt64LE(t+32)),r=Number(e.readBigUInt64LE(t+48))}let a=[];for(let t=0;t<n;t++){if(r+46>e.length||e.readUInt32LE(r)!==33639248)throw Error(`corrupt ZIP central directory`);let t=e.readUInt16LE(r+28),n=e.toString(`utf8`,r+46,r+46+t);a.push({name:n,method:e.readUInt16LE(r+10),size:e.readUInt32LE(r+20),offset:e.readUInt32LE(r+42)}),r+=46+t+e.readUInt16LE(r+30)+e.readUInt16LE(r+32)}return a}function jarEntry(e,t){try{let n=jarEntries(e).find(e=>e.name===t);if(!n||e.readUInt32LE(n.offset)!==67324752)return;let i=n.offset+30+e.readUInt16LE(n.offset+26)+e.readUInt16LE(n.offset+28),a=e.subarray(i,i+n.size);return n.method===0?a:n.method===8?(0,r.inflateRawSync)(a):void 0}catch{return}}function mainClass(e){let t=jarEntry(e,`META-INF/MANIFEST.MF`)?.toString(`utf8`);return t&&/^Main-Class:\s*(\S+)\s*$/mu.exec(t)?.[1]}const m=`/var/wiremock/extensions`,h=Symbol.for(`@hellaweb3/wiremock-testcontainers-nodejs/runtime`),g=globalThis,getRuntime=()=>g[h],setRuntime=e=>{e?g[h]=e:delete g[h]},_=/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u;function fail(e){throw Error(e)}function within(e,t){try{return t()}catch(t){throw Error(`${e}: ${t.message}`,{cause:t})}}const isRecord=e=>typeof e==`object`&&!!e&&!Array.isArray(e),show=e=>e instanceof URL?e.href:JSON.stringify(e)??String(e),text=(e,t)=>typeof e==`string`&&e.trim()!==``?e:fail(`"${t}" must be a non-empty string, got ${show(e)}`),toPath=(e,n)=>{if(!(e instanceof URL)&&!text(e,n).startsWith(`file:`))return(0,t.resolve)(e);try{return(0,a.fileURLToPath)(e)}catch{return fail(`"${n}" must be a path or a file: URL, got ${show(e)}`)}},existingFile=t=>{let n=(0,e.statSync)(t,{throwIfNoEntry:!1});return n?.isFile()?t:fail(`"${t}" ${n?`is not a file`:`does not exist`}`)},packageRoot=n=>(_.test(n)||fail(`"${n}" is not a valid npm package name`),((0,i.createRequire)((0,t.join)(process.cwd(),`noop.js`)).resolve.paths(n)??[]).map(e=>(0,t.join)(e,n)).find(n=>(0,e.existsSync)((0,t.join)(n,`package.json`)))??fail(`package "${n}" is not installed (searched from "${process.cwd()}")`));function resolvePath$1(n){if(typeof n==`string`||n instanceof URL)return toPath(n,`source`);if(!isRecord(n))return fail(`unsupported source ${show(n)}; expected a path, a file: URL, { file }, { package, file } or { module, file }`);let{file:r,package:i,module:a}=n;if(i!==void 0&&a!==void 0&&fail(`a source may set "package" or "module", not both`),i!==void 0){let e=packageRoot(text(i,`package`)),n=(0,t.resolve)(e,text(r,`file`));return(0,t.relative)(e,n).split(t.sep)[0]===`..`?fail(`"${String(r)}" must point to a file inside package "${i}"`):n}if(a===void 0)return toPath(r,`file`);let o=toPath(a,`module`);return(0,t.resolve)((0,e.statSync)(o,{throwIfNoEntry:!1})?.isDirectory()?o:(0,t.dirname)(o),text(r,`file`))}const resolveFile=e=>existingFile(resolvePath$1(e)),containerName=e=>{let t=text(e,`name`);return t.split(`/`).some(e=>[``,`.`,`..`].includes(e)||/[\\\0]/u.test(e))?fail(`name "${t}" must be a relative path using "/" separators, without empty, "." or ".." segments`):t},inlineJson=e=>typeof e==`string`?e:JSON.stringify(e,null,2)??fail(`"json" must be a JSON string or an object`),parseJson=e=>{try{return JSON.parse(e)}catch(e){return fail(`invalid JSON (${e.message})`)}};function stubIds(e){let t=parseJson(e),n=isRecord(t)&&`mappings`in t,r=n?t.mappings:[t];return Array.isArray(r)&&r.length!==0||fail(`"mappings" must be a non-empty array of stub mappings`),r.flatMap((e,t)=>{isRecord(e)&&isRecord(e.request)&&isRecord(e.response)||fail(`${n?`mappings[${t}]`:`the mapping`} is not a WireMock stub mapping ("request" and "response" objects are required)`);let r=e.id??e.uuid;return typeof r==`string`?[r]:[]})}const stubIdsOf=t=>{try{return t.endsWith(`.json`)?stubIds((0,e.readFileSync)(t,`utf8`)):[]}catch{return[]}},listFiles=n=>(0,e.readdirSync)(n,{recursive:!0,withFileTypes:!0}).filter(e=>!e.isDirectory()).map(e=>(0,t.relative)(n,(0,t.join)(e.parentPath,e.name)).split(t.sep).join(`/`)),v=String.raw`[\p{L}_$][\p{L}\p{N}_$]*`,y=RegExp(`^${v}(?:\\.${v})+$`,`u`),b=/^--[a-z\d][\w-]*(?:=.*)?$/iu,x=[`com.github.tomakehurst.wiremock.`,`org.wiremock.`];function className(e){let t=text(e,`class name`).trim();return y.test(t)&&!t.endsWith(`.class`)?t:fail(`"${t}" is not a fully qualified Java class name such as "com.acme.MyTransformer" (${t.endsWith(`.class`)?`drop the ".class" suffix`:/[/\\]/u.test(t)?`separate packages with "." not "/"`:t.includes(`.`)?`only letters, digits, _ and $ may form each part`:`include the package – an extension's short name is not enough`})`)}function inspectJar(n,r){r.includes(`/`)&&fail(`name "${r}" must be a file name: WireMock's class path holds only the JARs directly in ${m}`),/\.jar$/iu.test(r)||fail(`"${r}" must end in ".jar" – WireMock's class path picks up only *.jar files; pass { file, name: "<name>.jar" } to rename it`);let i;try{i=(0,e.readFileSync)(n)}catch(e){return fail(`"${n}" cannot be read: ${e.message}`)}let a=within(`"${n}" is not a valid JAR`,()=>jarEntries(i)).map(e=>e.name),o=a.filter(e=>e.endsWith(`.class`)&&!e.startsWith(`META-INF/`)).map(e=>e.slice(0,-6).replaceAll(`/`,`.`));return o.length===0&&!a.some(e=>e.startsWith(`META-INF/services/`))&&fail(`"${n}" contains no Java classes – is it really an extension JAR?`),{target:t.posix.join(m,r),origin:n,classes:o}}function resolveJars(n){let r=resolvePath$1(n),i=(0,e.statSync)(r,{throwIfNoEntry:!1})??fail(`"${r}" does not exist`),a=isRecord(n)?n.name:void 0;if(i.isDirectory()){a!==void 0&&fail(`"name" cannot rename the directory "${r}", only a single JAR`);let n=(0,e.readdirSync)(r,{withFileTypes:!0}).filter(e=>e.isFile()&&/\.jar$/iu.test(e.name));return n.length===0&&fail(`the directory "${r}" contains no *.jar files`),n.map(e=>e.name).toSorted().map(e=>inspectJar((0,t.join)(r,e),e))}return i.isFile()||fail(`"${r}" is not a file`),[inspectJar(r,a===void 0?(0,t.basename)(r):text(a,`name`))]}function list(e,t){let n=e[t];if(n===void 0)return[];typeof n!=`function`&&fail(`${t} must be a method, got ${show(n)}`);let r=n.call(e);return Array.isArray(r)?r:fail(`${t}() must return an array, got ${show(r)}`)}function contribution(e){if(typeof e==`string`)return{classNames:[className(e)],jars:[],options:[]};let t=e;if(typeof e==`function`)try{t=new e}catch(t){return fail(`could not instantiate the extension class ${e.name||`(anonymous)`}: ${t.message}`)}if(!isRecord(t)||typeof t.getName!=`function`)return fail(`unsupported extension ${show(e)}; expected a fully qualified Java class name, a WireMockExtension or its class`);let n=t,r=text(n.getName(),`getName()`);return within(`extension "${r}"`,()=>{let e=list(n,`getOptions`).map(e=>typeof e==`string`&&b.test(e)?e:fail(`option ${show(e)} must be "--flag" or "--option=value", one option per entry`));e.some(e=>optionName(e)===`--extensions`)&&fail(`set class names with getClassNames(), not "--extensions"`);let t=list(n,`getClassNames`).map(className),i=list(n,`getJars`).flatMap(resolveJars);return t.length+i.length+e.length===0&&fail(`it contributes nothing: implement getClassNames(), getJars() or getOptions()`),{name:r,classNames:t,jars:i,options:e}})}const optionName=e=>e.split(`=`,1)[0];function mergeOptions(e,t,n){let r=[...e];for(let e of t){if(r.includes(e))continue;let t=r.find(t=>optionName(t)===optionName(e));t&&fail(`option "${e}" conflicts with "${t}" set by ${n}; WireMock accepts each option only once`),r.push(e)}return r}const simpleName=e=>e.slice(Math.max(e.lastIndexOf(`.`),e.lastIndexOf(`$`))+1).toLowerCase();function verifyClassNames(e,n){for(let r of e){if(x.some(e=>r.startsWith(e))||n.some(e=>e.classes.includes(r)))continue;let e=n.flatMap(e=>e.classes.filter(e=>simpleName(e)===simpleName(r)));fail(`class "${r}" is in ${n.length?`none of the registered extension JARs (${n.map(e=>(0,t.basename)(e.target)).join(`, `)})`:`no JAR: none is registered`}${e.length?`; did you mean "${e.join(`" or "`)}"?`:`.`} Register its JAR with withExtensionJar(), or – if the image already provides it – skip this check with withExtensionOptions({ verifyClassNames: false })`)}}const S=`WIREMOCK_NO_DOCKER`,C=[`CI`,`CONTINUOUS_INTEGRATION`,`GITHUB_ACTIONS`,`GITLAB_CI`,`CIRCLECI`,`BUILDKITE`,`JENKINS_URL`,`TF_BUILD`,`TEAMCITY_VERSION`,`BITBUCKET_BUILD_NUMBER`,`CODEBUILD_BUILD_ID`],w=`127.0.0.1`,T=[`-XX:+IgnoreUnrecognizedVMOptions`,`-XX:TieredStopAtLevel=1`];function detectCI(e=process.env){return C.find(t=>{let n=e[t]?.trim().toLowerCase();return n!==void 0&&n!==``&&n!==`false`&&n!==`0`})}function isNoDockerRequested(e=process.env){let t=e[S]?.trim().toLowerCase();return t===`1`||t===`true`}function installNoDockerRuntime(e={}){let n=e.env??process.env,r=detectCI(n);if(r)throw Error(`--no-docker must not be used in CI/CD (detected ${r}=${n[r]}): it does not run WireMock in a container. Run the E2E suite with Docker instead.`);let i=new NoDockerRuntime({jar:resolveJar(e.jar??n.WIREMOCK_JAR),java:e.java??n.WIREMOCK_JAVA??(n.JAVA_HOME?(0,t.join)(n.JAVA_HOME,`bin`,`java`):`java`),startupTimeout:e.startupTimeout??6e4,cacheDir:e.cacheDir??(0,t.join)((0,u.tmpdir)(),`wiremock-testcontainers`),log:e.log??(e=>process.emitWarning(e,{code:`WIREMOCK_NO_DOCKER`}))});return setRuntime(i),()=>{getRuntime()===i&&setRuntime(void 0)}}function resolveJar(n){if(n){let r=(0,t.resolve)(n);if(!(0,e.existsSync)(r))throw Error(`--no-docker: WireMock JAR "${r}" does not exist`);return r}let r;try{r=(0,t.join)((0,t.dirname)((0,i.createRequire)((0,t.join)(process.cwd(),`noop.js`)).resolve(`wiremock/package.json`)),`build`)}catch{throw Error('--no-docker: no WireMock standalone JAR found – install the "wiremock" npm package (e.g. `npm i -D wiremock`) or set WIREMOCK_JAR to a wiremock-standalone-*.jar')}let a=(0,e.existsSync)(r)?(0,e.readdirSync)(r).find(e=>/^wiremock-standalone-.+\.jar$/u.test(e)):void 0;if(!a)throw Error(`--no-docker: the "wiremock" npm package contains no wiremock-standalone-*.jar in "${r}"`);return(0,t.join)(r,a)}function classData(n,r,i){if(n!==!1)try{(0,e.mkdirSync)(n,{recursive:!0});let{size:a,mtimeMs:o}=(0,e.statSync)(r),c=(0,s.createHash)(`sha256`).update(`${r}\0${a}\0${o}\0${i}`).digest(`hex`).slice(0,16),l=(0,t.join)(n,`wiremock-${c}.jsa`);if((0,e.existsSync)(l))return{path:l};for(let r of(0,e.readdirSync)(n)){let i=(0,t.join)(n,r);r.endsWith(`.tmp`)&&Date.now()-(0,e.statSync)(i).mtimeMs>36e5&&(0,e.rmSync)(i,{force:!0})}return{path:l,recording:`${l}.${(0,s.randomUUID)()}.tmp`}}catch{return}}var NoDockerRuntime=class{#e;#t;constructor(e){this.#e=e}get mainClass(){return this.#t??=mainClass((0,e.readFileSync)(this.#e.jar))??`wiremock.Run`,this.#t}async start(n){let r=(0,e.mkdtempSync)((0,t.join)((0,u.tmpdir)(),`wiremock-no-docker-`));try{await materialize(n,r);let i=(0,t.join)(r,`/home/wiremock`);for(let n of[`mappings`,`__files`])(0,e.mkdirSync)((0,t.join)(i,n),{recursive:!0});let a=new NoDockerContainer(n,this.#e,r,i,await freePort(),()=>this.mainClass);return await a.launch(),this.#e.log(a.describe()),a}catch(t){throw(0,e.rmSync)(r,{recursive:!0,force:!0}),t}}};async function materialize(n,r){let at=n=>{let i=(0,t.join)(r,n);return(0,e.mkdirSync)((0,t.dirname)(i),{recursive:!0}),i};for(let{source:t,target:r}of n.directories)(0,e.cpSync)(t,at(r),{recursive:!0});for(let{source:t,target:r}of n.files)(0,e.copyFileSync)(t,at(r));let i=await Promise.all(n.contents.map(async({content:e,target:t})=>({target:t,bytes:e instanceof d.Readable?await(0,f.buffer)(e):e})));for(let{target:t,bytes:n}of i)(0,e.writeFileSync)(at(t),n)}function freePort(){return new Promise((e,t)=>{let n=(0,l.createServer)();n.once(`error`,t),n.listen(0,w,()=>{let{port:t}=n.address();n.close(()=>e(t))})})}async function isHealthy(e){try{let t=await fetch(`${e}/__admin/health`);return t.status===404?(await fetch(`${e}/__admin/mappings`)).ok:t.ok}catch{return!1}}const split=e=>e?.split(/\s+/u).filter(Boolean)??[],E=/* @__PURE__ */ new Set;let D=!1;const killOnExit=e=>{E.add(e),D||(D=!0,process.once(`exit`,()=>{for(let e of E)e.kill(`SIGKILL`)}))},unsupported=e=>/* @__PURE__ */ Error(`${e}() is not supported with --no-docker: WireMock runs as a local JVM process, not in a container`);var NoDockerContainer=class{#e;#t;#n;#r;#i;#a=`no-docker-${(0,s.randomUUID)()}`;#o;#s;#c;#l;#u=``;constructor(e,t,n,r,i,a){this.#e=e,this.#t=t,this.#n=n,this.#r=r,this.#i=i,this.#o=a}#d(){let n=(0,t.join)(this.#n,m);return(0,e.existsSync)(n)&&(0,e.readdirSync)(n).some(e=>/\.jar$/iu.test(e))?[`-cp`,[this.#t.jar,(0,t.join)(n,`*`)].join(t.delimiter),this.#o()]:[`-jar`,this.#t.jar]}async launch(){let{environment:t,command:n}=this.#e,r=this.#d(),i=classData(this.#t.cacheDir,this.#t.jar,this.#t.java),a=this.#c=i?.recording&&r[0]===`-cp`?void 0:i,s=[...T,...a?[a.recording?`-XX:ArchiveClassesAtExit=${a.recording}`:`-XX:SharedArchiveFile=${a.path}`]:[],...split(t.JAVA_OPTS),...r,`--port`,String(this.#i),`--bind-address`,w,`--root-dir`,this.#r,...n,...split(t.WIREMOCK_OPTIONS)];this.#u=``,this.#l=void 0;let c=(0,o.spawn)(this.#t.java,s,{env:{...process.env,...t},stdio:[`ignore`,`pipe`,`pipe`]});this.#s=c,c.once(`error`,e=>{this.#l=e});let record=e=>{this.#u=(this.#u+e.toString()).slice(-65536)};c.stdout?.on(`data`,record),c.stderr?.on(`data`,record),killOnExit(c);try{await this.#f(c)}catch(e){throw await this.#p(),e}a&&!a.recording&&/\[warning\]\[cds/u.test(this.#u)&&(0,e.rmSync)(a.path,{force:!0})}async#f(e){let t=Date.now()+this.#t.startupTimeout,output=()=>this.#u.trim()?`\n--- WireMock output ---\n${this.#u.trim()}`:``,poll=async()=>{if(this.#l){let e=this.#l.code===`ENOENT`;throw Error(e?`--no-docker: Java executable "${this.#t.java}" not found – WireMock's standalone JAR needs a Java 11+ runtime; install one or set WIREMOCK_JAVA / JAVA_HOME`:`--no-docker: could not start "${this.#t.java}": ${this.#l.message}`,{cause:this.#l})}if(e.exitCode!==null||e.signalCode!==null)throw Error(`--no-docker: WireMock exited during start-up (${e.signalCode??`exit code ${e.exitCode}`})${output()}`);if(!await isHealthy(this.#m)){if(Date.now()>=t)throw Error(`--no-docker: WireMock was not healthy within ${this.#t.startupTimeout}ms${output()}`);return await new Promise(e=>setTimeout(e,25)),poll()}};await poll()}async#p(){let t=this.#s,n=this.#c?.recording;if(E.delete(t),t?.pid&&t.exitCode===null&&t.signalCode===null){let e=(0,c.once)(t,`exit`);t.kill(n?`SIGTERM`:`SIGKILL`);let r=setTimeout(()=>t.kill(`SIGKILL`),1e4);await e,clearTimeout(r)}if(n){try{t?.exitCode===143&&(0,e.existsSync)(n)&&(0,e.renameSync)(n,this.#c.path)}catch{}(0,e.rmSync)(n,{force:!0}),this.#c=void 0}}get#m(){return`http://${w}:${this.#i}`}describe(){let e=/wiremock-standalone-(.+)\.jar$/u.exec(this.#t.jar)?.[1]??`unknown`,{image:t}=this.#e,n=t.slice(t.lastIndexOf(`:`)+1),r=/^(\d+\.\d+\.\d+)/u.exec(n)?.[1],i=r&&r!==e?` The image tag "${n}" differs from the JAR version ${e}.`:``;return`--no-docker: "${this.#e.image}" is NOT running in a container – WireMock ${e} standalone (local JVM, pid ${this.#s?.pid}) serves ${this.#m} instead.${i} Local development only; never use --no-docker in CI/CD.`}async stop(){await this.#p(),(0,e.rmSync)(this.#n,{recursive:!0,force:!0});let t=this.#a;return{getId:()=>t,copyArchiveFromContainer:async()=>Promise.reject(unsupported(`copyArchiveFromContainer`))}}async restart(){await this.#p(),await this.launch()}async[Symbol.asyncDispose](){await this.stop()}getHost(){return w}getHostname(){return this.getName()}getFirstMappedPort(){return this.#i}getMappedPort(e,t=`tcp`){let n=typeof e==`number`?`${e}/${t}`:e;if(n!==`8080/tcp`)throw Error(`No port mapping found for "${n}"`);return this.#i}getName(){return this.#e.name??`wiremock-no-docker-${this.#i}`}getLabels(){return{}}getId(){return this.#a}getNetworkNames(){return[]}async logs(){return d.Readable.from([this.#u])}getNetworkId(){throw unsupported(`getNetworkId`)}getIpAddress(){throw unsupported(`getIpAddress`)}async commit(){throw unsupported(`commit`)}async exec(){throw unsupported(`exec`)}async copyArchiveFromContainer(){throw unsupported(`copyArchiveFromContainer`)}async copyArchiveToContainer(){throw unsupported(`copyArchiveToContainer`)}async copyDirectoriesToContainer(){throw unsupported(`copyDirectoriesToContainer`)}async copyFilesToContainer(){throw unsupported(`copyFilesToContainer`)}async copyContentToContainer(){throw unsupported(`copyContentToContainer`)}};const O=`/home/wiremock/mappings`,k=`/home/wiremock/__files`,A=`inline JSON`;var WiremockContainer=class extends n.GenericContainer{#e=/* @__PURE__ */ new Map;#t=/* @__PURE__ */ new Map;#n=/* @__PURE__ */ new Set;#r=/* @__PURE__ */ new Map;#i=/* @__PURE__ */ new Map;#a=[];#o={scanning:!0,verifyClassNames:!0};#s;constructor(e=`wiremock/wiremock`){super(e),this.withExposedPorts(8080),this.withWaitStrategy(n.Wait.forListeningPorts())}withCommand(e){return this.#s=e,super.withCommand(e)}withMappings(n){return within(`WiremockContainer.withMappings`,()=>{let r=(0,t.join)(n,`mappings`);(0,e.existsSync)(r)||fail(`"${r}" does not exist`),(0,e.statSync)(r).isDirectory()||fail(`"${r}" is not a directory`);let i=(0,t.join)(n,`__files`),a=[{source:r,target:O},...(0,e.existsSync)(i)?[{source:i,target:k}]:[]];this.#m(a.flatMap(({source:e,target:n})=>listFiles(e).map(r=>({target:t.posix.join(n,r),origin:(0,t.resolve)(e,r),ids:n===O?stubIdsOf((0,t.join)(e,r)):[]})))),this.withCopyDirectoriesToContainer(a)}),this}withMapping(n,r){return within(`WiremockContainer.withMapping("${n}")`,()=>{let i=containerName(n),a=t.posix.join(O,i.endsWith(`.json`)?i:`${i}.json`),o=isRecord(r)&&`json`in r,s=o?A:resolveFile(r),c=o?inlineJson(r.json):(0,e.readFileSync)(s),l=within(s,()=>stubIds(c.toString()));this.withCopyContentToContainer(this.#m([{target:a,origin:s,ids:l}]).map(()=>({content:c,target:a})))}),this}withFiles(...e){return within(`WiremockContainer.withFiles`,()=>{let n=e.map(e=>{let n=resolveFile(e),r=isRecord(e)&&e.name!==void 0?containerName(e.name):(0,t.basename)(n);return{target:t.posix.join(k,r),origin:n}});this.withCopyFilesToContainer(this.#m(n).map(({origin:e,target:t})=>({source:e,target:t})))}),this}withExtension(e){return this.#c(`withExtension`,[e])}withExtensions(e){return this.#c(`withExtensions`,e)}withExtensionJar(e){return this.#l(`withExtensionJar`,[e])}withExtensionJars(e){return this.#l(`withExtensionJars`,e)}withExtensionOptions(e){return within(`WiremockContainer.withExtensionOptions`,()=>{let t=Object.entries(isRecord(e)?e:fail(`expected an options object, got ${show(e)}`));for(let[e,n]of t)e in this.#o||fail(`unknown option "${e}"; expected ${Object.keys(this.#o).join(` or `)}`),n!==void 0&&typeof n!=`boolean`&&fail(`"${e}" must be a boolean, got ${show(n)}`);this.#o={...this.#o,...Object.fromEntries(t.filter(([,e])=>e!==void 0))}}),this}async start(){let e=within(`WiremockContainer.start`,()=>this.#d());this.createOpts.Cmd=e.length>0?[...this.#s??[],...e]:this.#s?.slice();let t=getRuntime();try{let e=t?await t.start(this.#p()):await super.start();return new StartedWiremockContainer(e,`http://${e.getHost()}:${e.getMappedPort(8080)}`)}catch(e){throw this.#f(e)}}#c(e,t){return within(`WiremockContainer.${e}`,()=>{let e=(Array.isArray(t)?t:fail(`expected an array, got ${show(t)}`)).map(contribution),n=new Map(this.#r),r=this.#a;for(let{name:t,classNames:i,jars:a,options:o}of e){if(t===void 0)continue;let e=JSON.stringify([i,a.map(({origin:e,target:t})=>[e,t]),o]);(n.get(t)??e)!==e&&fail(`another extension named "${t}" is already registered; names must be unique`),n.set(t,e),r=within(`extension "${t}"`,()=>mergeOptions(r,o,`another extension`))}this.#u(e.flatMap(({jars:e})=>e)),[this.#r,this.#a]=[n,r];for(let t of e.flatMap(({classNames:e})=>e))this.#n.add(t)}),this}#l(e,t){return within(`WiremockContainer.${e}`,()=>this.#u((Array.isArray(t)?t:fail(`expected an array, got ${show(t)}`)).flatMap(resolveJars))),this}#u(e){let t=this.#m(e);for(let e of t)this.#i.set(e.target,e);this.withCopyFilesToContainer(t.map(({origin:e,target:t})=>({source:e,target:t})))}#d(){let{scanning:e,verifyClassNames:t}=this.#o;t&&verifyClassNames(this.#n,[...this.#i.values()]);let n=[...this.#s??[],...this.environment.WIREMOCK_OPTIONS?.split(/\s+/u).filter(Boolean)??[]],r=mergeOptions(n,[...this.#a,...e?[]:[`--disable-extensions-scanning`]],`withCommand() or WIREMOCK_OPTIONS`).slice(n.length);if(this.#n.size===0)return r;let i=n.find(e=>optionName(e)===`--extensions`);return i&&fail(`"${i}" is already set by withCommand() or WIREMOCK_OPTIONS; WireMock accepts it only once – register those classes with withExtensions() instead`),[...r,`--extensions`,[...this.#n].join(`,`)]}#f(e){if(this.#n.size+this.#i.size===0)return e;let n=[...this.#i.keys()].map(e=>(0,t.basename)(e));return Error(`${e.message}\nWireMock was started with extensions: ${[...this.#n].join(`, `)||`none`}; JARs: ${n.join(`, `)||`none`}. An extension stops WireMock if its class is missing (ClassNotFoundException), lacks a public no-argument constructor or targets a newer Java than the image's; WireMock's output names the cause (Docker: run with DEBUG=testcontainers:containers).`,{cause:e})}#p(){return{image:this.imageName.string,name:this.createOpts.name,command:[...this.createOpts.Cmd??[]],environment:{...this.environment},directories:this.directoriesToCopy.map(({source:e,target:t})=>({source:e,target:t})),files:this.filesToCopy.map(({source:e,target:t})=>({source:e,target:t})),contents:this.contentsToCopy.map(({content:e,target:t})=>({content:e,target:t}))}}#m(e){let t=new Map(this.#e),n=new Map(this.#t),r=e.filter(({target:e,origin:r,ids:i=[]})=>{let a=t.get(e);if(a===r&&r!==A)return!1;a&&fail(`"${e}" is already registered from ${a}; registering ${r} would overwrite it`);for(let t of i){let i=n.get(t);i&&fail(`stub id "${t}" in ${r} is already used by "${i}"; WireMock refuses to start with duplicate stub ids`),n.set(t,e)}return t.set(e,r),!0});return[this.#e,this.#t]=[t,n],r}};const j=`/__admin`;var StartedWiremockContainer=class extends n.AbstractStartedContainer{#e;constructor(e,t){super(e),this.#e=t}get rpcUrl(){return this.#e}getUrl(e=``){if(e===``)return this.#e;let t=e.startsWith(`/`)||e.startsWith(`?`)?e:`/${e}`;return`${this.#e}${t}`}getAdminUrl(){return`${this.#e}${j}`}getMappingUrl(){return`${this.getAdminUrl()}/mappings`}async isRunning(){try{let e=await fetch(`${this.getAdminUrl()}/health`);return e.status===404?(await fetch(this.getMappingUrl())).ok:e.ok}catch{return!1}}async waitUntilReady(e={}){let{timeout:t=3e4,interval:n=250}=e,r=Date.now()+t,poll=async()=>{if(!await this.isRunning()){if(Date.now()>=r)throw Error(`WireMock at ${this.#e} was not ready within ${t}ms`);return await new Promise(e=>setTimeout(e,n)),poll()}};await poll()}async reset(){await this.#t(`POST`,`/reset`)}async resetRequests(){await this.#t(`DELETE`,`/requests`)}async countRequests(e){let{count:t}=await(await this.#t(`POST`,`/requests/count`,e)).json();return t}async findRequests(e){let{requests:t}=await(await this.#t(`POST`,`/requests/find`,e)).json();return t??[]}async#t(e,t,n){let r=await fetch(`${this.getAdminUrl()}${t}`,{method:e,...n===void 0?{}:{headers:{"Content-Type":`application/json`},body:JSON.stringify(n)}});if(!r.ok){let n=await r.text().catch(()=>``);throw Error(`WireMock admin request ${e} ${j}${t} failed with ${r.status} ${r.statusText}${n?`: ${n}`:``}`)}return r}};exports.NO_DOCKER_ENV=S,exports.StartedWiremockContainer=StartedWiremockContainer,exports.WiremockContainer=WiremockContainer,exports.detectCI=detectCI,exports.installNoDockerRuntime=installNoDockerRuntime,exports.isNoDockerRequested=isNoDockerRequested;
package/dist/index.d.cts CHANGED
@@ -1,11 +1,7 @@
1
1
  import { AbstractStartedContainer, GenericContainer, StartedTestContainer } from "testcontainers";
2
2
  /** A path (relative to the working directory) or a `file:` URL. */
3
3
  type WiremockPath = string | URL;
4
- /**
5
- * A fixture file: a local path or `file:` URL, `{ file }`, a file inside an installed npm
6
- * package `{ package, file }` (its `exports` are ignored), or a file next to a module
7
- * `{ module: import.meta.url | __filename | __dirname, file }`.
8
- */
4
+ /** A path or `file:` URL, `{ file }`, a file of an installed package `{ package, file }`, or one next to a module `{ module, file }`. */
9
5
  type WiremockFileSource = WiremockPath | {
10
6
  readonly file: WiremockPath;
11
7
  } | {
@@ -27,87 +23,200 @@ type WiremockMappingSource = WiremockFileSource | {
27
23
  readonly mappings: readonly WiremockStubMapping[];
28
24
  };
29
25
  };
30
- /** A file source; object forms may set `name`, the path below `__files/` (default: the base name). */
26
+ /** A file source; object forms may set `name`, the path below `__files/`. */
31
27
  type WiremockStaticFile = WiremockPath | (Exclude<WiremockFileSource, WiremockPath> & {
32
28
  readonly name?: string;
33
29
  });
30
+ /** An extension JAR, or a directory of them – any {@link WiremockStaticFile}; `name` renames a JAR. */
31
+ type WiremockExtensionJar = WiremockStaticFile;
34
32
  /**
35
- * A Testcontainer for Wiremock. Stubs are registered with {@link withMappings} (a directory),
36
- * {@link withMapping} (one named stub) and {@link withFiles} (response bodies), in any
37
- * combination. Registrations are validated immediately and may not overwrite one another.
38
- *
33
+ * Base interface all WireMock extensions must implement: what WireMock loads for it. Implement at
34
+ * least one optional member.
39
35
  * @example
40
- * ```typescript
41
- * const container = await new WiremockContainer().start();
42
- * ```
36
+ * class Uppercase implements WireMockExtension {
37
+ * getName() { return "uppercase"; }
38
+ * getClassNames() { return ["com.acme.UppercaseTransformer"]; }
39
+ * getJars() { return ["./acme.jar"]; }
40
+ * }
43
41
  */
44
- export declare class WiremockContainer extends GenericContainer {
45
- #private;
42
+ interface WireMockExtension {
43
+ /** Its name, e.g. WireMock's `"uppercase"`. Re-registering a name: no-op if configured alike, else an error. */
44
+ getName(): string;
45
+ /** Fully qualified Java class names, passed to `--extensions`. */
46
+ getClassNames?(): readonly string[];
47
+ /** JARs – or directories of them – for WireMock's class path. */
48
+ getJars?(): readonly WiremockExtensionJar[];
49
+ /** WireMock CLI options, one per entry: `"--flag"` or `"--option=value"`. */
50
+ getOptions?(): readonly string[];
51
+ }
52
+ /** Represents a class constructor for an extension */
53
+ type ExtensionConstructor<T extends WireMockExtension = WireMockExtension> = new () => T;
54
+ /** Union type of all supported ways to register an extension: a Java class name, a {@link WireMockExtension} or its class. */
55
+ type ExtensionInput = string | ExtensionConstructor | WireMockExtension;
56
+ /** Options for `WiremockContainer.withExtensionOptions()`. */
57
+ interface WiremockExtensionOptions {
58
+ /** Load extensions JARs declare in `META-INF/services`; `false`: `--disable-extensions-scanning`. Default `true`. */
59
+ readonly scanning?: boolean;
60
+ /** Fail `start()` for a class in no registered JAR and not WireMock's own; off for classes the image provides. Default `true`. */
61
+ readonly verifyClassNames?: boolean;
62
+ }
63
+ /** Set to `1` by the `--no-docker` flag. */
64
+ export declare const NO_DOCKER_ENV = "WIREMOCK_NO_DOCKER";
65
+ type Environment = Readonly<Record<string, string | undefined>>;
66
+ /** Options for {@link installNoDockerRuntime}. */
67
+ interface NoDockerOptions {
68
+ /** WireMock standalone JAR. Default: `$WIREMOCK_JAR`, else the `wiremock` npm package's. */
69
+ readonly jar?: string;
70
+ /** Java executable. Default: `$WIREMOCK_JAVA`, `$JAVA_HOME/bin/java`, else `java`. */
71
+ readonly java?: string;
72
+ /** Milliseconds to wait for WireMock to be healthy. Default `60_000`. */
73
+ readonly startupTimeout?: number;
74
+ /** Receives the per-start warning. Default: `process.emitWarning`. */
75
+ readonly log?: (message: string) => void;
46
76
  /**
47
- * Creates a new WiremockContainer.
48
- * @example
49
- * ```typescript
50
- * const container = await new WiremockContainer().start();
51
- * ```
52
- * @param image The docker image to use. Defaults to "wiremock/wiremock".
77
+ * Cache for the class-data archive that halves JVM start-up after the first run; `false`
78
+ * disables it. Default: `<os tmpdir>/wiremock-testcontainers`.
53
79
  */
80
+ readonly cacheDir?: string | false;
81
+ /** Source of the CI/CD check and the defaults above. Default: `process.env`. */
82
+ readonly env?: Environment;
83
+ }
84
+ /** The CI/CD variable set in `env`, if any. */
85
+ export declare function detectCI(env?: Environment): string | undefined;
86
+ /** Whether {@link NO_DOCKER_ENV} is `1` or `true`. */
87
+ export declare function isNoDockerRequested(env?: Environment): boolean;
88
+ /**
89
+ * **Local development only**: serves every `WiremockContainer` started from now on from a local
90
+ * WireMock JVM (Java 11+) on `127.0.0.1`, warning on each start. Container-only features throw.
91
+ * @example
92
+ * // setup file, registered only when WIREMOCK_NO_DOCKER=1
93
+ * import { installNoDockerRuntime } from "@hellaweb3/wiremock-testcontainers-nodejs/no-docker";
94
+ * installNoDockerRuntime();
95
+ * @returns A function that uninstalls it.
96
+ * @throws In CI/CD, or if no WireMock JAR is found.
97
+ */
98
+ export declare function installNoDockerRuntime(options?: NoDockerOptions): () => void;
99
+ /** A WireMock Testcontainer. Registrations are validated at once and may not overwrite one another. */
100
+ export declare class WiremockContainer extends GenericContainer {
101
+ #private;
102
+ /** @param image Docker image, default `wiremock/wiremock`. */
54
103
  constructor(image?: string);
104
+ /** WireMock CLI arguments, e.g. `["--verbose"]`; extensions add theirs on `start()`. */
105
+ override withCommand(command: string[]): this;
55
106
  /**
56
- * Adds mappings to the Wiremock container.
57
- * @example
58
- * ```typescript
59
- * const container = await new WiremockContainer()
60
- * .withMappings("./test/__mocks__/wiremock")
61
- * .start();
62
- * ```
63
- * @param directory The directory containing `mappings/` and, optionally, `__files/`.
64
- * @throws If `mappings/` is not a directory in `directory`, or a file would overwrite an earlier registration.
107
+ * Copies `directory/mappings/` (required) and `directory/__files/` (optional) into the container.
108
+ * @throws If `mappings/` is missing or a file would overwrite an earlier registration.
65
109
  */
66
110
  withMappings(directory: string): this;
67
111
  /**
68
- * Registers one stub at `mappings/<name>.json`, validated and copied byte-for-byte.
69
- * @example
70
- * ```typescript
71
- * new WiremockContainer()
72
- * .withMapping("cards", "./test/fixtures/cards.json")
73
- * .withMapping("user", { package: "@acme/fixtures", file: "wiremock/user.json" })
74
- * .withMapping("balance", { module: import.meta.url, file: "./balance.json" })
75
- * .withMapping("ping", { json: { request: { url: "/ping" }, response: { status: 200 } } });
76
- * ```
77
- * @param name File name below `mappings/`, may contain `/`; `.json` is appended unless present.
78
- * @throws If the name or source is invalid, the source is not a WireMock mapping, or it would overwrite an earlier registration.
112
+ * Registers one stub at `mappings/<name>.json` (`name` may contain `/`).
113
+ * @example .withMapping("ping", { json: { request: { url: "/ping" }, response: { status: 200 } } })
114
+ * @throws If the source is invalid, not a WireMock stub, or would overwrite an earlier registration.
79
115
  */
80
116
  withMapping(name: string, source: WiremockMappingSource): this;
81
117
  /**
82
- * Registers response bodies at `__files/<name>` (default: the base name), copied as-is.
83
- * @example
84
- * ```typescript
85
- * new WiremockContainer().withFiles("./test/fixtures/body.json", { file: "./logo.png", name: "img/logo.png" });
86
- * ```
87
- * @throws If a source or name is invalid, or a file would overwrite an earlier registration; nothing is registered then.
118
+ * Registers response bodies at `__files/<name>` (default: the base name). All or nothing.
119
+ * @throws If a source or name is invalid or a file would overwrite an earlier registration.
88
120
  */
89
121
  withFiles(...files: WiremockStaticFile[]): this;
90
122
  /**
91
- * Starts the container and returns a {@link StartedWiremockContainer}.
92
- * @returns A promise that resolves to the started container.
123
+ * Registers an extension: a fully qualified Java class name, or a {@link WireMockExtension} or its
124
+ * class. Class names share one `--extensions`, in registration order, without repeats.
125
+ * @example .withExtensionJar("./acme.jar").withExtension("com.acme.UppercaseTransformer")
126
+ * @throws If a class name, an extension or its JARs are invalid, or an option conflicts.
127
+ */
128
+ withExtension(extension: ExtensionInput): this;
129
+ /** {@link withExtension} for several extensions – all or nothing. */
130
+ withExtensions(extensions: readonly ExtensionInput[]): this;
131
+ /**
132
+ * Puts a JAR – or every `*.jar` of a directory – on WireMock's class path. Extensions listed in
133
+ * its `META-INF/services` load by themselves; others need {@link withExtension}.
134
+ * @example .withExtensionJar("./acme.jar").withExtensionJar({ package: "@acme/wiremock", file: "jars" })
135
+ * @throws If the source is missing, unreadable or not a JAR, or another JAR has the same name.
136
+ */
137
+ withExtensionJar(jar: WiremockExtensionJar): this;
138
+ /** {@link withExtensionJar} for several JARs or directories – all or nothing. */
139
+ withExtensionJars(jars: readonly WiremockExtensionJar[]): this;
140
+ /**
141
+ * Configures how extensions load; options not given keep their value.
142
+ * @throws For an unknown option or a non-boolean value.
143
+ */
144
+ withExtensionOptions(options: WiremockExtensionOptions): this;
145
+ /**
146
+ * Starts WireMock – in Docker, or locally once `./no-docker` is installed.
147
+ * @throws Before starting, for a class in no registered JAR or options conflicting with
148
+ * `withCommand()`; if WireMock fails, naming the extensions in use.
93
149
  */
94
150
  override start(): Promise<StartedWiremockContainer>;
95
151
  }
96
- /**
97
- * A started Wiremock container with helper methods for interacting with the node.
98
- */
152
+ /** WireMock [request-matching](https://wiremock.org/docs/request-matching/) criteria; unknown fields pass through. */
153
+ export interface WiremockRequestCriteria {
154
+ /** E.g. `"GET"`; `"ANY"` matches every method. */
155
+ readonly method?: string;
156
+ /** Exact path and query string. */
157
+ readonly url?: string;
158
+ /** Exact path, any query string. */
159
+ readonly urlPath?: string;
160
+ /** Regex for the path. */
161
+ readonly urlPathPattern?: string;
162
+ /** Regex for path and query string. */
163
+ readonly urlPattern?: string;
164
+ /** E.g. `{ "Content-Type": { equalTo: "application/json" } }`. */
165
+ readonly headers?: Readonly<Record<string, unknown>>;
166
+ /** E.g. `{ page: { equalTo: "1" } }`. */
167
+ readonly queryParameters?: Readonly<Record<string, unknown>>;
168
+ /** E.g. `[{ equalToJson: { id: 1 } }]`. */
169
+ readonly bodyPatterns?: readonly object[];
170
+ readonly [field: string]: unknown;
171
+ }
172
+ /** A request from WireMock's journal; fields not modelled here are kept as-is. */
173
+ export interface RecordedRequest {
174
+ readonly id?: string;
175
+ /** Path and query string, e.g. `"/cards?page=1"`. */
176
+ readonly url: string;
177
+ readonly absoluteUrl?: string;
178
+ readonly method: string;
179
+ readonly headers?: Readonly<Record<string, unknown>>;
180
+ readonly queryParams?: Readonly<Record<string, unknown>>;
181
+ readonly body?: string;
182
+ /** Epoch milliseconds. */
183
+ readonly loggedDate?: number;
184
+ readonly [field: string]: unknown;
185
+ }
186
+ /** Options for {@link StartedWiremockContainer.waitUntilReady}. */
187
+ export interface WaitUntilReadyOptions {
188
+ /** Milliseconds before giving up. Default `30_000`. */
189
+ readonly timeout?: number;
190
+ /** Milliseconds between probes. Default `250`. */
191
+ readonly interval?: number;
192
+ }
193
+ /** A started WireMock container with URL, health, reset and verification helpers. */
99
194
  export declare class StartedWiremockContainer extends AbstractStartedContainer {
100
195
  #private;
101
- /**
102
- * Creates a new StartedWiremockContainer.
103
- * @param startedTestContainer The underlying TestContainer.
104
- * @param url The URL of the started container.
105
- */
196
+ /** Created by {@link WiremockContainer.start}; `url` is WireMock's base URL. */
106
197
  constructor(startedTestContainer: StartedTestContainer, url: string);
198
+ /** Base URL, e.g. `http://localhost:32768`. */
199
+ get rpcUrl(): string;
200
+ /** Absolute URL for `path`, e.g. `getUrl("cards?page=1")`; no argument → {@link rpcUrl}. */
201
+ getUrl(path?: string): string;
202
+ /** Admin API base URL: `<rpcUrl>/__admin`. */
203
+ getAdminUrl(): string;
204
+ /** Stub mappings endpoint: `<rpcUrl>/__admin/mappings`. */
205
+ getMappingUrl(): string;
206
+ /** Whether WireMock responds healthily. Never throws. */
207
+ isRunning(): Promise<boolean>;
107
208
  /**
108
- * Gets the URL of the Wiremock node.
209
+ * Polls {@link isRunning} until healthy.
210
+ * @throws If not healthy within `options.timeout`.
109
211
  */
110
- get rpcUrl(): string;
212
+ waitUntilReady(options?: WaitUntilReadyOptions): Promise<void>;
213
+ /** Restores registered stubs and scenarios and clears the journal – no restart. */
214
+ reset(): Promise<void>;
215
+ /** Clears the request journal; stubs are kept. */
216
+ resetRequests(): Promise<void>;
217
+ /** Number of journal entries matching `request` since the last reset. */
218
+ countRequests(request: WiremockRequestCriteria): Promise<number>;
219
+ /** Journal entries matching `request` since the last reset. */
220
+ findRequests(request: WiremockRequestCriteria): Promise<RecordedRequest[]>;
111
221
  }
112
- export type { WiremockFileSource, WiremockMappingSource, WiremockPath, WiremockStaticFile, WiremockStubMapping };
113
- //# sourceMappingURL=index.d.cts.map
222
+ export type { ExtensionConstructor, ExtensionInput, NoDockerOptions, WireMockExtension, WiremockExtensionJar, WiremockExtensionOptions, WiremockFileSource, WiremockMappingSource, WiremockPath, WiremockStaticFile, WiremockStubMapping };