@jterrazz/test 3.3.1 → 3.4.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/dist/index.d.cts CHANGED
@@ -118,6 +118,15 @@ interface CommandResult {
118
118
  stdout: string;
119
119
  stderr: string;
120
120
  }
121
+ /**
122
+ * Options for spawning a long-running process.
123
+ */
124
+ interface SpawnOptions {
125
+ /** Resolve when stdout/stderr contains this string. */
126
+ waitFor: string;
127
+ /** Kill the process after this many milliseconds. */
128
+ timeout: number;
129
+ }
121
130
  /**
122
131
  * Abstract CLI interface for specification runners.
123
132
  * Implement this to plug in your command execution strategy.
@@ -125,6 +134,8 @@ interface CommandResult {
125
134
  interface CommandPort {
126
135
  /** Execute a CLI command with the given arguments in the given working directory. */
127
136
  exec(args: string, cwd: string): Promise<CommandResult>;
137
+ /** Spawn a long-running process and wait for a pattern or timeout. */
138
+ spawn(args: string, cwd: string, options: SpawnOptions): Promise<CommandResult>;
128
139
  }
129
140
  //#endregion
130
141
  //#region src/specification/ports/server.port.d.ts
@@ -200,6 +211,7 @@ declare class SpecificationBuilder {
200
211
  private projectName;
201
212
  private request;
202
213
  private seeds;
214
+ private spawnConfig;
203
215
  private testDir;
204
216
  constructor(config: SpecificationConfig, testDir: string, label: string);
205
217
  seed(file: string, options?: {
@@ -212,7 +224,8 @@ declare class SpecificationBuilder {
212
224
  post(path: string, bodyFile?: string): this;
213
225
  put(path: string, bodyFile?: string): this;
214
226
  delete(path: string): this;
215
- exec(args: string): this;
227
+ exec(args: string | string[]): this;
228
+ spawn(args: string, options: SpawnOptions): this;
216
229
  run(): Promise<SpecificationResult>;
217
230
  private prepareWorkDir;
218
231
  private runHttpAction;
@@ -289,13 +302,14 @@ declare function redis(options?: RedisOptions): RedisHandle;
289
302
  //#endregion
290
303
  //#region src/specification/adapters/exec.adapter.d.ts
291
304
  /**
292
- * Executes CLI commands via execSync.
305
+ * Executes CLI commands via execSync (blocking) or spawn (long-running).
293
306
  * Used by cli() for local command execution.
294
307
  */
295
308
  declare class ExecAdapter implements CommandPort {
296
309
  private command;
297
310
  constructor(command: string);
298
311
  exec(args: string, cwd: string): Promise<CommandResult>;
312
+ spawn(args: string, cwd: string, options: SpawnOptions): Promise<CommandResult>;
299
313
  }
300
314
  //#endregion
301
315
  //#region src/specification/adapters/fetch.adapter.d.ts
package/dist/index.d.ts CHANGED
@@ -118,6 +118,15 @@ interface CommandResult {
118
118
  stdout: string;
119
119
  stderr: string;
120
120
  }
121
+ /**
122
+ * Options for spawning a long-running process.
123
+ */
124
+ interface SpawnOptions {
125
+ /** Resolve when stdout/stderr contains this string. */
126
+ waitFor: string;
127
+ /** Kill the process after this many milliseconds. */
128
+ timeout: number;
129
+ }
121
130
  /**
122
131
  * Abstract CLI interface for specification runners.
123
132
  * Implement this to plug in your command execution strategy.
@@ -125,6 +134,8 @@ interface CommandResult {
125
134
  interface CommandPort {
126
135
  /** Execute a CLI command with the given arguments in the given working directory. */
127
136
  exec(args: string, cwd: string): Promise<CommandResult>;
137
+ /** Spawn a long-running process and wait for a pattern or timeout. */
138
+ spawn(args: string, cwd: string, options: SpawnOptions): Promise<CommandResult>;
128
139
  }
129
140
  //#endregion
130
141
  //#region src/specification/ports/server.port.d.ts
@@ -200,6 +211,7 @@ declare class SpecificationBuilder {
200
211
  private projectName;
201
212
  private request;
202
213
  private seeds;
214
+ private spawnConfig;
203
215
  private testDir;
204
216
  constructor(config: SpecificationConfig, testDir: string, label: string);
205
217
  seed(file: string, options?: {
@@ -212,7 +224,8 @@ declare class SpecificationBuilder {
212
224
  post(path: string, bodyFile?: string): this;
213
225
  put(path: string, bodyFile?: string): this;
214
226
  delete(path: string): this;
215
- exec(args: string): this;
227
+ exec(args: string | string[]): this;
228
+ spawn(args: string, options: SpawnOptions): this;
216
229
  run(): Promise<SpecificationResult>;
217
230
  private prepareWorkDir;
218
231
  private runHttpAction;
@@ -289,13 +302,14 @@ declare function redis(options?: RedisOptions): RedisHandle;
289
302
  //#endregion
290
303
  //#region src/specification/adapters/exec.adapter.d.ts
291
304
  /**
292
- * Executes CLI commands via execSync.
305
+ * Executes CLI commands via execSync (blocking) or spawn (long-running).
293
306
  * Used by cli() for local command execution.
294
307
  */
295
308
  declare class ExecAdapter implements CommandPort {
296
309
  private command;
297
310
  constructor(command: string);
298
311
  exec(args: string, cwd: string): Promise<CommandResult>;
312
+ spawn(args: string, cwd: string, options: SpawnOptions): Promise<CommandResult>;
299
313
  }
300
314
  //#endregion
301
315
  //#region src/specification/adapters/fetch.adapter.d.ts
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import MockDatePackage from "mockdate";
4
4
  import { mockDeep } from "vitest-mock-extended";
5
5
  import { cpSync, existsSync, mkdtempSync, readFileSync } from "node:fs";
6
6
  import { dirname, isAbsolute, resolve } from "node:path";
7
- import { execSync } from "node:child_process";
7
+ import { execSync, spawn } from "node:child_process";
8
8
  import { tmpdir } from "node:os";
9
9
  //#region src/mocking/mock-of-date.ts
10
10
  const mockOfDate = MockDatePackage;
@@ -4899,7 +4899,7 @@ var Orchestrator = class {
4899
4899
  //#endregion
4900
4900
  //#region src/specification/adapters/exec.adapter.ts
4901
4901
  /**
4902
- * Executes CLI commands via execSync.
4902
+ * Executes CLI commands via execSync (blocking) or spawn (long-running).
4903
4903
  * Used by cli() for local command execution.
4904
4904
  */
4905
4905
  var ExecAdapter = class {
@@ -4935,6 +4935,55 @@ var ExecAdapter = class {
4935
4935
  };
4936
4936
  }
4937
4937
  }
4938
+ async spawn(args, cwd, options) {
4939
+ const env = {
4940
+ ...process.env,
4941
+ INIT_CWD: void 0
4942
+ };
4943
+ return new Promise((resolve) => {
4944
+ let stdout = "";
4945
+ let stderr = "";
4946
+ let resolved = false;
4947
+ const child = spawn(this.command, args.split(/\s+/).filter(Boolean), {
4948
+ cwd,
4949
+ env,
4950
+ stdio: [
4951
+ "pipe",
4952
+ "pipe",
4953
+ "pipe"
4954
+ ]
4955
+ });
4956
+ const finish = (exitCode) => {
4957
+ if (resolved) return;
4958
+ resolved = true;
4959
+ child.kill("SIGTERM");
4960
+ resolve({
4961
+ exitCode,
4962
+ stdout,
4963
+ stderr
4964
+ });
4965
+ };
4966
+ let patternMatched = false;
4967
+ const checkPattern = () => {
4968
+ if (!patternMatched && (stdout.includes(options.waitFor) || stderr.includes(options.waitFor))) {
4969
+ patternMatched = true;
4970
+ finish(0);
4971
+ }
4972
+ };
4973
+ child.stdout?.on("data", (data) => {
4974
+ stdout += data.toString();
4975
+ checkPattern();
4976
+ });
4977
+ child.stderr?.on("data", (data) => {
4978
+ stderr += data.toString();
4979
+ checkPattern();
4980
+ });
4981
+ child.on("exit", (code) => {
4982
+ if (!patternMatched) finish(code === 0 ? 1 : code ?? 1);
4983
+ });
4984
+ setTimeout(() => finish(124), options.timeout);
4985
+ });
4986
+ }
4938
4987
  };
4939
4988
  //#endregion
4940
4989
  //#region src/specification/adapters/fetch.adapter.ts
@@ -5093,6 +5142,7 @@ var SpecificationBuilder = class {
5093
5142
  projectName = null;
5094
5143
  request = null;
5095
5144
  seeds = [];
5145
+ spawnConfig = null;
5096
5146
  testDir;
5097
5147
  constructor(config, testDir, label) {
5098
5148
  this.config = config;
@@ -5152,11 +5202,18 @@ var SpecificationBuilder = class {
5152
5202
  this.commandArgs = args;
5153
5203
  return this;
5154
5204
  }
5205
+ spawn(args, options) {
5206
+ this.spawnConfig = {
5207
+ args,
5208
+ options
5209
+ };
5210
+ return this;
5211
+ }
5155
5212
  async run() {
5156
5213
  const hasHttpAction = this.request !== null;
5157
- const hasCliAction = this.commandArgs !== null;
5214
+ const hasCliAction = this.commandArgs !== null || this.spawnConfig !== null;
5158
5215
  if (!hasHttpAction && !hasCliAction) throw new Error(`Specification "${this.label}": no action defined. Call .get(), .post(), .exec(), etc. before .run()`);
5159
- if (hasHttpAction && hasCliAction) throw new Error(`Specification "${this.label}": cannot mix HTTP (.get/.post) and CLI (.exec) actions`);
5216
+ if (hasHttpAction && hasCliAction) throw new Error(`Specification "${this.label}": cannot mix HTTP (.get/.post) and CLI (.exec/.spawn) actions`);
5160
5217
  let workDir = null;
5161
5218
  if (hasCliAction) workDir = this.prepareWorkDir();
5162
5219
  if (this.config.databases) for (const db of this.config.databases.values()) await db.reset();
@@ -5203,8 +5260,21 @@ var SpecificationBuilder = class {
5203
5260
  }
5204
5261
  async runCliAction(workDir) {
5205
5262
  if (!this.config.command) throw new Error("CLI actions require a command adapter (use cli())");
5263
+ let commandResult;
5264
+ if (this.spawnConfig) commandResult = await this.config.command.spawn(this.spawnConfig.args, workDir, this.spawnConfig.options);
5265
+ else if (Array.isArray(this.commandArgs)) {
5266
+ commandResult = {
5267
+ exitCode: 0,
5268
+ stdout: "",
5269
+ stderr: ""
5270
+ };
5271
+ for (const args of this.commandArgs) {
5272
+ commandResult = await this.config.command.exec(args, workDir);
5273
+ if (commandResult.exitCode !== 0) break;
5274
+ }
5275
+ } else commandResult = await this.config.command.exec(this.commandArgs, workDir);
5206
5276
  return new SpecificationResult({
5207
- commandResult: await this.config.command.exec(this.commandArgs, workDir),
5277
+ commandResult,
5208
5278
  config: this.config,
5209
5279
  testDir: this.testDir,
5210
5280
  workDir