`run-fargate` 2.0.33: esbuild dependency tracing adds `.` and `..` to module list, causing `EINVALIDPACKAGENAME` on workers
Version info:
artillery 2.0.33Running this command:
artillery run-fargate ./load-tests/load-test.yml \
--output ./artillery-report/report.json \
--dotenv .env.artillery.temp \
--region ap-southeast-1 \
--count 5 \
--cluster my-ecs-cluster \
--subnet-ids subnet-xxx \
--security-group-ids sg-xxxI expected to see this happen:
Workers sync successfully and the load test executes. This worked correctly on artillery 2.0.21 with the same project structure.
Instead, this happened:
The "Test bundle contents" table printed by the coordinator includes . and .. as packages (with note "not in package.json"):
┌────────────────────────────────────────┬─────────┬──────────────────────────────┐
│ Name │ Type │ Notes │
├────────────────────────────────────────┼─────────┼──────────────────────────────┤
│ load-tests/load-test.yml │ file │ │
│ ... │ file │ │
│ package.json │ file │ │
│ package-lock.json │ file │ │
├────────────────────────────────────────┼─────────┼──────────────────────────────┤
│ . │ package │ not in package.json │
│ .. │ package │ not in package.json · v1.0.0 │
│ @datadog/datadog-ci │ package │ │
│ ... │ package │ │
└────────────────────────────────────────┴─────────┴──────────────────────────────┘The leader worker then attempts npm install .. and fails:
******** [a519e2ac...] Worker starting up, ID = a519e2ac..., version = 2.0.33, leader = true
******** [a519e2ac...] Syncing test data
******** [a519e2ac...] Installing dependencies
Installing required npm dependencies
installing .
Protected by Socket Firewall
npm error code EINVALIDPACKAGENAME
npm error Invalid package name ".." of package "[email protected]": name cannot start with a period.Because the leader crashes, it never produces node_modules.zip. All non-leader workers hang waiting for it:
******** [c929e234...] Worker starting up, ID = c929e234..., version = 2.0.33, leader = false
******** [c929e234...] Installing dependencies
Waiting... (s3://artilleryio-test-data-<account-id>/tests/<test-run-id>/node_modules.zip)The coordinator eventually times out: Error: Timed out waiting for worker sync
Root Cause
The new getCustomJsDependencies function in lib/platform/aws-ecs/legacy/bom.js uses esbuild to trace imports from the processor file. The recoverPlugin catches relative imports that can't be resolved on disk and marks them as external:
build.onResolve({ filter: /^\.{1,2}\// }, (args) => {
const candidate = path.resolve(args.resolveDir, args.path);
for (const ext of exts) {
try { if (fs.statSync(candidate + ext).isFile()) return null; } catch (_e) {}
}
// can't resolve → mark external
return { path: args.path, external: true };
});Later, extractPackageName extracts the first path segment as a "package name":
function extractPackageName(spec) {
if (spec.startsWith('@')) { ... }
return spec.split('/')[0]; // '../common/foo' → '..' , './bar' → '.'
}So when a relative import like '../common/network/types' can't be resolved (e.g. TypeScript file without explicit extension in a transitive dependency), it becomes .. in the modules list. Similarly './something' becomes ..
These get written into metadata.json and the leader worker runs npm install . .. which fails.
Suggested Fix
Filter out . and .. from the modules list in bom.js before it's returned:
const modules = _.uniq(context.npmModules).filter(
(m) =>
m !== 'artillery' &&
m !== 'playwright' &&
!m.startsWith('@playwright/') &&
m !== '.' && // <-- add this
m !== '..' // <-- add this
);Or guard in extractPackageName:
function extractPackageName(spec) {
if (spec === '.' || spec === '..') return null;
if (spec.startsWith('@')) { ... }
return spec.split('/')[0];
}Files being used:
# load-tests/load-test.yml
config:
target: "https://example.com"
engines:
playwright:
launchOptions:
headless: true
contextOptions:
ignoreHTTPSErrors: true
extendedMetrics: true
processor: ./flows.ts
phases:
- name: ramp up phase
rampTo: "{{ $env.A_VUSER_ARRIVAL_RATE }}"
duration: "{{ $env.A_RAMP_UP_TIME }}"
maxVusers: "{{ $env.A_MAX_VUSERS }}"
scenarios:
- name: "login-scenario"
engine: playwright
flowFunction: "loginTask"// load-tests/flows.ts (processor file)
import { type Page } from '@playwright/test'
import { type RpLoginInfo } from '../common/network/types' // relative import going up one level
import { executeLoginTask } from '../common/task/tasks' // same pattern
import { getClient } from '../common/globalSetUp'
export async function loginTask(page: Page, userContext: any, events: any, test: any): Promise<void> {
// ...
}# Project structure:
/app
├── load-tests/
│ ├── load-test.yml
│ └── flows.ts ← processor, imports from ../common/
├── common/
│ ├── network/types.ts
│ ├── task/tasks.ts
│ └── globalSetUp.ts
├── package.json
└── package-lock.jsonAdditional Context
- This did NOT occur on artillery 2.0.21 (which did not have esbuild-based dependency tracing)
- The project uses TypeScript with relative imports across sibling directories (
load-tests/→../common/) - The issue is reproducible on every run with 2.0.33; it is not intermittent
Source: artilleryio/artillery