@hellaweb3/wiremock-testcontainers-nodejs 0.2.1 → 0.3.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/README.md +123 -366
- package/dist/index.cjs +1 -194
- package/dist/index.d.cts +77 -66
- package/dist/index.d.ts +77 -66
- package/dist/index.js +1 -192
- package/dist/no-docker.cjs +1 -0
- package/dist/no-docker.d.cts +45 -0
- package/dist/no-docker.d.ts +45 -0
- package/dist/no-docker.js +1 -0
- package/dist/runtime-6blQsklJ.cjs +1 -0
- package/dist/runtime-ChyDTLYb.js +1 -0
- package/package.json +29 -12
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/src/index.ts +0 -210
- package/src/sources.ts +0 -143
package/dist/index.js
CHANGED
|
@@ -1,192 +1 @@
|
|
|
1
|
-
import { createRequire } from "
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, join, posix, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { AbstractStartedContainer, GenericContainer, Wait } from "testcontainers";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
// src/sources.ts
|
|
7
|
-
const PACKAGE_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u;
|
|
8
|
-
function fail(message) {
|
|
9
|
-
throw new Error(message);
|
|
10
|
-
}
|
|
11
|
-
function within(context, fn) {
|
|
12
|
-
try {
|
|
13
|
-
return fn();
|
|
14
|
-
} catch (error) {
|
|
15
|
-
throw new Error(`${context}: ${error.message}`, { cause: error });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
|
-
const show = (value) => value instanceof URL ? value.href : JSON.stringify(value) ?? String(value);
|
|
20
|
-
const text = (value, field) => typeof value === "string" && value.trim() !== "" ? value : fail(`"${field}" must be a non-empty string, got ${show(value)}`);
|
|
21
|
-
const toPath = (value, field) => {
|
|
22
|
-
if (!(value instanceof URL) && !text(value, field).startsWith("file:")) return resolve(value);
|
|
23
|
-
try {
|
|
24
|
-
return fileURLToPath(value);
|
|
25
|
-
} catch {
|
|
26
|
-
return fail(`"${field}" must be a path or a file: URL, got ${show(value)}`);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
const existingFile = (path) => {
|
|
30
|
-
const stats = statSync(path, { throwIfNoEntry: false });
|
|
31
|
-
return stats?.isFile() ? path : fail(`"${path}" ${stats ? "is not a file" : "does not exist"}`);
|
|
32
|
-
};
|
|
33
|
-
const packageRoot = (name) => {
|
|
34
|
-
if (!PACKAGE_NAME.test(name)) fail(`"${name}" is not a valid npm package name`);
|
|
35
|
-
return (createRequire(join(process.cwd(), "noop.js")).resolve.paths(name) ?? []).map((dir) => join(dir, name)).find((dir) => existsSync(join(dir, "package.json"))) ?? fail(`package "${name}" is not installed (searched from "${process.cwd()}")`);
|
|
36
|
-
};
|
|
37
|
-
function resolveFile(source) {
|
|
38
|
-
if (typeof source === "string" || source instanceof URL) return existingFile(toPath(source, "source"));
|
|
39
|
-
if (!isRecord(source)) return fail(`unsupported source ${show(source)}; expected a path, a file: URL, { file }, { package, file } or { module, file }`);
|
|
40
|
-
const { file, package: pkg, module } = source;
|
|
41
|
-
if (pkg !== void 0 && module !== void 0) fail("a source may set \"package\" or \"module\", not both");
|
|
42
|
-
if (pkg !== void 0) {
|
|
43
|
-
const root = packageRoot(text(pkg, "package"));
|
|
44
|
-
const path = resolve(root, text(file, "file"));
|
|
45
|
-
return relative(root, path).split(sep)[0] === ".." ? fail(`"${String(file)}" must point to a file inside package "${pkg}"`) : existingFile(path);
|
|
46
|
-
}
|
|
47
|
-
if (module === void 0) return existingFile(toPath(file, "file"));
|
|
48
|
-
const anchor = toPath(module, "module");
|
|
49
|
-
return existingFile(resolve(statSync(anchor, { throwIfNoEntry: false })?.isDirectory() ? anchor : dirname(anchor), text(file, "file")));
|
|
50
|
-
}
|
|
51
|
-
const containerName = (name) => {
|
|
52
|
-
const value = text(name, "name");
|
|
53
|
-
return value.split("/").some((part) => [
|
|
54
|
-
"",
|
|
55
|
-
".",
|
|
56
|
-
".."
|
|
57
|
-
].includes(part) || /[\\\0]/u.test(part)) ? fail(`name "${value}" must be a relative path using "/" separators, without empty, "." or ".." segments`) : value;
|
|
58
|
-
};
|
|
59
|
-
const inlineJson = (json) => typeof json === "string" ? json : JSON.stringify(json, null, 2) ?? fail("\"json\" must be a JSON string or an object");
|
|
60
|
-
const parseJson = (json) => {
|
|
61
|
-
try {
|
|
62
|
-
return JSON.parse(json);
|
|
63
|
-
} catch (error) {
|
|
64
|
-
return fail(`invalid JSON (${error.message})`);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
function stubIds(json) {
|
|
68
|
-
const value = parseJson(json);
|
|
69
|
-
const many = isRecord(value) && "mappings" in value;
|
|
70
|
-
const stubs = many ? value.mappings : [value];
|
|
71
|
-
if (!Array.isArray(stubs) || stubs.length === 0) fail("\"mappings\" must be a non-empty array of stub mappings");
|
|
72
|
-
return stubs.flatMap((stub, index) => {
|
|
73
|
-
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)`);
|
|
74
|
-
const id = stub.id ?? stub.uuid;
|
|
75
|
-
return typeof id === "string" ? [id] : [];
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
const stubIdsOf = (path) => {
|
|
79
|
-
try {
|
|
80
|
-
return path.endsWith(".json") ? stubIds(readFileSync(path, "utf8")) : [];
|
|
81
|
-
} catch {
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const listFiles = (dir) => readdirSync(dir, {
|
|
86
|
-
recursive: true,
|
|
87
|
-
withFileTypes: true
|
|
88
|
-
}).filter((entry) => !entry.isDirectory()).map((entry) => relative(dir, join(entry.parentPath, entry.name)).split(sep).join("/"));
|
|
89
|
-
// src/index.ts
|
|
90
|
-
const MAPPINGS = "/home/wiremock/mappings";
|
|
91
|
-
const FILES = "/home/wiremock/__files";
|
|
92
|
-
const INLINE = "inline JSON";
|
|
93
|
-
var WiremockContainer = class extends GenericContainer {
|
|
94
|
-
#origins = /* @__PURE__ */ new Map();
|
|
95
|
-
#stubIds = /* @__PURE__ */ new Map();
|
|
96
|
-
constructor(image = "wiremock/wiremock") {
|
|
97
|
-
super(image);
|
|
98
|
-
this.withExposedPorts(8080);
|
|
99
|
-
this.withWaitStrategy(Wait.forListeningPorts());
|
|
100
|
-
}
|
|
101
|
-
withMappings(directory) {
|
|
102
|
-
within("WiremockContainer.withMappings", () => {
|
|
103
|
-
const mappings = join(directory, "mappings");
|
|
104
|
-
if (!existsSync(mappings)) fail(`"${mappings}" does not exist`);
|
|
105
|
-
if (!statSync(mappings).isDirectory()) fail(`"${mappings}" is not a directory`);
|
|
106
|
-
const files = join(directory, "__files");
|
|
107
|
-
const dirs = [{
|
|
108
|
-
source: mappings,
|
|
109
|
-
target: MAPPINGS
|
|
110
|
-
}, ...existsSync(files) ? [{
|
|
111
|
-
source: files,
|
|
112
|
-
target: FILES
|
|
113
|
-
}] : []];
|
|
114
|
-
this.#claim(dirs.flatMap(({ source, target }) => listFiles(source).map((name) => ({
|
|
115
|
-
target: posix.join(target, name),
|
|
116
|
-
origin: resolve(source, name),
|
|
117
|
-
ids: target === MAPPINGS ? stubIdsOf(join(source, name)) : []
|
|
118
|
-
}))));
|
|
119
|
-
this.withCopyDirectoriesToContainer(dirs);
|
|
120
|
-
});
|
|
121
|
-
return this;
|
|
122
|
-
}
|
|
123
|
-
withMapping(name, source) {
|
|
124
|
-
within(`WiremockContainer.withMapping("${name}")`, () => {
|
|
125
|
-
const file = containerName(name);
|
|
126
|
-
const target = posix.join(MAPPINGS, file.endsWith(".json") ? file : `${file}.json`);
|
|
127
|
-
const inline = isRecord(source) && "json" in source;
|
|
128
|
-
const origin = inline ? INLINE : resolveFile(source);
|
|
129
|
-
const content = inline ? inlineJson(source.json) : readFileSync(origin);
|
|
130
|
-
const ids = within(origin, () => stubIds(content.toString()));
|
|
131
|
-
this.withCopyContentToContainer(this.#claim([{
|
|
132
|
-
target,
|
|
133
|
-
origin,
|
|
134
|
-
ids
|
|
135
|
-
}]).map(() => ({
|
|
136
|
-
content,
|
|
137
|
-
target
|
|
138
|
-
})));
|
|
139
|
-
});
|
|
140
|
-
return this;
|
|
141
|
-
}
|
|
142
|
-
withFiles(...files) {
|
|
143
|
-
within("WiremockContainer.withFiles", () => {
|
|
144
|
-
const claims = files.map((file) => {
|
|
145
|
-
const origin = resolveFile(file);
|
|
146
|
-
const name = isRecord(file) && file.name !== void 0 ? containerName(file.name) : basename(origin);
|
|
147
|
-
return {
|
|
148
|
-
target: posix.join(FILES, name),
|
|
149
|
-
origin
|
|
150
|
-
};
|
|
151
|
-
});
|
|
152
|
-
this.withCopyFilesToContainer(this.#claim(claims).map(({ origin, target }) => ({
|
|
153
|
-
source: origin,
|
|
154
|
-
target
|
|
155
|
-
})));
|
|
156
|
-
});
|
|
157
|
-
return this;
|
|
158
|
-
}
|
|
159
|
-
async start() {
|
|
160
|
-
const startedContainer = await super.start();
|
|
161
|
-
return new StartedWiremockContainer(startedContainer, `http://${startedContainer.getHost()}:${startedContainer.getMappedPort(8080)}`);
|
|
162
|
-
}
|
|
163
|
-
#claim(claims) {
|
|
164
|
-
const origins = new Map(this.#origins);
|
|
165
|
-
const owners = new Map(this.#stubIds);
|
|
166
|
-
const fresh = claims.filter(({ target, origin, ids = [] }) => {
|
|
167
|
-
const existing = origins.get(target);
|
|
168
|
-
if (existing === origin && origin !== INLINE) return false;
|
|
169
|
-
if (existing) fail(`"${target}" is already registered from ${existing}; registering ${origin} would overwrite it`);
|
|
170
|
-
for (const id of ids) {
|
|
171
|
-
const owner = owners.get(id);
|
|
172
|
-
if (owner) fail(`stub id "${id}" in ${origin} is already used by "${owner}"; WireMock refuses to start with duplicate stub ids`);
|
|
173
|
-
owners.set(id, target);
|
|
174
|
-
}
|
|
175
|
-
origins.set(target, origin);
|
|
176
|
-
return true;
|
|
177
|
-
});
|
|
178
|
-
[this.#origins, this.#stubIds] = [origins, owners];
|
|
179
|
-
return fresh;
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
var StartedWiremockContainer = class extends AbstractStartedContainer {
|
|
183
|
-
#rpcUrl;
|
|
184
|
-
constructor(startedTestContainer, url) {
|
|
185
|
-
super(startedTestContainer);
|
|
186
|
-
this.#rpcUrl = url;
|
|
187
|
-
}
|
|
188
|
-
get rpcUrl() {
|
|
189
|
-
return this.#rpcUrl;
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
export { StartedWiremockContainer, WiremockContainer };
|
|
1
|
+
import{t as getRuntime}from"./runtime-ChyDTLYb.js";import{createRequire}from"node:module";import{existsSync,readFileSync,readdirSync,statSync}from"node:fs";import{basename,dirname,join,posix,relative,resolve,sep}from"node:path";import{AbstractStartedContainer,GenericContainer,Wait}from"testcontainers";import{fileURLToPath}from"node:url";const PACKAGE_NAME=/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u;function fail(message){throw new Error(message)}function within(context,fn){try{return fn()}catch(error){throw new Error(`${context}: ${error.message}`,{cause:error})}}const isRecord=value=>typeof value===`object`&&value!==null&&!Array.isArray(value);const show=value=>value instanceof URL?value.href:JSON.stringify(value)??String(value);const text=(value,field)=>typeof value===`string`&&value.trim()!==``?value:fail(`"${field}" must be a non-empty string, got ${show(value)}`);const toPath=(value,field)=>{if(!(value instanceof URL)&&!text(value,field).startsWith(`file:`))return resolve(value);try{return fileURLToPath(value)}catch{return fail(`"${field}" must be a path or a file: URL, got ${show(value)}`)}};const existingFile=path=>{const stats=statSync(path,{throwIfNoEntry:false});return stats?.isFile()?path:fail(`"${path}" ${stats?`is not a file`:`does not exist`}`)};const packageRoot=name=>{if(!PACKAGE_NAME.test(name))fail(`"${name}" is not a valid npm package name`);return(createRequire(join(process.cwd(),`noop.js`)).resolve.paths(name)??[]).map(dir=>join(dir,name)).find(dir=>existsSync(join(dir,`package.json`)))??fail(`package "${name}" is not installed (searched from "${process.cwd()}")`)};function resolveFile(source){if(typeof source===`string`||source instanceof URL)return existingFile(toPath(source,`source`));if(!isRecord(source))return fail(`unsupported source ${show(source)}; expected a path, a file: URL, { file }, { package, file } or { module, file }`);const{file,package:pkg,module}=source;if(pkg!==void 0&&module!==void 0)fail(`a source may set "package" or "module", not both`);if(pkg!==void 0){const root=packageRoot(text(pkg,`package`));const path=resolve(root,text(file,`file`));return relative(root,path).split(sep)[0]===`..`?fail(`"${String(file)}" must point to a file inside package "${pkg}"`):existingFile(path)}if(module===void 0)return existingFile(toPath(file,`file`));const anchor=toPath(module,`module`);return existingFile(resolve(statSync(anchor,{throwIfNoEntry:false})?.isDirectory()?anchor:dirname(anchor),text(file,`file`)))}const containerName=name=>{const value=text(name,`name`);return value.split(`/`).some(part=>[``,`.`,`..`].includes(part)||/[\\\0]/u.test(part))?fail(`name "${value}" must be a relative path using "/" separators, without empty, "." or ".." segments`):value};const inlineJson=json=>typeof json===`string`?json:JSON.stringify(json,null,2)??fail(`"json" must be a JSON string or an object`);const parseJson=json=>{try{return JSON.parse(json)}catch(error){return fail(`invalid JSON (${error.message})`)}};function stubIds(json){const value=parseJson(json);const many=isRecord(value)&&`mappings`in value;const stubs=many?value.mappings:[value];if(!Array.isArray(stubs)||stubs.length===0)fail(`"mappings" must be a non-empty array of stub mappings`);return stubs.flatMap((stub,index)=>{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)`);const id=stub.id??stub.uuid;return typeof id===`string`?[id]:[]})}const stubIdsOf=path=>{try{return path.endsWith(`.json`)?stubIds(readFileSync(path,`utf8`)):[]}catch{return[]}};const listFiles=dir=>readdirSync(dir,{recursive:true,withFileTypes:true}).filter(entry=>!entry.isDirectory()).map(entry=>relative(dir,join(entry.parentPath,entry.name)).split(sep).join(`/`));const MAPPINGS=`/home/wiremock/mappings`;const FILES=`/home/wiremock/__files`;const INLINE=`inline JSON`;var WiremockContainer=class extends GenericContainer{#origins=/* @__PURE__ */ new Map;#stubIds=/* @__PURE__ */ new Map;constructor(image=`wiremock/wiremock`){super(image);this.withExposedPorts(8080);this.withWaitStrategy(Wait.forListeningPorts())}withMappings(directory){within(`WiremockContainer.withMappings`,()=>{const mappings=join(directory,`mappings`);if(!existsSync(mappings))fail(`"${mappings}" does not exist`);if(!statSync(mappings).isDirectory())fail(`"${mappings}" is not a directory`);const files=join(directory,`__files`);const dirs=[{source:mappings,target:MAPPINGS},...existsSync(files)?[{source:files,target:FILES}]:[]];this.#claim(dirs.flatMap(({source,target})=>listFiles(source).map(name=>({target:posix.join(target,name),origin:resolve(source,name),ids:target===MAPPINGS?stubIdsOf(join(source,name)):[]}))));this.withCopyDirectoriesToContainer(dirs)});return this}withMapping(name,source){within(`WiremockContainer.withMapping("${name}")`,()=>{const file=containerName(name);const target=posix.join(MAPPINGS,file.endsWith(`.json`)?file:`${file}.json`);const inline=isRecord(source)&&`json`in source;const origin=inline?INLINE:resolveFile(source);const content=inline?inlineJson(source.json):readFileSync(origin);const ids=within(origin,()=>stubIds(content.toString()));this.withCopyContentToContainer(this.#claim([{target,origin,ids}]).map(()=>({content,target})))});return this}withFiles(...files){within(`WiremockContainer.withFiles`,()=>{const claims=files.map(file=>{const origin=resolveFile(file);const name=isRecord(file)&&file.name!==void 0?containerName(file.name):basename(origin);return{target:posix.join(FILES,name),origin}});this.withCopyFilesToContainer(this.#claim(claims).map(({origin,target})=>({source:origin,target})))});return this}async start(){const runtime=getRuntime();const startedContainer=runtime?await runtime.start(this.#startRequest()):await super.start();return new StartedWiremockContainer(startedContainer,`http://${startedContainer.getHost()}:${startedContainer.getMappedPort(8080)}`)}#startRequest(){return{image:this.imageName.string,name:this.createOpts.name,command:[...this.createOpts.Cmd??[]],environment:{...this.environment},directories:this.directoriesToCopy.map(({source,target})=>({source,target})),files:this.filesToCopy.map(({source,target})=>({source,target})),contents:this.contentsToCopy.map(({content,target})=>({content,target}))}}#claim(claims){const origins=new Map(this.#origins);const owners=new Map(this.#stubIds);const fresh=claims.filter(({target,origin,ids=[]})=>{const existing=origins.get(target);if(existing===origin&&origin!==INLINE)return false;if(existing)fail(`"${target}" is already registered from ${existing}; registering ${origin} would overwrite it`);for(const id of ids){const owner=owners.get(id);if(owner)fail(`stub id "${id}" in ${origin} is already used by "${owner}"; WireMock refuses to start with duplicate stub ids`);owners.set(id,target)}origins.set(target,origin);return true});[this.#origins,this.#stubIds]=[origins,owners];return fresh}};const ADMIN_PATH=`/__admin`;var StartedWiremockContainer=class extends AbstractStartedContainer{#rpcUrl;constructor(startedTestContainer,url){super(startedTestContainer);this.#rpcUrl=url}get rpcUrl(){return this.#rpcUrl}getUrl(path=``){if(path===``)return this.#rpcUrl;const suffix=path.startsWith(`/`)||path.startsWith(`?`)?path:`/${path}`;return`${this.#rpcUrl}${suffix}`}getAdminUrl(){return`${this.#rpcUrl}${ADMIN_PATH}`}getMappingUrl(){return`${this.getAdminUrl()}/mappings`}async isRunning(){try{const health=await fetch(`${this.getAdminUrl()}/health`);if(health.status!==404)return health.ok;return(await fetch(this.getMappingUrl())).ok}catch{return false}}async waitUntilReady(options={}){const{timeout=3e4,interval=250}=options;const deadline=Date.now()+timeout;const poll=async()=>{if(await this.isRunning())return;if(Date.now()>=deadline)throw new Error(`WireMock at ${this.#rpcUrl} was not ready within ${timeout}ms`);await new Promise(_resolve=>setTimeout(_resolve,interval));return poll()};await poll()}async reset(){await this.#admin(`POST`,`/reset`)}async resetRequests(){await this.#admin(`DELETE`,`/requests`)}async countRequests(request){const{count}=await(await this.#admin(`POST`,`/requests/count`,request)).json();return count}async findRequests(request){const{requests}=await(await this.#admin(`POST`,`/requests/find`,request)).json();return requests??[]}async#admin(method,path,body){const response=await fetch(`${this.getAdminUrl()}${path}`,{method,...body===void 0?{}:{headers:{"Content-Type":`application/json`},body:JSON.stringify(body)}});if(!response.ok){const detail=await response.text().catch(()=>``);throw new Error(`WireMock admin request ${method} ${ADMIN_PATH}${path} failed with ${response.status} ${response.statusText}${detail?`: ${detail}`:``}`)}return response}};export{StartedWiremockContainer,WiremockContainer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const require_runtime=require("./runtime-6blQsklJ.cjs");let node_fs=require("node:fs");let node_path=require("node:path");let node_module=require("node:module");let node_child_process=require("node:child_process");let node_crypto=require("node:crypto");let node_events=require("node:events");let node_net=require("node:net");let node_os=require("node:os");let node_stream=require("node:stream");let node_stream_consumers=require("node:stream/consumers");const NO_DOCKER_ENV=`WIREMOCK_NO_DOCKER`;const CI_VARIABLES=[`CI`,`CONTINUOUS_INTEGRATION`,`GITHUB_ACTIONS`,`GITLAB_CI`,`CIRCLECI`,`BUILDKITE`,`JENKINS_URL`,`TF_BUILD`,`TEAMCITY_VERSION`,`BITBUCKET_BUILD_NUMBER`,`CODEBUILD_BUILD_ID`];const WIREMOCK_HOME=`/home/wiremock`;const WIREMOCK_PORT=8080;const HOST=`127.0.0.1`;const POLL_INTERVAL=25;const FAST_START=[`-XX:+IgnoreUnrecognizedVMOptions`,`-XX:TieredStopAtLevel=1`];const GRACEFUL_EXIT=143;const STALE_RECORDING=36e5;function detectCI(env=process.env){return CI_VARIABLES.find(name=>{const value=env[name]?.trim().toLowerCase();return value!==void 0&&value!==``&&value!==`false`&&value!==`0`})}function isNoDockerRequested(env=process.env){const value=env[NO_DOCKER_ENV]?.trim().toLowerCase();return value===`1`||value===`true`}function installNoDockerRuntime(options={}){const env=options.env??process.env;const ci=detectCI(env);if(ci)throw new Error(`--no-docker must not be used in CI/CD (detected ${ci}=${env[ci]}): it does not run WireMock in a container. Run the E2E suite with Docker instead.`);const runtime=new NoDockerRuntime({jar:resolveJar(options.jar??env.WIREMOCK_JAR),java:options.java??env.WIREMOCK_JAVA??(env.JAVA_HOME?(0,node_path.join)(env.JAVA_HOME,`bin`,`java`):`java`),startupTimeout:options.startupTimeout??6e4,cacheDir:options.cacheDir??(0,node_path.join)((0,node_os.tmpdir)(),`wiremock-testcontainers`),log:options.log??(message=>process.emitWarning(message,{code:`WIREMOCK_NO_DOCKER`}))});require_runtime.n(runtime);return()=>{if(require_runtime.t()===runtime)require_runtime.n(void 0)}}function resolveJar(explicit){if(explicit){const jar=(0,node_path.resolve)(explicit);if(!(0,node_fs.existsSync)(jar))throw new Error(`--no-docker: WireMock JAR "${jar}" does not exist`);return jar}let build;try{build=(0,node_path.join)((0,node_path.dirname)((0,node_module.createRequire)((0,node_path.join)(process.cwd(),`noop.js`)).resolve(`wiremock/package.json`)),`build`)}catch{throw new 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')}const name=(0,node_fs.existsSync)(build)?(0,node_fs.readdirSync)(build).find(file=>/^wiremock-standalone-.+\.jar$/u.test(file)):void 0;if(!name)throw new Error(`--no-docker: the "wiremock" npm package contains no wiremock-standalone-*.jar in "${build}"`);return(0,node_path.join)(build,name)}function classData(cacheDir,jar,java){if(cacheDir===false)return void 0;try{(0,node_fs.mkdirSync)(cacheDir,{recursive:true});const{size,mtimeMs}=(0,node_fs.statSync)(jar);const key=(0,node_crypto.createHash)(`sha256`).update(`${jar}\0${size}\0${mtimeMs}\0${java}`).digest(`hex`).slice(0,16);const path=(0,node_path.join)(cacheDir,`wiremock-${key}.jsa`);if((0,node_fs.existsSync)(path))return{path};for(const file of(0,node_fs.readdirSync)(cacheDir)){const stale=(0,node_path.join)(cacheDir,file);if(file.endsWith(`.tmp`)&&Date.now()-(0,node_fs.statSync)(stale).mtimeMs>STALE_RECORDING)(0,node_fs.rmSync)(stale,{force:true})}return{path,recording:`${path}.${(0,node_crypto.randomUUID)()}.tmp`}}catch{return}}var NoDockerRuntime=class{#config;constructor(config){this.#config=config}async start(request){const root=(0,node_fs.mkdtempSync)((0,node_path.join)((0,node_os.tmpdir)(),`wiremock-no-docker-`));try{await materialize(request,root);const home=(0,node_path.join)(root,WIREMOCK_HOME);for(const dir of[`mappings`,`__files`])(0,node_fs.mkdirSync)((0,node_path.join)(home,dir),{recursive:true});const container=new NoDockerContainer(request,this.#config,root,home,await freePort());await container.launch();this.#config.log(container.describe());return container}catch(error){(0,node_fs.rmSync)(root,{recursive:true,force:true});throw error}}};async function materialize(request,root){const at=target=>{const path=(0,node_path.join)(root,target);(0,node_fs.mkdirSync)((0,node_path.dirname)(path),{recursive:true});return path};for(const{source,target}of request.directories)(0,node_fs.cpSync)(source,at(target),{recursive:true});for(const{source,target}of request.files)(0,node_fs.copyFileSync)(source,at(target));const contents=await Promise.all(request.contents.map(async({content,target})=>({target,bytes:content instanceof node_stream.Readable?await(0,node_stream_consumers.buffer)(content):content})));for(const{target,bytes}of contents)(0,node_fs.writeFileSync)(at(target),bytes)}function freePort(){return new Promise((_resolve,_reject)=>{const server=(0,node_net.createServer)();server.once(`error`,_reject);server.listen(0,HOST,()=>{const{port}=server.address();server.close(()=>_resolve(port))})})}async function isHealthy(url){try{const health=await fetch(`${url}/__admin/health`);if(health.status!==404)return health.ok;return(await fetch(`${url}/__admin/mappings`)).ok}catch{return false}}const split=value=>value?.split(/\s+/u).filter(Boolean)??[];const live=/* @__PURE__ */ new Set;let exitHookInstalled=false;const killOnExit=child=>{live.add(child);if(exitHookInstalled)return;exitHookInstalled=true;process.once(`exit`,()=>{for(const process of live)process.kill(`SIGKILL`)})};const unsupported=method=>/* @__PURE__ */ new Error(`${method}() is not supported with --no-docker: WireMock runs as a local JVM process, not in a container`);var NoDockerContainer=class{#request;#config;#root;#home;#port;#id=`no-docker-${(0,node_crypto.randomUUID)()}`;#child;#classData;#spawnError;#output=``;constructor(request,config,root,home,port){this.#request=request;this.#config=config;this.#root=root;this.#home=home;this.#port=port}async launch(){const{environment,command}=this.#request;const data=this.#classData=classData(this.#config.cacheDir,this.#config.jar,this.#config.java);const args=[...FAST_START,...data?[data.recording?`-XX:ArchiveClassesAtExit=${data.recording}`:`-XX:SharedArchiveFile=${data.path}`]:[],...split(environment.JAVA_OPTS),`-jar`,this.#config.jar,`--port`,String(this.#port),`--bind-address`,HOST,`--root-dir`,this.#home,...command,...split(environment.WIREMOCK_OPTIONS)];this.#output=``;this.#spawnError=void 0;const child=(0,node_child_process.spawn)(this.#config.java,args,{env:{...process.env,...environment},stdio:[`ignore`,`pipe`,`pipe`]});this.#child=child;child.once(`error`,error=>{this.#spawnError=error});const record=chunk=>{this.#output=(this.#output+chunk.toString()).slice(-65536)};child.stdout?.on(`data`,record);child.stderr?.on(`data`,record);killOnExit(child);try{await this.#waitUntilHealthy(child)}catch(error){await this.#terminate();throw error}if(data&&!data.recording&&/\[warning\]\[cds/u.test(this.#output))(0,node_fs.rmSync)(data.path,{force:true})}async#waitUntilHealthy(child){const deadline=Date.now()+this.#config.startupTimeout;const output=()=>this.#output.trim()?`\n--- WireMock output ---\n${this.#output.trim()}`:``;const poll=async()=>{if(this.#spawnError){const missing=this.#spawnError.code===`ENOENT`;throw new Error(missing?`--no-docker: Java executable "${this.#config.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.#config.java}": ${this.#spawnError.message}`,{cause:this.#spawnError})}if(child.exitCode!==null||child.signalCode!==null)throw new Error(`--no-docker: WireMock exited during start-up (${child.signalCode??`exit code ${child.exitCode}`})${output()}`);if(await isHealthy(this.#url))return;if(Date.now()>=deadline)throw new Error(`--no-docker: WireMock was not healthy within ${this.#config.startupTimeout}ms${output()}`);await new Promise(_resolve=>setTimeout(_resolve,POLL_INTERVAL));return poll()};await poll()}async#terminate(){const child=this.#child;const recording=this.#classData?.recording;live.delete(child);if(child?.pid&&child.exitCode===null&&child.signalCode===null){const exited=(0,node_events.once)(child,`exit`);child.kill(recording?`SIGTERM`:`SIGKILL`);const timer=setTimeout(()=>child.kill(`SIGKILL`),1e4);await exited;clearTimeout(timer)}if(!recording)return;try{if(child?.exitCode===GRACEFUL_EXIT&&(0,node_fs.existsSync)(recording))(0,node_fs.renameSync)(recording,this.#classData.path)}catch{}(0,node_fs.rmSync)(recording,{force:true});this.#classData=void 0}get#url(){return`http://${HOST}:${this.#port}`}describe(){const version=/wiremock-standalone-(.+)\.jar$/u.exec(this.#config.jar)?.[1]??`unknown`;const{image}=this.#request;const tag=image.slice(image.lastIndexOf(`:`)+1);const pinned=/^(\d+\.\d+\.\d+)/u.exec(tag)?.[1];const mismatch=pinned&&pinned!==version?` The image tag "${tag}" differs from the JAR version ${version}.`:``;return`--no-docker: "${this.#request.image}" is NOT running in a container – WireMock ${version} standalone (local JVM, pid ${this.#child?.pid}) serves ${this.#url} instead.${mismatch} Local development only; never use --no-docker in CI/CD.`}async stop(){await this.#terminate();(0,node_fs.rmSync)(this.#root,{recursive:true,force:true});const id=this.#id;return{getId:()=>id,copyArchiveFromContainer:async()=>Promise.reject(unsupported(`copyArchiveFromContainer`))}}async restart(){await this.#terminate();await this.launch()}async[Symbol.asyncDispose](){await this.stop()}getHost(){return HOST}getHostname(){return this.getName()}getFirstMappedPort(){return this.#port}getMappedPort(port,protocol=`tcp`){const key=typeof port===`number`?`${port}/${protocol}`:port;if(key!==`${WIREMOCK_PORT}/tcp`)throw new Error(`No port mapping found for "${key}"`);return this.#port}getName(){return this.#request.name??`wiremock-no-docker-${this.#port}`}getLabels(){return{}}getId(){return this.#id}getNetworkNames(){return[]}async logs(){return node_stream.Readable.from([this.#output])}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`)}};exports.NO_DOCKER_ENV=NO_DOCKER_ENV;exports.detectCI=detectCI;exports.installNoDockerRuntime=installNoDockerRuntime;exports.isNoDockerRequested=isNoDockerRequested;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--no-docker` harness – **local development only**: serves every `WiremockContainer` from a
|
|
3
|
+
* local WireMock standalone JVM instead of Docker. Refuses to run in CI/CD and warns on every
|
|
4
|
+
* start. Needs Java 11+ and a WireMock JAR (e.g. the `wiremock` npm package).
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* // Vitest/Jest setup file, registered only when `WIREMOCK_NO_DOCKER=1`
|
|
8
|
+
* import { installNoDockerRuntime } from "@hellaweb3/wiremock-testcontainers-nodejs/no-docker";
|
|
9
|
+
* installNoDockerRuntime();
|
|
10
|
+
* ```
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/** Set to `1` by the `--no-docker` flag. */
|
|
14
|
+
export declare const NO_DOCKER_ENV = "WIREMOCK_NO_DOCKER";
|
|
15
|
+
type Environment = Readonly<Record<string, string | undefined>>;
|
|
16
|
+
/** Options for {@link installNoDockerRuntime}. */
|
|
17
|
+
export interface NoDockerOptions {
|
|
18
|
+
/** WireMock standalone JAR. Default: `$WIREMOCK_JAR`, else the `wiremock` npm package's. */
|
|
19
|
+
readonly jar?: string;
|
|
20
|
+
/** Java executable. Default: `$WIREMOCK_JAVA`, `$JAVA_HOME/bin/java`, else `java`. */
|
|
21
|
+
readonly java?: string;
|
|
22
|
+
/** Milliseconds to wait for WireMock to be healthy. Default `60_000`. */
|
|
23
|
+
readonly startupTimeout?: number;
|
|
24
|
+
/** Receives the per-start warning. Default: `process.emitWarning`. */
|
|
25
|
+
readonly log?: (message: string) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Cache for the class-data archive that halves JVM start-up after the first run; `false`
|
|
28
|
+
* disables it. Default: `<os tmpdir>/wiremock-testcontainers`.
|
|
29
|
+
*/
|
|
30
|
+
readonly cacheDir?: string | false;
|
|
31
|
+
/** Source of the CI/CD check and the defaults above. Default: `process.env`. */
|
|
32
|
+
readonly env?: Environment;
|
|
33
|
+
}
|
|
34
|
+
/** The CI/CD variable set in `env`, if any. */
|
|
35
|
+
export declare function detectCI(env?: Environment): string | undefined;
|
|
36
|
+
/** Whether {@link NO_DOCKER_ENV} is `1` or `true`. */
|
|
37
|
+
export declare function isNoDockerRequested(env?: Environment): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Serves every `WiremockContainer` started from now on from a local WireMock JVM: its mappings,
|
|
40
|
+
* files and command are materialised into a temp directory, WireMock listens on a free port of
|
|
41
|
+
* `127.0.0.1`. Container-only features (`exec`, networks, …) throw.
|
|
42
|
+
* @returns A function that uninstalls it.
|
|
43
|
+
* @throws In CI/CD, or if no WireMock JAR is found.
|
|
44
|
+
*/
|
|
45
|
+
export declare function installNoDockerRuntime(options?: NoDockerOptions): () => void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--no-docker` harness – **local development only**: serves every `WiremockContainer` from a
|
|
3
|
+
* local WireMock standalone JVM instead of Docker. Refuses to run in CI/CD and warns on every
|
|
4
|
+
* start. Needs Java 11+ and a WireMock JAR (e.g. the `wiremock` npm package).
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* // Vitest/Jest setup file, registered only when `WIREMOCK_NO_DOCKER=1`
|
|
8
|
+
* import { installNoDockerRuntime } from "@hellaweb3/wiremock-testcontainers-nodejs/no-docker";
|
|
9
|
+
* installNoDockerRuntime();
|
|
10
|
+
* ```
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/** Set to `1` by the `--no-docker` flag. */
|
|
14
|
+
export declare const NO_DOCKER_ENV = "WIREMOCK_NO_DOCKER";
|
|
15
|
+
type Environment = Readonly<Record<string, string | undefined>>;
|
|
16
|
+
/** Options for {@link installNoDockerRuntime}. */
|
|
17
|
+
export interface NoDockerOptions {
|
|
18
|
+
/** WireMock standalone JAR. Default: `$WIREMOCK_JAR`, else the `wiremock` npm package's. */
|
|
19
|
+
readonly jar?: string;
|
|
20
|
+
/** Java executable. Default: `$WIREMOCK_JAVA`, `$JAVA_HOME/bin/java`, else `java`. */
|
|
21
|
+
readonly java?: string;
|
|
22
|
+
/** Milliseconds to wait for WireMock to be healthy. Default `60_000`. */
|
|
23
|
+
readonly startupTimeout?: number;
|
|
24
|
+
/** Receives the per-start warning. Default: `process.emitWarning`. */
|
|
25
|
+
readonly log?: (message: string) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Cache for the class-data archive that halves JVM start-up after the first run; `false`
|
|
28
|
+
* disables it. Default: `<os tmpdir>/wiremock-testcontainers`.
|
|
29
|
+
*/
|
|
30
|
+
readonly cacheDir?: string | false;
|
|
31
|
+
/** Source of the CI/CD check and the defaults above. Default: `process.env`. */
|
|
32
|
+
readonly env?: Environment;
|
|
33
|
+
}
|
|
34
|
+
/** The CI/CD variable set in `env`, if any. */
|
|
35
|
+
export declare function detectCI(env?: Environment): string | undefined;
|
|
36
|
+
/** Whether {@link NO_DOCKER_ENV} is `1` or `true`. */
|
|
37
|
+
export declare function isNoDockerRequested(env?: Environment): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Serves every `WiremockContainer` started from now on from a local WireMock JVM: its mappings,
|
|
40
|
+
* files and command are materialised into a temp directory, WireMock listens on a free port of
|
|
41
|
+
* `127.0.0.1`. Container-only features (`exec`, networks, …) throw.
|
|
42
|
+
* @returns A function that uninstalls it.
|
|
43
|
+
* @throws In CI/CD, or if no WireMock JAR is found.
|
|
44
|
+
*/
|
|
45
|
+
export declare function installNoDockerRuntime(options?: NoDockerOptions): () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as setRuntime,t as getRuntime}from"./runtime-ChyDTLYb.js";import{createRequire}from"node:module";import{copyFileSync,cpSync,existsSync,mkdirSync,mkdtempSync,readdirSync,renameSync,rmSync,statSync,writeFileSync}from"node:fs";import{dirname,join,resolve}from"node:path";import{spawn}from"node:child_process";import{createHash,randomUUID}from"node:crypto";import{once}from"node:events";import{createServer}from"node:net";import{tmpdir}from"node:os";import{Readable}from"node:stream";import{buffer}from"node:stream/consumers";const NO_DOCKER_ENV=`WIREMOCK_NO_DOCKER`;const CI_VARIABLES=[`CI`,`CONTINUOUS_INTEGRATION`,`GITHUB_ACTIONS`,`GITLAB_CI`,`CIRCLECI`,`BUILDKITE`,`JENKINS_URL`,`TF_BUILD`,`TEAMCITY_VERSION`,`BITBUCKET_BUILD_NUMBER`,`CODEBUILD_BUILD_ID`];const WIREMOCK_HOME=`/home/wiremock`;const WIREMOCK_PORT=8080;const HOST=`127.0.0.1`;const POLL_INTERVAL=25;const FAST_START=[`-XX:+IgnoreUnrecognizedVMOptions`,`-XX:TieredStopAtLevel=1`];const GRACEFUL_EXIT=143;const STALE_RECORDING=36e5;function detectCI(env=process.env){return CI_VARIABLES.find(name=>{const value=env[name]?.trim().toLowerCase();return value!==void 0&&value!==``&&value!==`false`&&value!==`0`})}function isNoDockerRequested(env=process.env){const value=env[NO_DOCKER_ENV]?.trim().toLowerCase();return value===`1`||value===`true`}function installNoDockerRuntime(options={}){const env=options.env??process.env;const ci=detectCI(env);if(ci)throw new Error(`--no-docker must not be used in CI/CD (detected ${ci}=${env[ci]}): it does not run WireMock in a container. Run the E2E suite with Docker instead.`);const runtime=new NoDockerRuntime({jar:resolveJar(options.jar??env.WIREMOCK_JAR),java:options.java??env.WIREMOCK_JAVA??(env.JAVA_HOME?join(env.JAVA_HOME,`bin`,`java`):`java`),startupTimeout:options.startupTimeout??6e4,cacheDir:options.cacheDir??join(tmpdir(),`wiremock-testcontainers`),log:options.log??(message=>process.emitWarning(message,{code:`WIREMOCK_NO_DOCKER`}))});setRuntime(runtime);return()=>{if(getRuntime()===runtime)setRuntime(void 0)}}function resolveJar(explicit){if(explicit){const jar=resolve(explicit);if(!existsSync(jar))throw new Error(`--no-docker: WireMock JAR "${jar}" does not exist`);return jar}let build;try{build=join(dirname(createRequire(join(process.cwd(),`noop.js`)).resolve(`wiremock/package.json`)),`build`)}catch{throw new 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')}const name=existsSync(build)?readdirSync(build).find(file=>/^wiremock-standalone-.+\.jar$/u.test(file)):void 0;if(!name)throw new Error(`--no-docker: the "wiremock" npm package contains no wiremock-standalone-*.jar in "${build}"`);return join(build,name)}function classData(cacheDir,jar,java){if(cacheDir===false)return void 0;try{mkdirSync(cacheDir,{recursive:true});const{size,mtimeMs}=statSync(jar);const key=createHash(`sha256`).update(`${jar}\0${size}\0${mtimeMs}\0${java}`).digest(`hex`).slice(0,16);const path=join(cacheDir,`wiremock-${key}.jsa`);if(existsSync(path))return{path};for(const file of readdirSync(cacheDir)){const stale=join(cacheDir,file);if(file.endsWith(`.tmp`)&&Date.now()-statSync(stale).mtimeMs>STALE_RECORDING)rmSync(stale,{force:true})}return{path,recording:`${path}.${randomUUID()}.tmp`}}catch{return}}var NoDockerRuntime=class{#config;constructor(config){this.#config=config}async start(request){const root=mkdtempSync(join(tmpdir(),`wiremock-no-docker-`));try{await materialize(request,root);const home=join(root,WIREMOCK_HOME);for(const dir of[`mappings`,`__files`])mkdirSync(join(home,dir),{recursive:true});const container=new NoDockerContainer(request,this.#config,root,home,await freePort());await container.launch();this.#config.log(container.describe());return container}catch(error){rmSync(root,{recursive:true,force:true});throw error}}};async function materialize(request,root){const at=target=>{const path=join(root,target);mkdirSync(dirname(path),{recursive:true});return path};for(const{source,target}of request.directories)cpSync(source,at(target),{recursive:true});for(const{source,target}of request.files)copyFileSync(source,at(target));const contents=await Promise.all(request.contents.map(async({content,target})=>({target,bytes:content instanceof Readable?await buffer(content):content})));for(const{target,bytes}of contents)writeFileSync(at(target),bytes)}function freePort(){return new Promise((_resolve,_reject)=>{const server=createServer();server.once(`error`,_reject);server.listen(0,HOST,()=>{const{port}=server.address();server.close(()=>_resolve(port))})})}async function isHealthy(url){try{const health=await fetch(`${url}/__admin/health`);if(health.status!==404)return health.ok;return(await fetch(`${url}/__admin/mappings`)).ok}catch{return false}}const split=value=>value?.split(/\s+/u).filter(Boolean)??[];const live=/* @__PURE__ */ new Set;let exitHookInstalled=false;const killOnExit=child=>{live.add(child);if(exitHookInstalled)return;exitHookInstalled=true;process.once(`exit`,()=>{for(const process of live)process.kill(`SIGKILL`)})};const unsupported=method=>/* @__PURE__ */ new Error(`${method}() is not supported with --no-docker: WireMock runs as a local JVM process, not in a container`);var NoDockerContainer=class{#request;#config;#root;#home;#port;#id=`no-docker-${randomUUID()}`;#child;#classData;#spawnError;#output=``;constructor(request,config,root,home,port){this.#request=request;this.#config=config;this.#root=root;this.#home=home;this.#port=port}async launch(){const{environment,command}=this.#request;const data=this.#classData=classData(this.#config.cacheDir,this.#config.jar,this.#config.java);const args=[...FAST_START,...data?[data.recording?`-XX:ArchiveClassesAtExit=${data.recording}`:`-XX:SharedArchiveFile=${data.path}`]:[],...split(environment.JAVA_OPTS),`-jar`,this.#config.jar,`--port`,String(this.#port),`--bind-address`,HOST,`--root-dir`,this.#home,...command,...split(environment.WIREMOCK_OPTIONS)];this.#output=``;this.#spawnError=void 0;const child=spawn(this.#config.java,args,{env:{...process.env,...environment},stdio:[`ignore`,`pipe`,`pipe`]});this.#child=child;child.once(`error`,error=>{this.#spawnError=error});const record=chunk=>{this.#output=(this.#output+chunk.toString()).slice(-65536)};child.stdout?.on(`data`,record);child.stderr?.on(`data`,record);killOnExit(child);try{await this.#waitUntilHealthy(child)}catch(error){await this.#terminate();throw error}if(data&&!data.recording&&/\[warning\]\[cds/u.test(this.#output))rmSync(data.path,{force:true})}async#waitUntilHealthy(child){const deadline=Date.now()+this.#config.startupTimeout;const output=()=>this.#output.trim()?`\n--- WireMock output ---\n${this.#output.trim()}`:``;const poll=async()=>{if(this.#spawnError){const missing=this.#spawnError.code===`ENOENT`;throw new Error(missing?`--no-docker: Java executable "${this.#config.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.#config.java}": ${this.#spawnError.message}`,{cause:this.#spawnError})}if(child.exitCode!==null||child.signalCode!==null)throw new Error(`--no-docker: WireMock exited during start-up (${child.signalCode??`exit code ${child.exitCode}`})${output()}`);if(await isHealthy(this.#url))return;if(Date.now()>=deadline)throw new Error(`--no-docker: WireMock was not healthy within ${this.#config.startupTimeout}ms${output()}`);await new Promise(_resolve=>setTimeout(_resolve,POLL_INTERVAL));return poll()};await poll()}async#terminate(){const child=this.#child;const recording=this.#classData?.recording;live.delete(child);if(child?.pid&&child.exitCode===null&&child.signalCode===null){const exited=once(child,`exit`);child.kill(recording?`SIGTERM`:`SIGKILL`);const timer=setTimeout(()=>child.kill(`SIGKILL`),1e4);await exited;clearTimeout(timer)}if(!recording)return;try{if(child?.exitCode===GRACEFUL_EXIT&&existsSync(recording))renameSync(recording,this.#classData.path)}catch{}rmSync(recording,{force:true});this.#classData=void 0}get#url(){return`http://${HOST}:${this.#port}`}describe(){const version=/wiremock-standalone-(.+)\.jar$/u.exec(this.#config.jar)?.[1]??`unknown`;const{image}=this.#request;const tag=image.slice(image.lastIndexOf(`:`)+1);const pinned=/^(\d+\.\d+\.\d+)/u.exec(tag)?.[1];const mismatch=pinned&&pinned!==version?` The image tag "${tag}" differs from the JAR version ${version}.`:``;return`--no-docker: "${this.#request.image}" is NOT running in a container – WireMock ${version} standalone (local JVM, pid ${this.#child?.pid}) serves ${this.#url} instead.${mismatch} Local development only; never use --no-docker in CI/CD.`}async stop(){await this.#terminate();rmSync(this.#root,{recursive:true,force:true});const id=this.#id;return{getId:()=>id,copyArchiveFromContainer:async()=>Promise.reject(unsupported(`copyArchiveFromContainer`))}}async restart(){await this.#terminate();await this.launch()}async[Symbol.asyncDispose](){await this.stop()}getHost(){return HOST}getHostname(){return this.getName()}getFirstMappedPort(){return this.#port}getMappedPort(port,protocol=`tcp`){const key=typeof port===`number`?`${port}/${protocol}`:port;if(key!==`${WIREMOCK_PORT}/tcp`)throw new Error(`No port mapping found for "${key}"`);return this.#port}getName(){return this.#request.name??`wiremock-no-docker-${this.#port}`}getLabels(){return{}}getId(){return this.#id}getNetworkNames(){return[]}async logs(){return Readable.from([this.#output])}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`)}};export{NO_DOCKER_ENV,detectCI,installNoDockerRuntime,isNoDockerRequested};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const RUNTIME_KEY=Symbol.for(`@hellaweb3/wiremock-testcontainers-nodejs/runtime`);const registry=globalThis;const getRuntime=()=>registry[RUNTIME_KEY];const setRuntime=runtime=>{if(runtime)registry[RUNTIME_KEY]=runtime;else delete registry[RUNTIME_KEY]};Object.defineProperty(exports,"n",{enumerable:true,get:function(){return setRuntime}});Object.defineProperty(exports,"t",{enumerable:true,get:function(){return getRuntime}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const RUNTIME_KEY=Symbol.for(`@hellaweb3/wiremock-testcontainers-nodejs/runtime`);const registry=globalThis;const getRuntime=()=>registry[RUNTIME_KEY];const setRuntime=runtime=>{if(runtime)registry[RUNTIME_KEY]=runtime;else delete registry[RUNTIME_KEY]};export{setRuntime as n,getRuntime as t};
|
package/package.json
CHANGED
|
@@ -14,18 +14,14 @@
|
|
|
14
14
|
"mock",
|
|
15
15
|
"api"
|
|
16
16
|
],
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.3.0",
|
|
18
18
|
"publishConfig": {
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "Chris Anatalio",
|
|
23
23
|
"files": [
|
|
24
|
-
"dist"
|
|
25
|
-
"!dist/*.js.map",
|
|
26
|
-
"src",
|
|
27
|
-
"README.md",
|
|
28
|
-
"LICENSE"
|
|
24
|
+
"dist"
|
|
29
25
|
],
|
|
30
26
|
"type": "module",
|
|
31
27
|
"sideEffects": false,
|
|
@@ -43,20 +39,39 @@
|
|
|
43
39
|
"default": "./dist/index.cjs"
|
|
44
40
|
}
|
|
45
41
|
},
|
|
42
|
+
"./no-docker": {
|
|
43
|
+
"import": {
|
|
44
|
+
"types": "./dist/no-docker.d.ts",
|
|
45
|
+
"default": "./dist/no-docker.js"
|
|
46
|
+
},
|
|
47
|
+
"require": {
|
|
48
|
+
"types": "./dist/no-docker.d.cts",
|
|
49
|
+
"default": "./dist/no-docker.cjs"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
46
52
|
"./package.json": "./package.json"
|
|
47
53
|
},
|
|
48
54
|
"dependencies": {
|
|
49
|
-
"testcontainers": "^12.2.
|
|
55
|
+
"testcontainers": "^12.2.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"wiremock": ">=3.0.0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"wiremock": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
50
64
|
},
|
|
51
65
|
"devDependencies": {
|
|
52
66
|
"@arethetypeswrong/core": "^0.18.5",
|
|
53
67
|
"@types/node": "^26.6.6",
|
|
54
68
|
"@vitest/coverage-v8": "^5.1.0",
|
|
55
|
-
"oxlint": "^1.
|
|
69
|
+
"oxlint": "^1.92.0",
|
|
56
70
|
"publint": "^0.3.25",
|
|
57
|
-
"tsdown": "
|
|
71
|
+
"tsdown": "0.23.0",
|
|
58
72
|
"typescript": "^7.0.2",
|
|
59
|
-
"vitest": "^5.1.0"
|
|
73
|
+
"vitest": "^5.1.0",
|
|
74
|
+
"wiremock": "^3.13.2"
|
|
60
75
|
},
|
|
61
76
|
"engines": {
|
|
62
77
|
"node": ">=22.22"
|
|
@@ -66,10 +81,12 @@
|
|
|
66
81
|
"build": "tsdown",
|
|
67
82
|
"test": "vitest run",
|
|
68
83
|
"test:unit": "vitest run test/unit",
|
|
69
|
-
"test:e2e": "
|
|
84
|
+
"test:e2e": "node scripts/test-e2e.mjs",
|
|
70
85
|
"test:coverage": "vitest run --coverage",
|
|
71
86
|
"lint": "oxlint --deny-warnings",
|
|
72
87
|
"typecheck": "tsc --noEmit",
|
|
73
|
-
"check": "pnpm build && pnpm typecheck && pnpm lint && pnpm test:coverage"
|
|
88
|
+
"check": "pnpm build && pnpm typecheck && pnpm lint && pnpm test:coverage",
|
|
89
|
+
"examples:setup": "pnpm build && npm link && cd examples && npm install && npm run link:library",
|
|
90
|
+
"examples:e2e": "pnpm examples:setup && cd examples && npm run typecheck && npm test"
|
|
74
91
|
}
|
|
75
92
|
}
|
package/dist/index.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/sources.ts","../src/index.ts"],"mappings":";;KAMY,wBAAwB;;;;;;KAOxB,qBACR;WACW,MAAM;;WACN;WAA0B;;WAC1B,QAAQ;WAAuB;;;UAG7B;WACN;WACA;;;;KAKC,wBACR;WACW,eAAe;aAAiC,mBAAmB;;;;KAGtE,qBAAqB,gBAAgB,QAAQ,oBAAoB;WAA2B;;;;;;;;;;;;qBCoB3F,0BAA0B;;;;;;;;;;EAcrC,YAAY;;;;;;;;;;;;EAiBZ,aAAoB;;;;;;;;;;;;;;EAmCpB,YAAmB,cAAc,QAAQ;;;;;;;;;EAqBzC,aAAoB,OAAO;;;;;WAgBL,SAAS,QAAQ;;;;;qBAmC5B,iCAAiC;;;;;;;EAQ5C,YAAY,sBAAsB,sBAAsB;;;;MAQpD"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/sources.ts","../src/index.ts"],"mappings":";;KAMY,wBAAwB;;;;;;KAOxB,qBACR;WACW,MAAM;;WACN;WAA0B;;WAC1B,QAAQ;WAAuB;;;UAG7B;WACN;WACA;;;;KAKC,wBACR;WACW,eAAe;aAAiC,mBAAmB;;;;KAGtE,qBAAqB,gBAAgB,QAAQ,oBAAoB;WAA2B;;;;;;;;;;;;qBCoB3F,0BAA0B;;;;;;;;;;EAcrC,YAAY;;;;;;;;;;;;EAiBZ,aAAoB;;;;;;;;;;;;;;EAmCpB,YAAmB,cAAc,QAAQ;;;;;;;;;EAqBzC,aAAoB,OAAO;;;;;WAgBL,SAAS,QAAQ;;;;;qBAmC5B,iCAAiC;;;;;;;EAQ5C,YAAY,sBAAsB,sBAAsB;;;;MAQpD"}
|
package/src/index.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { basename, join, posix, resolve } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
AbstractStartedContainer,
|
|
5
|
-
GenericContainer,
|
|
6
|
-
type StartedTestContainer,
|
|
7
|
-
Wait,
|
|
8
|
-
} from "testcontainers";
|
|
9
|
-
import {
|
|
10
|
-
containerName,
|
|
11
|
-
fail,
|
|
12
|
-
inlineJson,
|
|
13
|
-
isRecord,
|
|
14
|
-
listFiles,
|
|
15
|
-
resolveFile,
|
|
16
|
-
stubIds,
|
|
17
|
-
stubIdsOf,
|
|
18
|
-
within,
|
|
19
|
-
type WiremockMappingSource,
|
|
20
|
-
type WiremockStaticFile,
|
|
21
|
-
} from "./sources";
|
|
22
|
-
|
|
23
|
-
export type {
|
|
24
|
-
WiremockFileSource,
|
|
25
|
-
WiremockMappingSource,
|
|
26
|
-
WiremockPath,
|
|
27
|
-
WiremockStaticFile,
|
|
28
|
-
WiremockStubMapping,
|
|
29
|
-
} from "./sources";
|
|
30
|
-
|
|
31
|
-
const MAPPINGS = "/home/wiremock/mappings";
|
|
32
|
-
const FILES = "/home/wiremock/__files";
|
|
33
|
-
const INLINE = "inline JSON";
|
|
34
|
-
|
|
35
|
-
/** A file a registration places in the container. */
|
|
36
|
-
interface Claim {
|
|
37
|
-
readonly target: string;
|
|
38
|
-
/** The absolute source path, or {@link INLINE}. */
|
|
39
|
-
readonly origin: string;
|
|
40
|
-
readonly ids?: readonly string[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* A Testcontainer for Wiremock. Stubs are registered with {@link withMappings} (a directory),
|
|
45
|
-
* {@link withMapping} (one named stub) and {@link withFiles} (response bodies), in any
|
|
46
|
-
* combination. Registrations are validated immediately and may not overwrite one another.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```typescript
|
|
50
|
-
* const container = await new WiremockContainer().start();
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
export class WiremockContainer extends GenericContainer {
|
|
54
|
-
/** Container path → origin of every registered file. */
|
|
55
|
-
#origins = new Map<string, string>();
|
|
56
|
-
/** Stub id → container path declaring it: WireMock refuses to start with duplicates. */
|
|
57
|
-
#stubIds = new Map<string, string>();
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Creates a new WiremockContainer.
|
|
61
|
-
* @example
|
|
62
|
-
* ```typescript
|
|
63
|
-
* const container = await new WiremockContainer().start();
|
|
64
|
-
* ```
|
|
65
|
-
* @param image The docker image to use. Defaults to "wiremock/wiremock".
|
|
66
|
-
*/
|
|
67
|
-
constructor(image: string = "wiremock/wiremock") {
|
|
68
|
-
super(image);
|
|
69
|
-
this.withExposedPorts(8080);
|
|
70
|
-
this.withWaitStrategy(Wait.forListeningPorts());
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Adds mappings to the Wiremock container.
|
|
75
|
-
* @example
|
|
76
|
-
* ```typescript
|
|
77
|
-
* const container = await new WiremockContainer()
|
|
78
|
-
* .withMappings("./test/__mocks__/wiremock")
|
|
79
|
-
* .start();
|
|
80
|
-
* ```
|
|
81
|
-
* @param directory The directory containing `mappings/` and, optionally, `__files/`.
|
|
82
|
-
* @throws If `mappings/` is not a directory in `directory`, or a file would overwrite an earlier registration.
|
|
83
|
-
*/
|
|
84
|
-
public withMappings(directory: string): this {
|
|
85
|
-
within("WiremockContainer.withMappings", () => {
|
|
86
|
-
const mappings = join(directory, "mappings");
|
|
87
|
-
if (!existsSync(mappings)) fail(`"${mappings}" does not exist`);
|
|
88
|
-
if (!statSync(mappings).isDirectory()) fail(`"${mappings}" is not a directory`);
|
|
89
|
-
// `__files/` is optional: mappings may define their response bodies inline.
|
|
90
|
-
const files = join(directory, "__files");
|
|
91
|
-
const dirs = [{ source: mappings, target: MAPPINGS }, ...(existsSync(files) ? [{ source: files, target: FILES }] : [])];
|
|
92
|
-
this.#claim(
|
|
93
|
-
dirs.flatMap(({ source, target }) =>
|
|
94
|
-
listFiles(source).map((name) => ({
|
|
95
|
-
target: posix.join(target, name),
|
|
96
|
-
origin: resolve(source, name),
|
|
97
|
-
ids: target === MAPPINGS ? stubIdsOf(join(source, name)) : [],
|
|
98
|
-
})),
|
|
99
|
-
),
|
|
100
|
-
);
|
|
101
|
-
this.withCopyDirectoriesToContainer(dirs);
|
|
102
|
-
});
|
|
103
|
-
return this;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Registers one stub at `mappings/<name>.json`, validated and copied byte-for-byte.
|
|
108
|
-
* @example
|
|
109
|
-
* ```typescript
|
|
110
|
-
* new WiremockContainer()
|
|
111
|
-
* .withMapping("cards", "./test/fixtures/cards.json")
|
|
112
|
-
* .withMapping("user", { package: "@acme/fixtures", file: "wiremock/user.json" })
|
|
113
|
-
* .withMapping("balance", { module: import.meta.url, file: "./balance.json" })
|
|
114
|
-
* .withMapping("ping", { json: { request: { url: "/ping" }, response: { status: 200 } } });
|
|
115
|
-
* ```
|
|
116
|
-
* @param name File name below `mappings/`, may contain `/`; `.json` is appended unless present.
|
|
117
|
-
* @throws If the name or source is invalid, the source is not a WireMock mapping, or it would overwrite an earlier registration.
|
|
118
|
-
*/
|
|
119
|
-
public withMapping(name: string, source: WiremockMappingSource): this {
|
|
120
|
-
within(`WiremockContainer.withMapping("${name}")`, () => {
|
|
121
|
-
const file = containerName(name);
|
|
122
|
-
const target = posix.join(MAPPINGS, file.endsWith(".json") ? file : `${file}.json`);
|
|
123
|
-
const inline = isRecord(source) && "json" in source;
|
|
124
|
-
const origin = inline ? INLINE : resolveFile(source);
|
|
125
|
-
const content = inline ? inlineJson(source.json) : readFileSync(origin);
|
|
126
|
-
const ids = within(origin, () => stubIds(content.toString()));
|
|
127
|
-
this.withCopyContentToContainer(this.#claim([{ target, origin, ids }]).map(() => ({ content, target })));
|
|
128
|
-
});
|
|
129
|
-
return this;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Registers response bodies at `__files/<name>` (default: the base name), copied as-is.
|
|
134
|
-
* @example
|
|
135
|
-
* ```typescript
|
|
136
|
-
* new WiremockContainer().withFiles("./test/fixtures/body.json", { file: "./logo.png", name: "img/logo.png" });
|
|
137
|
-
* ```
|
|
138
|
-
* @throws If a source or name is invalid, or a file would overwrite an earlier registration; nothing is registered then.
|
|
139
|
-
*/
|
|
140
|
-
public withFiles(...files: WiremockStaticFile[]): this {
|
|
141
|
-
within("WiremockContainer.withFiles", () => {
|
|
142
|
-
const claims = files.map((file) => {
|
|
143
|
-
const origin = resolveFile(file);
|
|
144
|
-
const name = isRecord(file) && file.name !== undefined ? containerName(file.name) : basename(origin);
|
|
145
|
-
return { target: posix.join(FILES, name), origin };
|
|
146
|
-
});
|
|
147
|
-
this.withCopyFilesToContainer(this.#claim(claims).map(({ origin, target }) => ({ source: origin, target })));
|
|
148
|
-
});
|
|
149
|
-
return this;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Starts the container and returns a {@link StartedWiremockContainer}.
|
|
154
|
-
* @returns A promise that resolves to the started container.
|
|
155
|
-
*/
|
|
156
|
-
public override async start(): Promise<StartedWiremockContainer> {
|
|
157
|
-
const startedContainer = await super.start();
|
|
158
|
-
return new StartedWiremockContainer(
|
|
159
|
-
startedContainer,
|
|
160
|
-
`http://${startedContainer.getHost()}:${startedContainer.getMappedPort(8080)}`,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Records `claims` – all or nothing – unless one would overwrite a file from another origin or
|
|
166
|
-
* reuse a stub id. Returns the claims not already registered from the same file.
|
|
167
|
-
*/
|
|
168
|
-
#claim<T extends Claim>(claims: readonly T[]): T[] {
|
|
169
|
-
const origins = new Map(this.#origins);
|
|
170
|
-
const owners = new Map(this.#stubIds);
|
|
171
|
-
const fresh = claims.filter(({ target, origin, ids = [] }) => {
|
|
172
|
-
const existing = origins.get(target);
|
|
173
|
-
if (existing === origin && origin !== INLINE) return false;
|
|
174
|
-
if (existing) fail(`"${target}" is already registered from ${existing}; registering ${origin} would overwrite it`);
|
|
175
|
-
for (const id of ids) {
|
|
176
|
-
const owner = owners.get(id);
|
|
177
|
-
if (owner) fail(`stub id "${id}" in ${origin} is already used by "${owner}"; WireMock refuses to start with duplicate stub ids`);
|
|
178
|
-
owners.set(id, target);
|
|
179
|
-
}
|
|
180
|
-
origins.set(target, origin);
|
|
181
|
-
return true;
|
|
182
|
-
});
|
|
183
|
-
[this.#origins, this.#stubIds] = [origins, owners];
|
|
184
|
-
return fresh;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* A started Wiremock container with helper methods for interacting with the node.
|
|
190
|
-
*/
|
|
191
|
-
export class StartedWiremockContainer extends AbstractStartedContainer {
|
|
192
|
-
readonly #rpcUrl: string;
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Creates a new StartedWiremockContainer.
|
|
196
|
-
* @param startedTestContainer The underlying TestContainer.
|
|
197
|
-
* @param url The URL of the started container.
|
|
198
|
-
*/
|
|
199
|
-
constructor(startedTestContainer: StartedTestContainer, url: string) {
|
|
200
|
-
super(startedTestContainer);
|
|
201
|
-
this.#rpcUrl = url;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Gets the URL of the Wiremock node.
|
|
206
|
-
*/
|
|
207
|
-
get rpcUrl(): string {
|
|
208
|
-
return this.#rpcUrl;
|
|
209
|
-
}
|
|
210
|
-
}
|