Skip to content

Commit 90f580e

Browse files
committed
fix(typescript): fix stale .mjs refs the port missed, and type the three dev scripts for real
3 test files (iterate-loop-load-test-script, miner-benchmark-script, miner-cross-repo-evaluation) still imported the OLD .mjs paths for load-test-iterate-loop/benchmark/cross-repo-evaluation after their #9527 rename to .ts -- the package.json runners were updated but these direct test imports were missed. Broke CI on #9534 (Cannot find module ... .mjs). Fixed, plus the same stale path in two docs, a manifest description string, a script's own --help text, and a code comment, none of which the original port's grep sweep caught. Typing the three scripts for the first time (they were always .mjs, so tsc never saw them) surfaced real gaps, not just missing annotations: - cross-repo-evaluation.ts's main() called parseCrossRepoEvaluationArgs() with zero arguments against a REQUIRED parameter -- silently fine in untyped JS because the function internally falls back via , but the parameter needed to actually be optional to say what was always true. - benchmark.ts's buildSyntheticCandidates() was missing owner/repo/assignees entirely and mixed real booleans into a field opportunity-fanout.ts types as literal -only. Traced rankCandidateIssues' actual runtime check () before deciding how to type it: it genuinely branches on false, so narrowing the benchmark's synthetic mix to true-only would have silently dropped real exercised coverage. Added the missing fields for real, kept the deliberate true/false mix with a documented cast rather than narrowing it away. - load-test-iterate-loop.ts's fake driver was typed to a hand-narrowed { attemptId: string } task shape until the test file's own fuller task literal (workingDirectory, acceptanceCriteriaPath, ...) failed against it -- switched to the real CodingAgentDriverTask/Result types instead of the narrower hand-rolled ones. Refs #9527
1 parent fe01add commit 90f580e

11 files changed

Lines changed: 107 additions & 43 deletions

File tree

control-plane/src/worker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// driver-factory.ts) into the plain, already-tested Hono app (http-app.ts). Adds NO route logic of its own.
66
//
77
// Not unit-tested: exercised only by real Cloudflare Workers/KV/Containers infrastructure, matching
8-
// packages/discovery-index/src/worker.ts's own identical exclusion (see scripts/control-plane-coverage.mjs).
8+
// packages/discovery-index/src/worker.ts's own identical exclusion (see scripts/control-plane-coverage.ts).
99
import { Container } from "@cloudflare/containers";
1010
import { wakeDueAmsTenants } from "./ams-wake.js";
1111
import { createTenantProvisioningDriver } from "./driver-factory.js";

package-lock.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/loopover-engine/docs/iterate-loop-load-test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ mirrors; #5224 is the AMS-side counterpart this harness was built for.
1414
npm run loadtest:iterate-loop
1515
# or, from a workspace checkout, after building the engine:
1616
npm --workspace @loopover/engine run build
17-
node packages/loopover-engine/scripts/load-test-iterate-loop.mjs
17+
node --experimental-strip-types packages/loopover-engine/scripts/load-test-iterate-loop.ts
1818
```
1919

2020
This prints a short text report to stdout and exits `0`. It does not fail the build or a CI job on its own

packages/loopover-engine/scripts/load-test-iterate-loop.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
#!/usr/bin/env node
22
import { performance } from "node:perf_hooks";
3-
import { parseFocusManifest, runIterateLoop } from "../dist/index.js";
3+
import {
4+
parseFocusManifest,
5+
runIterateLoop,
6+
type IterateLoopInput,
7+
type IterateLoopDeps,
8+
type CodingAgentExecutionMode,
9+
type CodingAgentDriverTask,
10+
type CodingAgentDriverResult,
11+
} from "../dist/index.js";
12+
13+
/** A driver implementing only `run()` -- the single member iterate-loop's own dependency injection
14+
* calls -- rather than the full CodingAgentDriver interface, which this harness never otherwise
15+
* touches. The task/result shapes themselves are the real CodingAgentDriverTask/Result types, not
16+
* narrowed, so a caller building a full realistic task (as the test double coverage below does)
17+
* gets real type checking on it. */
18+
type FakeLoadTestDriver = { run: (task: CodingAgentDriverTask) => Promise<CodingAgentDriverResult> };
19+
type ConcurrencyLevelOptions = { attemptCount?: number; latencyMs?: number };
20+
type ConcurrencyLevelResult = { concurrency: number; attemptCount: number; latencyMs: number; wallMs: number; handoffCount: number; attemptsPerSecond: number };
21+
type LoadTestOptions = ConcurrencyLevelOptions & { levels?: number[] };
422

523
// Load-testing harness for the AMS iterate-loop orchestrator (#5224): every existing iterate-loop test is
624
// correctness-oriented (single attempt, fake driver returns instantly), so there is no signal today for how
@@ -20,7 +38,7 @@ const SYNTHETIC_ISSUE_NUMBER = 7;
2038
/** One open issue per synthetic tenant repo, matching that tenant's own `passesPredictedGate` linkage below --
2139
* each tenant is a fully independent repo/contributor pair (`buildSelfReviewPredictedGateInput`'s own identity
2240
* fields), the same "multi-tenant-like" shape the issue's Problem section asks this harness to load-test. */
23-
function buildReviewContext(tenantRepoFullName) {
41+
function buildReviewContext(tenantRepoFullName: string) {
2442
return {
2543
manifest: parseFocusManifest({ gate: { duplicates: "block", linkedIssue: "advisory" } }),
2644
repo: { fullName: tenantRepoFullName, owner: tenantRepoFullName.split("/")[0], name: tenantRepoFullName.split("/")[1], isInstalled: true, isRegistered: true, isPrivate: false },
@@ -33,7 +51,7 @@ function buildReviewContext(tenantRepoFullName) {
3351
* it resolves after `latencyMs` (simulating the wall-clock an iteration of a real coding-agent invocation
3452
* would take) with a scripted, always-passing result. `latencyMs` uses a real `setTimeout`, not a busy-loop, so
3553
* concurrent attempts genuinely interleave on the event loop the way concurrent live attempts would. */
36-
export function buildFakeLoadTestDriver(latencyMs) {
54+
export function buildFakeLoadTestDriver(latencyMs: number): FakeLoadTestDriver {
3755
return {
3856
async run(task) {
3957
await new Promise((resolve) => setTimeout(resolve, latencyMs));
@@ -48,15 +66,16 @@ const NOOP_SLOP_ASSESSMENT = { slopRisk: 0, band: "clean", findings: [] };
4866
* issue that matches on the first iteration, so every attempt hands off in exactly one iteration -- isolating
4967
* the measurement to iterate-loop's own per-attempt orchestration overhead rather than varying iteration counts
5068
* across runs. */
51-
async function runOneAttempt(tenantIndex, driver) {
69+
async function runOneAttempt(tenantIndex: number, driver: FakeLoadTestDriver) {
5270
const repoFullName = `load-test-tenant-${tenantIndex}/repo`;
5371
const attemptId = `attempt-${tenantIndex}`;
72+
const mode: CodingAgentExecutionMode = "live";
5473
const input = {
5574
attemptId,
5675
workingDirectory: `/tmp/${attemptId}`,
5776
acceptanceCriteriaPath: `/tmp/${attemptId}/acceptance-criteria.json`,
5877
instructions: "Synthetic load-test instructions",
59-
mode: "live",
78+
mode,
6079
maxIterations: 3,
6180
maxTurnsPerIteration: 20,
6281
repoFullName,
@@ -66,12 +85,16 @@ async function runOneAttempt(tenantIndex, driver) {
6685
linkedIssues: [SYNTHETIC_ISSUE_NUMBER],
6786
reviewContext: buildReviewContext(repoFullName),
6887
rejectionSignaled: false,
69-
};
88+
} as IterateLoopInput;
89+
// FakeLoadTestDriver only implements the single `run()` member iterate-loop actually calls, not
90+
// the full CodingAgentDriver interface -- deliberately narrow, matching this package's existing
91+
// injection-seam convention of typing a test/harness double to the minimal surface exercised
92+
// rather than the full production interface.
7093
const deps = {
7194
driver,
7295
runSlopAssessment: () => NOOP_SLOP_ASSESSMENT,
7396
appendAttemptLogEvent: () => {},
74-
};
97+
} as IterateLoopDeps;
7598
const start = performance.now();
7699
const result = await runIterateLoop(input, deps);
77100
return { elapsedMs: performance.now() - start, result };
@@ -83,13 +106,13 @@ async function runOneAttempt(tenantIndex, driver) {
83106
* to hand off after its first iteration (see {@link runOneAttempt}) -- a non-`"handoff"` outcome or a driver
84107
* error would silently understate real concurrent load, so both are counted and surfaced rather than ignored.
85108
*/
86-
export async function runConcurrencyLevel(concurrency, options = {}) {
109+
export async function runConcurrencyLevel(concurrency: number, options: ConcurrencyLevelOptions = {}): Promise<ConcurrencyLevelResult> {
87110
const attemptCount = options.attemptCount ?? DEFAULT_ATTEMPTS_PER_LEVEL;
88111
const latencyMs = options.latencyMs ?? DEFAULT_SIMULATED_DRIVER_LATENCY_MS;
89112
const driver = buildFakeLoadTestDriver(latencyMs);
90113

91114
const start = performance.now();
92-
const outcomes = [];
115+
const outcomes: Awaited<ReturnType<typeof runOneAttempt>>[] = [];
93116
for (let batchStart = 0; batchStart < attemptCount; batchStart += concurrency) {
94117
const batchSize = Math.min(concurrency, attemptCount - batchStart);
95118
const batch = await Promise.all(
@@ -112,17 +135,17 @@ export async function runConcurrencyLevel(concurrency, options = {}) {
112135

113136
/** Run every concurrency level in `levels` in sequence (never overlapping each other), so one level's
114137
* scheduling contention never bleeds into the next level's measurement. */
115-
export async function runLoadTest(options = {}) {
138+
export async function runLoadTest(options: LoadTestOptions = {}): Promise<ConcurrencyLevelResult[]> {
116139
const levels = options.levels ?? DEFAULT_CONCURRENCY_LEVELS;
117-
const results = [];
140+
const results: ConcurrencyLevelResult[] = [];
118141
for (const concurrency of levels) {
119142
results.push(await runConcurrencyLevel(concurrency, options));
120143
}
121144
return results;
122145
}
123146

124147
/** Render load-test results as a stable, greppable text report (no locale-dependent number formatting). */
125-
export function formatLoadTestReport(results) {
148+
export function formatLoadTestReport(results: ConcurrencyLevelResult[]): string {
126149
const lines = ["iterate-loop load test", ""];
127150
for (const r of results) {
128151
lines.push(

packages/loopover-miner/benchmarks/cross-repo/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"description": "Cross-repo evaluation benchmark set (#4788). Diverse public repos exercised by `node packages/loopover-miner/scripts/cross-repo-evaluation.mjs` after cloning into LOOPOVER_MINER_REPO_CLONE_DIR.",
2+
"description": "Cross-repo evaluation benchmark set (#4788). Diverse public repos exercised by `node --experimental-strip-types packages/loopover-miner/scripts/cross-repo-evaluation.ts` after cloning into LOOPOVER_MINER_REPO_CLONE_DIR.",
33
"repos": [
44
{
55
"repoFullName": "sindresorhus/is",

packages/loopover-miner/docs/cross-repo-evaluation.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fleet run-manifest).
5151
2. Run the harness from the repo root:
5252

5353
```bash
54-
node packages/loopover-miner/scripts/cross-repo-evaluation.mjs
54+
node --experimental-strip-types packages/loopover-miner/scripts/cross-repo-evaluation.ts
5555
```
5656

5757
Useful flags:
@@ -70,7 +70,7 @@ the coding agent runs against a synthetic benchmark issue inside that copy, and
7070
test commands are executed there. The scratch tree is removed afterward in every outcome — execute-locally-and-discard.
7171

7272
```bash
73-
node packages/loopover-miner/scripts/cross-repo-evaluation.mjs --full-execution
73+
node --experimental-strip-types packages/loopover-miner/scripts/cross-repo-evaluation.ts --full-execution
7474
```
7575

7676
Prerequisites beyond the readiness mode: a configured coding-agent provider (`MINER_CODING_AGENT_PROVIDER`, see
@@ -93,7 +93,7 @@ defaults to `DEFAULT_CROSS_REPO_EXECUTION_MAX_TURNS` (24).
9393

9494
## Library API
9595

96-
Pure functions live in [`lib/cross-repo-evaluation.js`](../lib/cross-repo-evaluation.js):
96+
Pure functions live in [`lib/cross-repo-evaluation.ts`](../lib/cross-repo-evaluation.ts):
9797

9898
- `parseCrossRepoEvaluationManifest(content)`
9999
- `evaluateRepoReadiness(entry, options)` — inject `existsSync`, `detectRepoStack`, etc. for unit tests

packages/loopover-miner/scripts/benchmark.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
#!/usr/bin/env node
22
import { performance } from "node:perf_hooks";
33
import { rankCandidateIssues } from "../dist/lib/opportunity-ranker.js";
4+
import type { RawCandidateIssue } from "../dist/lib/opportunity-fanout.js";
45
import { initPortfolioQueueStore } from "../dist/lib/portfolio-queue.js";
56

7+
type RankingBenchmarkOptions = { candidateCount?: number; iterations?: number };
8+
type LocalStoreBenchmarkOptions = { operationCount?: number; iterations?: number };
9+
type BenchmarkResult = { name: string; unitCount: number; iterations: number; medianMs: number; opsPerSecond: number };
10+
611
// Committed micro-benchmark for the two hot local paths that have no other way to notice a regression: the
712
// discovery fan-out ranking pass (opportunity-ranker.js, run once per repo per discovery cycle over every open
813
// candidate) and the local-store read/write path (portfolio-queue.js, run on every enqueue/claim). Neither is
@@ -21,44 +26,53 @@ const SYNTHETIC_EPOCH_MS = Date.UTC(2024, 0, 1);
2126
* `Date.now()`, so `buildSyntheticCandidates(n)` returns byte-identical input on every call/machine/run -- the
2227
* benchmark's numbers are comparable across runs precisely because its input never varies.
2328
*/
24-
export function buildSyntheticCandidates(count) {
25-
const candidates = [];
29+
export function buildSyntheticCandidates(count: number): RawCandidateIssue[] {
30+
const candidates: RawCandidateIssue[] = [];
2631
for (let i = 0; i < count; i += 1) {
2732
const timestamp = new Date(SYNTHETIC_EPOCH_MS + i * 3_600_000).toISOString();
2833
candidates.push({
34+
owner: "bench-owner",
35+
repo: `bench-repo-${i % 7}`,
2936
repoFullName: `bench-owner/bench-repo-${i % 7}`,
3037
issueNumber: i + 1,
3138
title: `Synthetic benchmark issue #${i + 1}`,
32-
labels: [LABEL_POOL[i % LABEL_POOL.length]],
39+
labels: [LABEL_POOL[i % LABEL_POOL.length]!],
40+
assignees: [],
3341
commentsCount: i % 11,
3442
createdAt: timestamp,
3543
updatedAt: timestamp,
3644
htmlUrl: `https://github.com/bench-owner/bench-repo-${i % 7}/issues/${i + 1}`,
45+
// RawCandidateIssue's aiPolicyAllowed is typed `true`-only (real production candidates
46+
// are pre-filtered to allowed-only before reaching this stage), but rankCandidateIssues'
47+
// own logic checks `candidate.aiPolicyAllowed !== false` at runtime -- it genuinely
48+
// tolerates and branches on a false value. The benchmark deliberately mixes in some
49+
// aiPolicyAllowed:false candidates to exercise that branch, so this is cast below rather
50+
// than narrowed to true-only, which would silently drop real exercised coverage.
3751
aiPolicyAllowed: i % 5 !== 0,
3852
aiPolicySource: i % 5 === 0 ? "AI-USAGE.md" : "none",
39-
});
53+
} as RawCandidateIssue);
4054
}
4155
return candidates;
4256
}
4357

44-
function timeMs(fn) {
58+
function timeMs(fn: () => void): number {
4559
const start = performance.now();
4660
fn();
4761
return performance.now() - start;
4862
}
4963

50-
function median(values) {
64+
function median(values: number[]): number {
5165
const sorted = [...values].sort((a, b) => a - b);
5266
const mid = Math.floor(sorted.length / 2);
53-
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
67+
return sorted.length % 2 === 0 ? (sorted[mid - 1]! + sorted[mid]!) / 2 : sorted[mid]!;
5468
}
5569

5670
/** Rank the same synthetic candidate set `iterations` times and report the median wall time (#4845). */
57-
export function runRankingBenchmark(options = {}) {
71+
export function runRankingBenchmark(options: RankingBenchmarkOptions = {}): BenchmarkResult {
5872
const candidateCount = options.candidateCount ?? DEFAULT_CANDIDATE_COUNT;
5973
const iterations = options.iterations ?? DEFAULT_ITERATIONS;
6074
const candidates = buildSyntheticCandidates(candidateCount);
61-
const samples = [];
75+
const samples: number[] = [];
6276
for (let i = 0; i < iterations; i += 1) {
6377
samples.push(timeMs(() => rankCandidateIssues(candidates, { nowMs: SYNTHETIC_EPOCH_MS })));
6478
}
@@ -77,10 +91,10 @@ export function runRankingBenchmark(options = {}) {
7791
* the median wall time -- the same read/write path every real enqueue/claim exercises against the on-disk file,
7892
* minus filesystem I/O, so the number isolates the query-plan/schema cost this package actually controls.
7993
*/
80-
export function runLocalStoreBenchmark(options = {}) {
94+
export function runLocalStoreBenchmark(options: LocalStoreBenchmarkOptions = {}): BenchmarkResult {
8195
const operationCount = options.operationCount ?? DEFAULT_QUEUE_OPERATION_COUNT;
8296
const iterations = options.iterations ?? DEFAULT_ITERATIONS;
83-
const samples = [];
97+
const samples: number[] = [];
8498
for (let i = 0; i < iterations; i += 1) {
8599
const store = initPortfolioQueueStore(":memory:");
86100
try {
@@ -113,7 +127,7 @@ export function runLocalStoreBenchmark(options = {}) {
113127
}
114128

115129
/** Render benchmark results as a stable, greppable text report (no locale-dependent number formatting). */
116-
export function formatBenchmarkReport(results) {
130+
export function formatBenchmarkReport(results: BenchmarkResult[]): string {
117131
const lines = ["loopover-miner benchmark", ""];
118132
for (const result of results) {
119133
lines.push(

packages/loopover-miner/scripts/cross-repo-evaluation.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,22 @@ import {
99
runCrossRepoEvaluation,
1010
runCrossRepoFullExecution,
1111
summarizeCrossRepoEvaluation,
12+
type ParsedCrossRepoEvaluationManifest,
1213
} from "../dist/lib/cross-repo-evaluation.js";
1314

1415
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
1516

17+
// dist/lib/cross-repo-evaluation.js DOES ship a sibling .d.ts (that package's declaration: true),
18+
// so the real ParsedCrossRepoEvaluationManifest type is available -- reused here rather than
19+
// duplicated. repoFilter is string | null (not string | undefined) to match
20+
// parseCrossRepoEvaluationArgs' own inferred return shape below.
21+
type CliOptions = { parsed?: ParsedCrossRepoEvaluationManifest; manifestPath?: string; repoFilter?: string | null };
22+
1623
export function resolveDefaultManifestPath() {
1724
return join(PACKAGE_ROOT, DEFAULT_CROSS_REPO_MANIFEST_RELATIVE_PATH);
1825
}
1926

20-
export function parseCrossRepoEvaluationArgs(argv) {
27+
export function parseCrossRepoEvaluationArgs(argv?: string[]) {
2128
const args = argv ?? process.argv.slice(2);
2229
let manifestPath = resolveDefaultManifestPath();
2330
let json = false;
@@ -60,23 +67,27 @@ export function parseCrossRepoEvaluationArgs(argv) {
6067
return { manifestPath, json, repoFilter, requireMajority, fullExecution };
6168
}
6269

63-
export function loadCrossRepoEvaluationManifest(manifestPath) {
70+
export function loadCrossRepoEvaluationManifest(manifestPath: string): ParsedCrossRepoEvaluationManifest {
6471
const content = readFileSync(manifestPath, "utf8");
6572
return parseCrossRepoEvaluationManifest(content);
6673
}
6774

68-
export function runCrossRepoEvaluationCli(options = {}) {
75+
export function runCrossRepoEvaluationCli(options: CliOptions = {}) {
6976
const parsed = options.parsed ?? loadCrossRepoEvaluationManifest(options.manifestPath ?? resolveDefaultManifestPath());
70-
const results = runCrossRepoEvaluation(parsed, { repoFilter: options.repoFilter ?? null });
77+
// exactOptionalPropertyTypes: conditionally spread rather than always set repoFilter to a
78+
// possibly-undefined/null value -- the target type's repoFilter?: string admits "absent", not
79+
// "present and undefined". null and undefined are equally falsy at every downstream truthiness
80+
// check either way, so this is a type-only normalization, not a behavior change.
81+
const results = runCrossRepoEvaluation(parsed, options.repoFilter ? { repoFilter: options.repoFilter } : {});
7182
const summary = summarizeCrossRepoEvaluation(results);
7283
return { parsed, results, summary };
7384
}
7485

7586
/** Full-execution counterpart of runCrossRepoEvaluationCli (#7634) — same shape, async because agent runs and
7687
* the benchmark repos' own test suites are. Dry-run: see runCrossRepoFullExecution. */
77-
export async function runCrossRepoFullExecutionCli(options = {}) {
88+
export async function runCrossRepoFullExecutionCli(options: CliOptions = {}) {
7889
const parsed = options.parsed ?? loadCrossRepoEvaluationManifest(options.manifestPath ?? resolveDefaultManifestPath());
79-
const results = await runCrossRepoFullExecution(parsed, { repoFilter: options.repoFilter ?? null });
90+
const results = await runCrossRepoFullExecution(parsed, options.repoFilter ? { repoFilter: options.repoFilter } : {});
8091
const summary = summarizeCrossRepoEvaluation(results);
8192
return { parsed, results, summary };
8293
}
@@ -87,7 +98,7 @@ function printHelp() {
8798
"loopover-miner cross-repo evaluation (#4788)",
8899
"",
89100
"Usage:",
90-
" node packages/loopover-miner/scripts/cross-repo-evaluation.mjs [options]",
101+
" node --experimental-strip-types packages/loopover-miner/scripts/cross-repo-evaluation.ts [options]",
91102
"",
92103
"Options:",
93104
" --manifest <path> Benchmark manifest (default: benchmarks/cross-repo/manifest.json)",

test/unit/iterate-loop-load-test-script.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
formatLoadTestReport,
1010
runConcurrencyLevel,
1111
runLoadTest,
12-
} from "../../packages/loopover-engine/scripts/load-test-iterate-loop.mjs";
12+
} from "../../packages/loopover-engine/scripts/load-test-iterate-loop";
1313

1414
describe("iterate-loop load-test script (#5224)", () => {
1515
it("the fake driver never spawns a real subprocess and resolves a scripted ok result after the configured latency", async () => {
@@ -76,7 +76,7 @@ describe("iterate-loop load-test script (#5224)", () => {
7676
});
7777

7878
it("runs end-to-end as a CLI script and prints the report header plus every default concurrency level", () => {
79-
const result = spawnSync(process.execPath, ["packages/loopover-engine/scripts/load-test-iterate-loop.mjs"], {
79+
const result = spawnSync(process.execPath, ["--experimental-strip-types", "packages/loopover-engine/scripts/load-test-iterate-loop.ts"], {
8080
cwd: process.cwd(),
8181
encoding: "utf8",
8282
});

0 commit comments

Comments
 (0)