@usejourney/cli 0.1.4 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +129 -75
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -24,8 +24,8 @@ async function runEnvList(projectDir) {
24
24
 
25
25
  // src/commands/exportK6.ts
26
26
  import { stat } from "fs/promises";
27
- import { isAbsolute, resolve } from "path";
28
- import { exportToK6 } from "@usejourney/k6-adapter";
27
+ import { dirname as dirname2, isAbsolute, resolve } from "path";
28
+ import { exportToK6, findProjectDir } from "@usejourney/k6-adapter";
29
29
 
30
30
  // src/util/discover.ts
31
31
  import { readdir } from "fs/promises";
@@ -58,6 +58,35 @@ async function importJourneyFiles(files) {
58
58
  }
59
59
  }
60
60
 
61
+ // src/util/projectCoreLink.ts
62
+ import { existsSync, readFileSync } from "fs";
63
+ import { mkdir, symlink } from "fs/promises";
64
+ import { dirname, join as join2 } from "path";
65
+ import { fileURLToPath } from "url";
66
+ function locateJourneyCoreDir() {
67
+ let dir = dirname(fileURLToPath(import.meta.url));
68
+ for (let depth = 0; depth < 50; depth++) {
69
+ const pkgJson = join2(dir, "node_modules", "@usejourney", "core", "package.json");
70
+ if (existsSync(pkgJson)) {
71
+ JSON.parse(readFileSync(pkgJson, "utf8"));
72
+ return dirname(pkgJson);
73
+ }
74
+ const parent = dirname(dir);
75
+ if (parent === dir) break;
76
+ dir = parent;
77
+ }
78
+ throw new Error("ensureProjectCoreLink: could not locate the CLI-bundled @usejourney/core");
79
+ }
80
+ var cachedCoreDir;
81
+ async function ensureProjectCoreLink(projectDir) {
82
+ const link = join2(projectDir, "node_modules", "@usejourney", "core");
83
+ if (existsSync(link)) return;
84
+ if (!cachedCoreDir) cachedCoreDir = locateJourneyCoreDir();
85
+ await mkdir(dirname(link), { recursive: true });
86
+ const type = process.platform === "win32" ? "junction" : "dir";
87
+ await symlink(cachedCoreDir, link, type);
88
+ }
89
+
61
90
  // src/commands/exportK6.ts
62
91
  function matches(def, tags) {
63
92
  if (tags.length === 0) return true;
@@ -97,6 +126,8 @@ async function runExportK6(opts) {
97
126
  console.log(`No .journey.ts files found in ${opts.path}`);
98
127
  return 0;
99
128
  }
129
+ const projectDir = findProjectDir(isDir ? target : dirname2(target)) ?? process.cwd();
130
+ await ensureProjectCoreLink(projectDir);
100
131
  const work = [];
101
132
  for (const file of files) {
102
133
  const defs = await loadJourneyDefs(file);
@@ -114,7 +145,10 @@ async function runExportK6(opts) {
114
145
  journeyFile: file,
115
146
  ...opts.out !== void 0 ? { outFile: opts.out } : {},
116
147
  ...opts.outDir !== void 0 ? { outDir: opts.outDir } : {},
117
- ...k6Options !== void 0 ? { k6Options } : {}
148
+ ...k6Options !== void 0 ? { k6Options } : {},
149
+ ...opts.metricsPrefix !== void 0 ? { metricsPrefix: opts.metricsPrefix } : {},
150
+ ...opts.haltOnFailure ? { haltOnFailure: true } : {},
151
+ projectDir
118
152
  });
119
153
  console.log(`Wrote k6 script \u2192 ${result.outFile}`);
120
154
  exported++;
@@ -126,8 +160,8 @@ async function runExportK6(opts) {
126
160
  }
127
161
 
128
162
  // src/commands/exportPostman.ts
129
- import { mkdir, stat as stat2, writeFile } from "fs/promises";
130
- import { basename, dirname, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "path";
163
+ import { mkdir as mkdir2, stat as stat2, writeFile } from "fs/promises";
164
+ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
131
165
  import {
132
166
  clearActiveEnvironment,
133
167
  collectPipeline,
@@ -232,8 +266,8 @@ function uniqueFolderName(name, seen) {
232
266
  async function exportEnvironment(environmentsDir, envName, outDir) {
233
267
  const values = await loadEnvironment(environmentsDir, envName);
234
268
  const env = buildEnvironment(envName, values);
235
- const outFile = join2(outDir, `${envName}.postman_environment.json`);
236
- await mkdir(dirname(outFile), { recursive: true });
269
+ const outFile = join3(outDir, `${envName}.postman_environment.json`);
270
+ await mkdir2(dirname3(outFile), { recursive: true });
237
271
  await writeFile(outFile, JSON.stringify(env, null, 2), "utf8");
238
272
  return outFile;
239
273
  }
@@ -319,8 +353,8 @@ async function runExportPostman(opts) {
319
353
  const collection = buildCollection(opts.name ?? "journeys", mergedFolders, {
320
354
  reset: opts.threadState ?? false
321
355
  });
322
- const collectionOutDir = dirname(outFile);
323
- await mkdir(collectionOutDir, { recursive: true });
356
+ const collectionOutDir = dirname3(outFile);
357
+ await mkdir2(collectionOutDir, { recursive: true });
324
358
  await writeFile(outFile, JSON.stringify(collection, null, 2), "utf8");
325
359
  console.log(`Wrote Postman collection \u2192 ${outFile}`);
326
360
  exported++;
@@ -351,9 +385,9 @@ async function runExportPostman(opts) {
351
385
  reset: opts.threadState ?? false
352
386
  });
353
387
  const json = JSON.stringify(collection, null, 2);
354
- const outFile = opts.out ? resolve2(process.cwd(), opts.out) : opts.outDir ? resolve2(process.cwd(), opts.outDir, `${fileBase}.postman_collection.json`) : join2(dirname(file), `${fileBase}.postman_collection.json`);
355
- const collectionOutDir = dirname(outFile);
356
- await mkdir(collectionOutDir, { recursive: true });
388
+ const outFile = opts.out ? resolve2(process.cwd(), opts.out) : opts.outDir ? resolve2(process.cwd(), opts.outDir, `${fileBase}.postman_collection.json`) : join3(dirname3(file), `${fileBase}.postman_collection.json`);
389
+ const collectionOutDir = dirname3(outFile);
390
+ await mkdir2(collectionOutDir, { recursive: true });
357
391
  await writeFile(outFile, json, "utf8");
358
392
  console.log(`Wrote Postman collection \u2192 ${outFile}`);
359
393
  exported++;
@@ -388,8 +422,8 @@ async function runGenerate(projectDir) {
388
422
  }
389
423
 
390
424
  // src/commands/init.ts
391
- import { access, copyFile, mkdir as mkdir2, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
392
- import { basename as basename2, isAbsolute as isAbsolute3, join as join3, resolve as resolve3 } from "path";
425
+ import { access, copyFile, mkdir as mkdir3, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
426
+ import { basename as basename2, isAbsolute as isAbsolute3, join as join4, resolve as resolve3 } from "path";
393
427
  import { generate as generate2, loadSpec } from "@usejourney/codegen";
394
428
  async function isEmpty(dir) {
395
429
  try {
@@ -416,12 +450,12 @@ async function runInit(opts) {
416
450
  }
417
451
  await loadSpec(specSource);
418
452
  const specDestName = basename2(specSource);
419
- const specDest = join3(projectDir, specDestName);
420
- await mkdir2(projectDir, { recursive: true });
421
- await mkdir2(join3(projectDir, "generated"), { recursive: true });
422
- await mkdir2(join3(projectDir, "journeys"), { recursive: true });
423
- await mkdir2(join3(projectDir, "environments"), { recursive: true });
424
- await mkdir2(join3(projectDir, ".journey", "cache"), { recursive: true });
453
+ const specDest = join4(projectDir, specDestName);
454
+ await mkdir3(projectDir, { recursive: true });
455
+ await mkdir3(join4(projectDir, "generated"), { recursive: true });
456
+ await mkdir3(join4(projectDir, "journeys"), { recursive: true });
457
+ await mkdir3(join4(projectDir, "environments"), { recursive: true });
458
+ await mkdir3(join4(projectDir, ".journey", "cache"), { recursive: true });
425
459
  await copyFile(specSource, specDest);
426
460
  const config = {
427
461
  name: basename2(projectDir),
@@ -431,23 +465,23 @@ async function runInit(opts) {
431
465
  environmentsDir: "environments"
432
466
  };
433
467
  await writeFile2(
434
- join3(projectDir, "journey.config.json"),
468
+ join4(projectDir, "journey.config.json"),
435
469
  `${JSON.stringify(config, null, 2)}
436
470
  `,
437
471
  "utf8"
438
472
  );
439
- await writeFile2(join3(projectDir, ".gitignore"), `.journey/cache/
473
+ await writeFile2(join4(projectDir, ".gitignore"), `.journey/cache/
440
474
  node_modules/
441
475
  `, "utf8");
442
476
  await writeFile2(
443
- join3(projectDir, "package.json"),
477
+ join4(projectDir, "package.json"),
444
478
  `${JSON.stringify({ name: basename2(projectDir), private: true, type: "module" }, null, 2)}
445
479
  `,
446
480
  "utf8"
447
481
  );
448
482
  const generated = await generate2({
449
483
  specPath: specDest,
450
- outDir: join3(projectDir, "generated")
484
+ outDir: join4(projectDir, "generated")
451
485
  });
452
486
  console.log(
453
487
  `Initialized Journey project at ${projectDir} (${generated.operationCount} operations).`
@@ -461,7 +495,7 @@ node_modules/
461
495
 
462
496
  // src/commands/run.ts
463
497
  import { watch } from "fs/promises";
464
- import { dirname as dirname3, isAbsolute as isAbsolute4, join as join5, resolve as resolve4 } from "path";
498
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join5, resolve as resolve4 } from "path";
465
499
  import {
466
500
  clearActiveEnvironment as clearActiveEnvironment2,
467
501
  createConsoleLogger,
@@ -499,46 +533,41 @@ function overallOk(results) {
499
533
  return results.every((r) => r.ok);
500
534
  }
501
535
 
502
- // src/util/projectCoreLink.ts
503
- import { existsSync, readFileSync } from "fs";
504
- import { mkdir as mkdir3, symlink } from "fs/promises";
505
- import { dirname as dirname2, join as join4 } from "path";
506
- import { fileURLToPath } from "url";
507
- function locateJourneyCoreDir() {
508
- let dir = dirname2(fileURLToPath(import.meta.url));
509
- for (let depth = 0; depth < 50; depth++) {
510
- const pkgJson = join4(dir, "node_modules", "@usejourney", "core", "package.json");
511
- if (existsSync(pkgJson)) {
512
- JSON.parse(readFileSync(pkgJson, "utf8"));
513
- return dirname2(pkgJson);
536
+ // src/util/dispatcher.ts
537
+ var current;
538
+ var warnedInsecure = false;
539
+ var defaults = {};
540
+ function setDispatcherDefaults(opts) {
541
+ defaults = opts;
542
+ }
543
+ async function configureDispatcher(opts) {
544
+ const insecure = opts.insecure === true || defaults.insecure === true;
545
+ const connectTimeoutMs = defaults.connectTimeoutMs ?? opts.connectTimeoutMs;
546
+ if (!insecure && connectTimeoutMs === void 0) return void 0;
547
+ const key = `${insecure}:${connectTimeoutMs ?? "default"}`;
548
+ if (current?.key === key) return current.agent;
549
+ const { Agent, fetch: undiciFetch, setGlobalDispatcher } = await import("undici");
550
+ const agent = new Agent({
551
+ ...connectTimeoutMs !== void 0 ? { connectTimeout: connectTimeoutMs } : {},
552
+ connect: {
553
+ ...insecure ? { rejectUnauthorized: false } : {},
554
+ // `connect.timeout` is what the connector actually reads; undici spreads
555
+ // `connect` last, so the top-level `connectTimeout` alone would be
556
+ // clobbered by this object. Set both.
557
+ ...connectTimeoutMs !== void 0 ? { timeout: connectTimeoutMs } : {}
514
558
  }
515
- const parent = dirname2(dir);
516
- if (parent === dir) break;
517
- dir = parent;
559
+ });
560
+ setGlobalDispatcher(agent);
561
+ globalThis.fetch = undiciFetch;
562
+ current = { key, agent };
563
+ if (insecure && !warnedInsecure) {
564
+ warnedInsecure = true;
565
+ console.error("journey: WARNING \u2014 TLS verification disabled (--insecure)");
518
566
  }
519
- throw new Error("ensureProjectCoreLink: could not locate the CLI-bundled @usejourney/core");
520
- }
521
- var cachedCoreDir;
522
- async function ensureProjectCoreLink(projectDir) {
523
- const link = join4(projectDir, "node_modules", "@usejourney", "core");
524
- if (existsSync(link)) return;
525
- if (!cachedCoreDir) cachedCoreDir = locateJourneyCoreDir();
526
- await mkdir3(dirname2(link), { recursive: true });
527
- const type = process.platform === "win32" ? "junction" : "dir";
528
- await symlink(cachedCoreDir, link, type);
567
+ return agent;
529
568
  }
530
569
 
531
570
  // src/commands/run.ts
532
- var insecureAgent;
533
- async function enableInsecureTls() {
534
- if (!insecureAgent) {
535
- const { Agent, setGlobalDispatcher } = await import("undici");
536
- insecureAgent = new Agent({ connect: { rejectUnauthorized: false } });
537
- setGlobalDispatcher(insecureAgent);
538
- console.error("journey: WARNING \u2014 TLS verification disabled (--insecure)");
539
- }
540
- return insecureAgent;
541
- }
542
571
  async function runCommand(opts) {
543
572
  const loaded = await loadConfig4(opts.projectDir);
544
573
  const paths = resolveConfigPaths4(loaded);
@@ -554,14 +583,17 @@ async function runCommand(opts) {
554
583
  }
555
584
  await ensureProjectCoreLink(opts.projectDir);
556
585
  await importJourneyFiles(files);
557
- const ctx = {};
586
+ const ctx = { baseDir: opts.projectDir };
558
587
  const baseUrl = resolveBaseUrl(loaded.config);
559
588
  if (baseUrl) ctx.baseUrl = baseUrl;
560
589
  const logger = opts.debug ? createConsoleLogger() : loggerFromEnv();
561
590
  if (logger) ctx.logger = logger;
562
- if (opts.insecure || loaded.config.tlsRejectUnauthorized === false) {
563
- ctx.dispatcher = await enableInsecureTls();
564
- }
591
+ const connectTimeoutMs = opts.connectTimeoutMs ?? loaded.config.connectTimeoutMs;
592
+ const dispatcher = await configureDispatcher({
593
+ insecure: opts.insecure || loaded.config.tlsRejectUnauthorized === false,
594
+ ...connectTimeoutMs !== void 0 ? { connectTimeoutMs } : {}
595
+ });
596
+ if (dispatcher !== void 0) ctx.dispatcher = dispatcher;
565
597
  const subCache = createSubJourneyCache(opts.cache ?? "process", {
566
598
  diskDir: join5(opts.projectDir, ".journey", "cache", "sub-journey")
567
599
  });
@@ -597,7 +629,7 @@ async function runCommand(opts) {
597
629
  timer = setTimeout(() => void rerun(), DEBOUNCE_MS);
598
630
  };
599
631
  const dirs = /* @__PURE__ */ new Set([paths.journeysDir]);
600
- for (const f of files) dirs.add(dirname3(f));
632
+ for (const f of files) dirs.add(dirname4(f));
601
633
  const watchers = [];
602
634
  for (const dir of dirs) {
603
635
  try {
@@ -702,10 +734,10 @@ function formatArgs(args) {
702
734
  // src/server/runner.ts
703
735
  async function runJourneyFile(opts) {
704
736
  const abs = isAbsolute5(opts.file) ? opts.file : opts.file.includes("/") || opts.file.includes("\\") ? resolve5(process.cwd(), opts.file) : join6(opts.journeysDir, opts.file);
705
- let dispatcher;
706
- if (opts.loaded.config.tlsRejectUnauthorized === false) {
707
- dispatcher = await enableInsecureTls();
708
- }
737
+ const dispatcher = await configureDispatcher({
738
+ insecure: opts.loaded.config.tlsRejectUnauthorized === false,
739
+ ...opts.loaded.config.connectTimeoutMs !== void 0 ? { connectTimeoutMs: opts.loaded.config.connectTimeoutMs } : {}
740
+ });
709
741
  clearActiveEnvironment3();
710
742
  const envName = opts.env ?? opts.loaded.config.defaultEnvironment;
711
743
  if (envName) {
@@ -714,7 +746,7 @@ async function runJourneyFile(opts) {
714
746
  }
715
747
  await ensureProjectCoreLink(opts.loaded.projectDir);
716
748
  await importJourneyFiles([abs]);
717
- const ctx = {};
749
+ const ctx = { baseDir: opts.loaded.projectDir };
718
750
  const baseUrl = resolveBaseUrl2(opts.loaded.config);
719
751
  if (baseUrl) ctx.baseUrl = baseUrl;
720
752
  const logger = opts.logger ?? (opts.debug ? createConsoleLogger2() : loggerFromEnv2());
@@ -1197,13 +1229,13 @@ var PATCHABLE_CONFIG_KEYS = ["tlsRejectUnauthorized"];
1197
1229
  async function patchProjectConfig(projectDir, patch) {
1198
1230
  const configPath = join9(projectDir, "journey.config.json");
1199
1231
  const raw = await readFile2(configPath, "utf8");
1200
- const current = JSON.parse(raw);
1232
+ const current2 = JSON.parse(raw);
1201
1233
  for (const key of Object.keys(patch)) {
1202
1234
  if (!PATCHABLE_CONFIG_KEYS.includes(key)) {
1203
1235
  throw new Error(`config key "${key}" is not patchable via the API`);
1204
1236
  }
1205
1237
  }
1206
- const merged = { ...current };
1238
+ const merged = { ...current2 };
1207
1239
  for (const key of PATCHABLE_CONFIG_KEYS) {
1208
1240
  if (Object.prototype.hasOwnProperty.call(patch, key)) {
1209
1241
  merged[key] = patch[key];
@@ -1685,7 +1717,11 @@ async function startServer(opts) {
1685
1717
 
1686
1718
  // src/commands/serve.ts
1687
1719
  async function runServe(opts) {
1688
- if (opts.insecure) await enableInsecureTls();
1720
+ setDispatcherDefaults({
1721
+ ...opts.insecure !== void 0 ? { insecure: opts.insecure } : {},
1722
+ ...opts.connectTimeoutMs !== void 0 ? { connectTimeoutMs: opts.connectTimeoutMs } : {}
1723
+ });
1724
+ await configureDispatcher({});
1689
1725
  const srv = await startServer({
1690
1726
  projectDir: opts.projectDir,
1691
1727
  ...opts.host !== void 0 ? { host: opts.host } : {},
@@ -1754,6 +1790,10 @@ function buildProgram() {
1754
1790
  "--timeout <ms>",
1755
1791
  "Request timeout in ms; 0 disables (default 60000)",
1756
1792
  (v) => parseInt(v, 10)
1793
+ ).option(
1794
+ "--connect-timeout <ms>",
1795
+ "DNS + TCP + TLS connect timeout in ms; 0 disables (default 10000)",
1796
+ (v) => parseInt(v, 10)
1757
1797
  ).description("Run one or more journeys (or --all)").action(
1758
1798
  (files, options) => handle(
1759
1799
  () => runCommand({
@@ -1766,7 +1806,8 @@ function buildProgram() {
1766
1806
  ...options.insecure !== void 0 ? { insecure: options.insecure } : {},
1767
1807
  ...options.cache !== void 0 ? { cache: options.cache } : {},
1768
1808
  ...options.cacheTtl !== void 0 ? { cacheTtlMs: options.cacheTtl } : {},
1769
- ...options.timeout !== void 0 ? { timeoutMs: options.timeout } : {}
1809
+ ...options.timeout !== void 0 ? { timeoutMs: options.timeout } : {},
1810
+ ...options.connectTimeout !== void 0 ? { connectTimeoutMs: options.connectTimeout } : {}
1770
1811
  })
1771
1812
  )
1772
1813
  );
@@ -1779,12 +1820,20 @@ function buildProgram() {
1779
1820
  "Only export journeys with this tag (repeatable, AND across repeats)",
1780
1821
  (v, prev = []) => [...prev, v],
1781
1822
  []
1823
+ ).option(
1824
+ "--metrics-prefix <prefix>",
1825
+ "Emit custom metrics per step (<prefix>_<step>_duration Trend, <prefix>_<step>_tps Counter) and per iteration (<prefix>_flow_duration / <prefix>_flow_tps)"
1826
+ ).option(
1827
+ "--halt-on-failure",
1828
+ "Stop an iteration's remaining steps when an assert fails (mirrors journey run)"
1782
1829
  ).description("Export one or more journeys as k6 scripts (path may be a file or directory)").action(
1783
1830
  (path, options) => handle(
1784
1831
  () => runExportK6({
1785
1832
  path,
1786
1833
  ...options.out !== void 0 ? { out: options.out } : {},
1787
1834
  ...options.outDir !== void 0 ? { outDir: options.outDir } : {},
1835
+ ...options.metricsPrefix !== void 0 ? { metricsPrefix: options.metricsPrefix } : {},
1836
+ ...options.haltOnFailure ? { haltOnFailure: true } : {},
1788
1837
  tags: options.tag
1789
1838
  })
1790
1839
  )
@@ -1834,6 +1883,10 @@ function buildProgram() {
1834
1883
  "--timeout <ms>",
1835
1884
  "Request timeout in ms for journey runs triggered via the API; 0 disables (default 60000)",
1836
1885
  (v) => parseInt(v, 10)
1886
+ ).option(
1887
+ "--connect-timeout <ms>",
1888
+ "DNS + TCP + TLS connect timeout in ms; 0 disables (default 10000)",
1889
+ (v) => parseInt(v, 10)
1837
1890
  ).description("Run the GUI backend API for the current project").action(
1838
1891
  (options) => {
1839
1892
  const projectDir = options.project ? resolvePath(process.cwd(), options.project) : process.cwd();
@@ -1846,7 +1899,8 @@ function buildProgram() {
1846
1899
  ...options.insecure !== void 0 ? { insecure: options.insecure } : {},
1847
1900
  ...options.cache !== void 0 ? { cache: options.cache } : {},
1848
1901
  ...options.cacheTtl !== void 0 ? { cacheTtlMs: options.cacheTtl } : {},
1849
- ...options.timeout !== void 0 ? { timeoutMs: options.timeout } : {}
1902
+ ...options.timeout !== void 0 ? { timeoutMs: options.timeout } : {},
1903
+ ...options.connectTimeout !== void 0 ? { connectTimeoutMs: options.connectTimeout } : {}
1850
1904
  })
1851
1905
  );
1852
1906
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usejourney/cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,10 +23,10 @@
23
23
  "commander": "^12.1.0",
24
24
  "tsx": "^4.21.0",
25
25
  "undici": "^8.3.0",
26
- "@usejourney/codegen": "^0.1.4",
27
- "@usejourney/core": "0.1.4",
28
- "@usejourney/k6-adapter": "^0.1.4",
29
- "@usejourney/postman-adapter": "^0.1.4"
26
+ "@usejourney/codegen": "^0.2.0",
27
+ "@usejourney/core": "0.2.0",
28
+ "@usejourney/k6-adapter": "^0.2.0",
29
+ "@usejourney/postman-adapter": "^0.2.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/newman": "^5.3.6",