@interfere/test-utils 10.0.2 → 10.1.0-rc.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.
@@ -82,4 +82,25 @@ export declare function exceptions(requests: ReceivedRequest[], message: string)
82
82
  };
83
83
  }[];
84
84
  }[];
85
+ export declare function exportedLogRecords(requests: ReceivedRequest[]): {
86
+ eventName?: string | undefined;
87
+ attributes: {
88
+ key: string;
89
+ value: {
90
+ stringValue?: string | undefined;
91
+ boolValue?: boolean | undefined;
92
+ intValue?: string | number | undefined;
93
+ };
94
+ }[];
95
+ traceId?: string | undefined;
96
+ spanId?: string | undefined;
97
+ resource: {
98
+ key: string;
99
+ value: {
100
+ stringValue?: string | undefined;
101
+ boolValue?: boolean | undefined;
102
+ intValue?: string | number | undefined;
103
+ };
104
+ }[];
105
+ }[];
85
106
  //#endregion
@@ -1 +1 @@
1
- import{z}from"zod";const attributes=z.array(z.object({key:z.string(),value:z.object({stringValue:z.string().optional(),boolValue:z.boolean().optional(),intValue:z.union([z.string(),z.number()]).optional()})})),span=z.object({name:z.string(),traceId:z.string(),spanId:z.string(),parentSpanId:z.string().optional(),attributes:attributes.default([]),events:z.array(z.object({name:z.string(),attributes:attributes.default([])})).default([]),status:z.object({code:z.number().optional()}).optional()}),traces=z.object({resourceSpans:z.array(z.object({resource:z.object({attributes}),scopeSpans:z.array(z.object({spans:z.array(span)}))}))});function exportedSpans(requests){return requests.filter(request=>request.url.startsWith(`/drain/v1/traces`)&&request.status<300).flatMap(request=>traces.parse(JSON.parse(request.body)).resourceSpans.flatMap(resource=>resource.scopeSpans.flatMap(scope=>scope.spans.map(item=>({...item,resource:resource.resource.attributes})))))}function attribute(values,key){let value=values.find(item=>item.key===key)?.value;return value?.stringValue??value?.boolValue??value?.intValue}function exceptions(requests,message){return exportedSpans(requests).filter(item=>item.events.some(event=>event.name===`exception`&&attribute(event.attributes,`exception.message`)===message))}export{attribute,exceptions,exportedSpans};
1
+ import{z}from"zod";const attributes=z.array(z.object({key:z.string(),value:z.object({stringValue:z.string().optional(),boolValue:z.boolean().optional(),intValue:z.union([z.string(),z.number()]).optional()})})),span=z.object({name:z.string(),traceId:z.string(),spanId:z.string(),parentSpanId:z.string().optional(),attributes:attributes.default([]),events:z.array(z.object({name:z.string(),attributes:attributes.default([])})).default([]),status:z.object({code:z.number().optional()}).optional()}),traces=z.object({resourceSpans:z.array(z.object({resource:z.object({attributes}),scopeSpans:z.array(z.object({spans:z.array(span)}))}))});function exportedSpans(requests){return requests.filter(request=>request.url.startsWith(`/drain/v1/traces`)&&request.status<300).flatMap(request=>traces.parse(JSON.parse(request.body)).resourceSpans.flatMap(resource=>resource.scopeSpans.flatMap(scope=>scope.spans.map(item=>({...item,resource:resource.resource.attributes})))))}function attribute(values,key){let value=values.find(item=>item.key===key)?.value;return value?.stringValue??value?.boolValue??value?.intValue}function exceptions(requests,message){return exportedSpans(requests).filter(item=>item.events.some(event=>event.name===`exception`&&attribute(event.attributes,`exception.message`)===message))}const logs=z.object({resourceLogs:z.array(z.object({resource:z.object({attributes}),scopeLogs:z.array(z.object({logRecords:z.array(z.object({eventName:z.string().optional(),attributes:attributes.default([]),traceId:z.string().optional(),spanId:z.string().optional()}))}))}))});function exportedLogRecords(requests){return requests.filter(request=>request.url.startsWith(`/drain/v1/logs`)&&request.status<300).flatMap(request=>logs.parse(JSON.parse(request.body)).resourceLogs.flatMap(resource=>resource.scopeLogs.flatMap(scope=>scope.logRecords.map(item=>({...item,resource:resource.resource.attributes})))))}export{attribute,exceptions,exportedLogRecords,exportedSpans};
@@ -1,21 +1,30 @@
1
1
  //#region src/vitest/packed-application.d.ts
2
- export declare function application(packageRoot: string, packages: string[]): Promise<{
2
+ export type PackageSelection = string | {
3
+ as: string;
4
+ from: string;
5
+ };
6
+ export interface ApplicationOptions {
7
+ /** Pre-built tarballs by package name; a listed `@interfere/*` package is installed from that file instead of being packed from the workspace. */
8
+ readonly tarballs?: Readonly<Record<string, string>>;
9
+ }
10
+ export declare function application(packageRoot: string, packages: PackageSelection[], options?: ApplicationOptions): Promise<{
3
11
  root: string;
4
12
  copyFixture(name: string): Promise<void>;
5
13
  run(script: string, env?: Record<string, string | undefined>, runtime?: string): Promise<{
6
14
  stdout: string;
7
15
  stderr: string;
8
16
  }>;
9
- build(env: Record<string, string | undefined>): Promise<{
17
+ build(env: Record<string, string | undefined>, cwd?: string): Promise<{
10
18
  stdout: string;
11
19
  stderr: string;
12
20
  }>;
13
- buildNext(env: Record<string, string | undefined>, bundler: "webpack" | "turbopack"): Promise<{
21
+ buildNext(env: Record<string, string | undefined>, bundler?: "webpack" | "turbopack", cwd?: string): Promise<{
14
22
  stdout: string;
15
23
  stderr: string;
16
24
  }>;
17
- start(script: string, env: Record<string, string | undefined>): Promise<{
25
+ start(script: string, env: Record<string, string | undefined>, cwd?: string): Promise<{
18
26
  url: string;
27
+ output: () => string;
19
28
  close(): Promise<void>;
20
29
  }>;
21
30
  close(): Promise<void>;
@@ -1 +1 @@
1
- import{createRequire}from"node:module";import{z}from"zod";import{execFile,spawn}from"node:child_process";import{cp,mkdir,mkdtemp,readFile,realpath,rm,symlink,writeFile}from"node:fs/promises";import{tmpdir}from"node:os";import{dirname,join}from"node:path";import{promisify}from"node:util";import{once}from"node:events";const exec=promisify(execFile),metadata=z.object({name:z.string(),dependencies:z.record(z.string(),z.string()).default({})});function childEnvironment(overrides){return Object.fromEntries([[`PATH`,process.env.PATH],[`NODE_ENV`,`production`],[`NEXT_TELEMETRY_DISABLED`,`1`],...Object.entries(overrides)])}async function application(packageRoot,packages){let root=await mkdtemp(join(tmpdir(),`interfere-packed-application-`)),installed=new Set,require=createRequire(join(packageRoot,`package.json`));async function install(name,from){if(installed.has(name))return;installed.add(name);let modules=join(from,`node_modules`,name),source=metadata.parse(JSON.parse(await readFile(join(from,`package.json`),`utf8`))).name===name?from:await realpath(modules),manifest=metadata.parse(JSON.parse(await readFile(join(source,`package.json`),`utf8`))),destination=join(root,`node_modules`,name);if(await mkdir(dirname(destination),{recursive:!0}),!name.startsWith(`@interfere/`)){await symlink(source,destination,`dir`);return}let tarball=join(root,`${name.replaceAll(`/`,`-`)}.tgz`);await exec(`bun`,[`pm`,`pack`,`--ignore-scripts`,`--quiet`,`--filename`,tarball],{cwd:source}),await mkdir(destination,{recursive:!0}),await exec(`tar`,[`-xzf`,tarball,`--strip-components=1`,`-C`,destination]),await Promise.all(Object.keys(manifest.dependencies).map(async dependency=>install(dependency,source)))}try{return await writeFile(join(root,`package.json`),JSON.stringify({private:!0,type:`module`})),await Promise.all(packages.map(async name=>install(name,packageRoot))),{root,async copyFixture(name){await cp(join(packageRoot,`test/fixtures`,name),root,{recursive:!0})},async run(script,env={},runtime=process.execPath){return exec(runtime,[script],{cwd:root,timeout:3e4,maxBuffer:5242880,env:childEnvironment(env)})},async build(env){return exec(process.execPath,[join(dirname(require.resolve(`vite/package.json`)),`bin/vite.js`),`build`],{cwd:root,timeout:9e4,maxBuffer:5242880,env:childEnvironment(env)})},async buildNext(env,bundler){return exec(process.execPath,[require.resolve(`next/dist/bin/next`),`build`,`--${bundler}`],{cwd:root,timeout:15e4,maxBuffer:10485760,env:childEnvironment(env)})},async start(script,env){let child=spawn(process.execPath,[script],{cwd:root,env:childEnvironment(env),stdio:[`ignore`,`pipe`,`pipe`,`ipc`]}),output=``;child.stdout?.on(`data`,chunk=>{output+=chunk.toString()}),child.stderr?.on(`data`,chunk=>{output+=chunk.toString()});let exited=once(child,`exit`);try{let messages=await once(child,`message`,{signal:AbortSignal.timeout(3e4)});return{url:z.url().parse(messages[0]),async close(){child.kill(`SIGTERM`),await exited}}}catch(error){throw child.kill(`SIGKILL`),await exited,Error(`Application startup failed: ${output}`,{cause:error})}},async close(){await rm(root,{recursive:!0,force:!0})}}}catch(error){throw await rm(root,{recursive:!0,force:!0}),error}}export{application};
1
+ import{createRequire}from"node:module";import{z}from"zod";import{execFile,spawn}from"node:child_process";import{cp,mkdir,mkdtemp,readFile,realpath,rm,symlink,writeFile}from"node:fs/promises";import{tmpdir}from"node:os";import{dirname,join}from"node:path";import{promisify}from"node:util";import{once}from"node:events";const exec=promisify(execFile),metadata=z.object({name:z.string(),dependencies:z.record(z.string(),z.string()).default({})});function childEnvironment(overrides){return Object.fromEntries([[`PATH`,process.env.PATH],[`NODE_ENV`,`production`],[`NEXT_TELEMETRY_DISABLED`,`1`],...Object.entries(overrides)])}async function application(packageRoot,packages,options={}){let root=await mkdtemp(join(tmpdir(),`interfere-packed-application-`)),installed=new Set,require=createRequire(join(packageRoot,`package.json`));async function install(name,from,as=name){if(installed.has(as))return;installed.add(as);let modules=join(from,`node_modules`,name),source=metadata.parse(JSON.parse(await readFile(join(from,`package.json`),`utf8`))).name===name?from:await realpath(modules),manifest=metadata.parse(JSON.parse(await readFile(join(source,`package.json`),`utf8`))),destination=join(root,`node_modules`,as);if(await mkdir(dirname(destination),{recursive:!0}),!name.startsWith(`@interfere/`)){await symlink(source,destination,`dir`);return}let tarball=join(root,`${name.replaceAll(`/`,`-`)}.tgz`),prebuilt=options.tarballs?.[name];prebuilt===void 0?await exec(`bun`,[`pm`,`pack`,`--ignore-scripts`,`--quiet`,`--filename`,tarball],{cwd:source}):await cp(prebuilt,tarball),await mkdir(destination,{recursive:!0}),await exec(`tar`,[`-xzf`,tarball,`--strip-components=1`,`-C`,destination]),await Promise.all(Object.keys(manifest.dependencies).map(async dependency=>install(dependency,source)))}try{return await writeFile(join(root,`package.json`),JSON.stringify({private:!0,type:`module`})),await Promise.all(packages.map(async selection=>selection instanceof Object?install(selection.from,packageRoot,selection.as):install(selection,packageRoot))),{root,async copyFixture(name){await cp(join(packageRoot,`test/fixtures`,name),root,{recursive:!0})},async run(script,env={},runtime=process.execPath){return exec(runtime,[script],{cwd:root,timeout:3e4,maxBuffer:5242880,env:childEnvironment(env)})},async build(env,cwd=root){return exec(process.execPath,[join(dirname(require.resolve(`vite/package.json`)),`bin/vite.js`),`build`],{cwd,timeout:9e4,maxBuffer:5242880,env:childEnvironment(env)})},async buildNext(env,bundler,cwd=root){let installedNext=join(root,`node_modules`,`next`,`dist`,`bin`,`next`),bin=installed.has(`next`)?installedNext:require.resolve(`next/dist/bin/next`);return exec(process.execPath,[bin,`build`,...bundler?[`--${bundler}`]:[]],{cwd,timeout:15e4,maxBuffer:10485760,env:childEnvironment(env)})},async start(script,env,cwd=root){let child=spawn(process.execPath,[script],{cwd,env:childEnvironment(env),stdio:[`ignore`,`pipe`,`pipe`,`ipc`]}),output=``;child.stdout?.on(`data`,chunk=>{output+=chunk.toString()}),child.stderr?.on(`data`,chunk=>{output+=chunk.toString()});let exited=once(child,`exit`);try{let messages=await once(child,`message`,{signal:AbortSignal.timeout(3e4)});return{url:z.url().parse(messages[0]),output:()=>output,async close(){child.kill(`SIGTERM`),await exited}}}catch(error){throw child.kill(`SIGKILL`),await exited,Error(`Application startup failed: ${output}`,{cause:error})}},async close(){await rm(root,{recursive:!0,force:!0})}}}catch(error){throw await rm(root,{recursive:!0,force:!0}),error}}export{application};
@@ -6,7 +6,9 @@ export interface ReceivedRequest {
6
6
  body: string;
7
7
  status: number;
8
8
  }
9
- export declare function receiver(): Promise<{
9
+ export declare function receiver(options?: {
10
+ readonly sdkConfig?: Readonly<Record<string, string>>;
11
+ }): Promise<{
10
12
  url: string;
11
13
  requests: ReceivedRequest[];
12
14
  serve(directory: string): void;
@@ -1 +1 @@
1
- import{readFile}from"node:fs/promises";import{extname,resolve,sep}from"node:path";import{once}from"node:events";import{HttpResponse,getResponse,http}from"msw";import{buffer}from"node:stream/consumers";import{createServer}from"node:http";import{setTimeout}from"node:timers/promises";import{gunzipSync}from"node:zlib";async function receiver(){let requests=[],failures=new Map,stalls=new Map,assets,upstream;async function resolveResponse(request,incoming){let url=new URL(request.url),{headers}=request,responseHeaders=new Headers({"access-control-allow-origin":`*`,"access-control-allow-headers":headers.get(`access-control-request-headers`)??`*`,"access-control-allow-methods":`GET, POST, OPTIONS`});try{if(request.method===`OPTIONS`)return new HttpResponse(null,{status:204,headers:responseHeaders});if(assets&&request.method===`GET`&&!url.pathname.startsWith(`/v3/`)){let file=resolve(assets,`.${url.pathname===`/`?`/index.html`:url.pathname}`);return file.startsWith(`${resolve(assets)}${sep}`)?(responseHeaders.set(`content-type`,extname(file)===`.html`?`text/html`:`text/javascript`),new HttpResponse(await readFile(file),{headers:responseHeaders})):new HttpResponse(null,{status:403,headers:responseHeaders})}let stall=[...stalls].find(([prefix])=>url.pathname.startsWith(prefix))?.[1];stall!==void 0&&(incoming.pause(),await setTimeout(stall),incoming.resume());let raw=await buffer(incoming),body=headers.get(`content-encoding`)===`gzip`?gunzipSync(raw).toString():raw.toString(),failure=[...failures].find(([prefix])=>url.pathname.startsWith(prefix))?.[1],entry={method:request.method,url:url.pathname+url.search,headers,body,status:200};if(requests.push(entry),failure&&failure.remaining>0)return--failure.remaining,entry.status=failure.status,responseHeaders.set(`content-type`,`application/json`),new HttpResponse([204,205,304].includes(failure.status)?null:failure.body,{status:failure.status,headers:responseHeaders});if(upstream){let init={method:entry.method,headers};raw.length>0&&(init.body=raw);let result=await upstream(new Request(url,init));entry.status=result.status;for(let[key,value]of result.headers)responseHeaders.set(key,value);return new HttpResponse(result.body===null?null:await result.arrayBuffer(),{status:result.status,headers:responseHeaders})}return HttpResponse.json(url.pathname.includes(`config`)?{plugins:{errors:!0,logs:!0,device:!0,pageEvents:!0,rageClick:!0,replay:!0}}:{},{headers:responseHeaders})}catch(error){return new HttpResponse(new TextEncoder().encode(String(error)),{status:500,headers:responseHeaders})}}async function handle(request,response){try{let headers=new Headers;for(let[key,value]of Object.entries(request.headers))value!==void 0&&headers.set(key,Array.isArray(value)?value.join(`, `):value);let init={method:request.method??`GET`,headers},resolved=await getResponse([http.all(`*`,async({request:intercepted})=>resolveResponse(intercepted,request))],new Request(new URL(request.url??`/`,`http://localhost`),init));if(!resolved)throw Error(`Missing telemetry receiver response`);for(let[key,value]of resolved.headers)response.setHeader(key,value);response.writeHead(resolved.status).end(Buffer.from(await resolved.arrayBuffer()))}catch(error){response.destroy(error instanceof Error?error:Error(String(error)))}}let server=createServer((request,response)=>{handle(request,response)});server.listen(0,`127.0.0.1`),await once(server,`listening`);let address=server.address();if(!(address instanceof Object))throw Error(`Receiver did not bind a TCP port`);return{url:`http://127.0.0.1:${address.port}`,requests,serve(directory){assets=directory},forward(handler){upstream=handler},reset(){requests.length=0,failures.clear(),stalls.clear()},fail(path,status,attempts=1,body=`{}`){failures.set(path,{status,remaining:attempts,body})},stall(path,ms){stalls.set(path,ms)},async close(){server.closeAllConnections(),await new Promise((resolve,reject)=>{server.close(error=>{error?reject(error):resolve()})})}}}export{receiver};
1
+ import{readFile}from"node:fs/promises";import{extname,resolve,sep}from"node:path";import{once}from"node:events";import{HttpResponse,getResponse,http}from"msw";import{buffer}from"node:stream/consumers";import{createServer}from"node:http";import{setTimeout}from"node:timers/promises";import{gunzipSync}from"node:zlib";async function receiver(options={}){let requests=[],failures=new Map,stalls=new Map,assets,upstream;async function resolveResponse(request,incoming){let url=new URL(request.url),{headers}=request,responseHeaders=new Headers({"access-control-allow-origin":`*`,"access-control-allow-headers":headers.get(`access-control-request-headers`)??`*`,"access-control-allow-methods":`GET, POST, OPTIONS`});try{if(request.method===`OPTIONS`)return new HttpResponse(null,{status:204,headers:responseHeaders});if(assets&&request.method===`GET`&&!url.pathname.startsWith(`/v3/`)){let file=resolve(assets,`.${url.pathname===`/`?`/index.html`:url.pathname}`);return file.startsWith(`${resolve(assets)}${sep}`)?(responseHeaders.set(`content-type`,extname(file)===`.html`?`text/html`:`text/javascript`),new HttpResponse(await readFile(file),{headers:responseHeaders})):new HttpResponse(null,{status:403,headers:responseHeaders})}let stall=[...stalls].find(([prefix])=>url.pathname.startsWith(prefix))?.[1];stall!==void 0&&(incoming.pause(),await setTimeout(stall),incoming.resume());let raw=await buffer(incoming),body=headers.get(`content-encoding`)===`gzip`?gunzipSync(raw).toString():raw.toString(),failure=[...failures].find(([prefix])=>url.pathname.startsWith(prefix))?.[1],entry={method:request.method,url:url.pathname+url.search,headers,body,status:200};if(requests.push(entry),failure&&failure.remaining>0)return--failure.remaining,entry.status=failure.status,responseHeaders.set(`content-type`,`application/json`),new HttpResponse([204,205,304].includes(failure.status)?null:failure.body,{status:failure.status,headers:responseHeaders});if(upstream){let init={method:entry.method,headers};raw.length>0&&(init.body=raw);let result=await upstream(new Request(url,init));entry.status=result.status;for(let[key,value]of result.headers)responseHeaders.set(key,value);return new HttpResponse(result.body===null?null:await result.arrayBuffer(),{status:result.status,headers:responseHeaders})}return url.pathname===`/v3/releases/config`?HttpResponse.json({org:{id:`org_acceptance`,slug:`acceptance`},surface:{id:`surface_acceptance`,slug:`web`,sourceProvider:`github`,destinationProvider:`cli`,environmentFiltering:!0,sdkConfig:null}},{headers:responseHeaders}):HttpResponse.json(url.pathname.includes(`config`)?{plugins:{errors:!0,logs:!0,device:!0,pageEvents:!0,rageClick:!0,replay:!0},...options.sdkConfig}:{},{headers:responseHeaders})}catch(error){return new HttpResponse(new TextEncoder().encode(String(error)),{status:500,headers:responseHeaders})}}async function handle(request,response){try{let headers=new Headers;for(let[key,value]of Object.entries(request.headers))value!==void 0&&headers.set(key,Array.isArray(value)?value.join(`, `):value);let init={method:request.method??`GET`,headers},resolved=await getResponse([http.all(`*`,async({request:intercepted})=>resolveResponse(intercepted,request))],new Request(new URL(request.url??`/`,`http://localhost`),init));if(!resolved)throw Error(`Missing telemetry receiver response`);for(let[key,value]of resolved.headers)response.setHeader(key,value);response.writeHead(resolved.status).end(Buffer.from(await resolved.arrayBuffer()))}catch(error){response.destroy(error instanceof Error?error:Error(String(error)))}}let server=createServer((request,response)=>{handle(request,response)});server.listen(0,`127.0.0.1`),await once(server,`listening`);let address=server.address();if(!(address instanceof Object))throw Error(`Receiver did not bind a TCP port`);return{url:`http://127.0.0.1:${address.port}`,requests,serve(directory){assets=directory},forward(handler){upstream=handler},reset(){requests.length=0,failures.clear(),stalls.clear()},fail(path,status,attempts=1,body=`{}`){failures.set(path,{status,remaining:attempts,body})},stall(path,ms){stalls.set(path,ms)},async close(){server.closeAllConnections(),await new Promise((resolve,reject)=>{server.close(error=>{error?reject(error):resolve()})})}}}export{receiver};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/test-utils",
3
- "version": "10.0.2",
3
+ "version": "10.1.0-rc.0",
4
4
  "description": "Shared test utilities used across Interfere packages.",
5
5
  "homepage": "https://interfere.com",
6
6
  "bugs": "mailto:support@interfere.com",
@@ -33,11 +33,12 @@
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsdown",
36
+ "lint:oxlint": "cd ../../../.. && GOMAXPROCS=2 oxlint --max-warnings=0 --report-unused-disable-directives-severity=error --no-error-on-unmatched-pattern --threads=2 src/packages/public/test-utils",
36
37
  "test": "node ../../private/vitest-config/src/cli.ts run --config ../../../../vitest.config.ts",
37
38
  "typecheck": "bun check"
38
39
  },
39
40
  "dependencies": {
40
- "@interfere/helpers": "^9.3.0",
41
+ "@interfere/helpers": "^9.3.1",
41
42
  "msw": "2.15.0",
42
43
  "zod": "4.6.6"
43
44
  },