@git.zone/tstest 2.6.2 → 2.8.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.
@@ -29,7 +29,7 @@ export interface ParserConfig {
29
29
 
30
30
  const KNOWN_RUNTIMES: Set<string> = new Set(['node', 'chromium', 'deno', 'bun']);
31
31
  const KNOWN_MODIFIERS: Set<string> = new Set(['nonci']);
32
- const VALID_EXTENSIONS: Set<string> = new Set(['ts', 'tsx', 'mts', 'cts']);
32
+ const VALID_EXTENSIONS: Set<string> = new Set(['ts', 'tsx', 'mts', 'cts', 'sh']);
33
33
  const ALL_RUNTIMES: Runtime[] = ['node', 'chromium', 'deno', 'bun'];
34
34
 
35
35
  // Legacy mappings for backwards compatibility
@@ -228,3 +228,81 @@ export function getLegacyMigrationTarget(fileName: string): string | null {
228
228
 
229
229
  return parts.join('.');
230
230
  }
231
+
232
+ /**
233
+ * Docker test file information
234
+ */
235
+ export interface DockerTestFileInfo {
236
+ baseName: string;
237
+ variant: string;
238
+ isDockerTest: true;
239
+ original: string;
240
+ }
241
+
242
+ /**
243
+ * Check if a filename matches the Docker test pattern: *.{variant}.docker.sh
244
+ * Examples: test.latest.docker.sh, test.integration.npmci.docker.sh
245
+ */
246
+ export function isDockerTestFile(fileName: string): boolean {
247
+ // Must end with .docker.sh
248
+ if (!fileName.endsWith('.docker.sh')) {
249
+ return false;
250
+ }
251
+
252
+ // Extract filename from path if needed
253
+ const name = fileName.split('/').pop() || fileName;
254
+
255
+ // Must have at least 3 parts: [baseName, variant, docker, sh]
256
+ const parts = name.split('.');
257
+ return parts.length >= 4 && parts[parts.length - 2] === 'docker' && parts[parts.length - 1] === 'sh';
258
+ }
259
+
260
+ /**
261
+ * Parse a Docker test filename to extract variant and base name
262
+ * Pattern: test.{baseName}.{variant}.docker.sh
263
+ * Examples:
264
+ * - test.latest.docker.sh -> { baseName: 'test', variant: 'latest' }
265
+ * - test.integration.npmci.docker.sh -> { baseName: 'test.integration', variant: 'npmci' }
266
+ */
267
+ export function parseDockerTestFilename(filePath: string): DockerTestFileInfo {
268
+ // Extract just the filename from the path
269
+ const fileName = filePath.split('/').pop() || filePath;
270
+ const original = fileName;
271
+
272
+ if (!isDockerTestFile(fileName)) {
273
+ throw new Error(`Not a valid Docker test file: "${fileName}". Expected pattern: *.{variant}.docker.sh`);
274
+ }
275
+
276
+ // Remove .docker.sh suffix
277
+ const withoutSuffix = fileName.slice(0, -10); // Remove '.docker.sh'
278
+ const tokens = withoutSuffix.split('.');
279
+
280
+ if (tokens.length === 0) {
281
+ throw new Error(`Invalid Docker test file: empty basename in "${fileName}"`);
282
+ }
283
+
284
+ // Last token before .docker.sh is the variant
285
+ const variant = tokens[tokens.length - 1];
286
+
287
+ // Everything else is the base name
288
+ const baseName = tokens.slice(0, -1).join('.');
289
+
290
+ return {
291
+ baseName: baseName || 'test',
292
+ variant,
293
+ isDockerTest: true,
294
+ original,
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Map a Docker variant to its corresponding Dockerfile path
300
+ * "latest" -> "Dockerfile"
301
+ * Other variants -> "Dockerfile_{variant}"
302
+ */
303
+ export function mapVariantToDockerfile(variant: string, baseDir: string): string {
304
+ if (variant === 'latest') {
305
+ return `${baseDir}/Dockerfile`;
306
+ }
307
+ return `${baseDir}/Dockerfile_${variant}`;
308
+ }
@@ -74,12 +74,20 @@ export class TestDirectory {
74
74
  case TestExecutionMode.DIRECTORY:
75
75
  // Directory mode - now recursive with ** pattern
76
76
  const dirPath = plugins.path.join(this.cwd, this.testPath);
77
- const testPattern = '**/test*.ts';
78
-
79
- const testFiles = await plugins.smartfile.fs.listFileTree(dirPath, testPattern);
80
-
77
+
78
+ // Search for both TypeScript test files and Docker shell test files
79
+ const tsPattern = '**/test*.ts';
80
+ const dockerPattern = '**/*.docker.sh';
81
+
82
+ const [tsFiles, dockerFiles] = await Promise.all([
83
+ plugins.smartfile.fs.listFileTree(dirPath, tsPattern),
84
+ plugins.smartfile.fs.listFileTree(dirPath, dockerPattern),
85
+ ]);
86
+
87
+ const allTestFiles = [...tsFiles, ...dockerFiles];
88
+
81
89
  this.testfileArray = await Promise.all(
82
- testFiles.map(async (filePath) => {
90
+ allTestFiles.map(async (filePath) => {
83
91
  const absolutePath = plugins.path.isAbsolute(filePath)
84
92
  ? filePath
85
93
  : plugins.path.join(dirPath, filePath);
@@ -11,12 +11,13 @@ import { TsTestLogger } from './tstest.logging.js';
11
11
  import type { LogOptions } from './tstest.logging.js';
12
12
 
13
13
  // Runtime adapters
14
- import { parseTestFilename } from './tstest.classes.runtime.parser.js';
14
+ import { parseTestFilename, isDockerTestFile, parseDockerTestFilename } from './tstest.classes.runtime.parser.js';
15
15
  import { RuntimeAdapterRegistry } from './tstest.classes.runtime.adapter.js';
16
16
  import { NodeRuntimeAdapter } from './tstest.classes.runtime.node.js';
17
17
  import { ChromiumRuntimeAdapter } from './tstest.classes.runtime.chromium.js';
18
18
  import { DenoRuntimeAdapter } from './tstest.classes.runtime.deno.js';
19
19
  import { BunRuntimeAdapter } from './tstest.classes.runtime.bun.js';
20
+ import { DockerRuntimeAdapter } from './tstest.classes.runtime.docker.js';
20
21
 
21
22
  export class TsTest {
22
23
  public testDir: TestDirectory;
@@ -37,6 +38,7 @@ export class TsTest {
37
38
  public tsbundleInstance = new plugins.tsbundle.TsBundle();
38
39
 
39
40
  public runtimeRegistry = new RuntimeAdapterRegistry();
41
+ public dockerAdapter: DockerRuntimeAdapter | null = null;
40
42
 
41
43
  constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = [], startFromFile: number | null = null, stopAtFile: number | null = null, timeoutSeconds: number | null = null) {
42
44
  this.executionMode = executionModeArg;
@@ -60,6 +62,14 @@ export class TsTest {
60
62
  this.runtimeRegistry.register(
61
63
  new BunRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags)
62
64
  );
65
+
66
+ // Initialize Docker adapter
67
+ this.dockerAdapter = new DockerRuntimeAdapter(
68
+ this.logger,
69
+ this.smartshellInstance,
70
+ this.timeoutSeconds,
71
+ cwdArg
72
+ );
63
73
  }
64
74
 
65
75
  /**
@@ -211,8 +221,14 @@ export class TsTest {
211
221
  }
212
222
 
213
223
  private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) {
214
- // Parse the filename to determine runtimes and modifiers
215
224
  const fileName = plugins.path.basename(fileNameArg);
225
+
226
+ // Check if this is a Docker test file
227
+ if (isDockerTestFile(fileName)) {
228
+ return await this.runDockerTest(fileNameArg, fileIndex, totalFiles, tapCombinator);
229
+ }
230
+
231
+ // Parse the filename to determine runtimes and modifiers (for TypeScript tests)
216
232
  const parsed = parseTestFilename(fileName, { strictUnknownRuntime: false });
217
233
 
218
234
  // Check for nonci modifier in CI environment
@@ -258,6 +274,28 @@ export class TsTest {
258
274
  }
259
275
  }
260
276
 
277
+ /**
278
+ * Execute a Docker test file
279
+ */
280
+ private async runDockerTest(
281
+ fileNameArg: string,
282
+ fileIndex: number,
283
+ totalFiles: number,
284
+ tapCombinator: TapCombinator
285
+ ): Promise<void> {
286
+ if (!this.dockerAdapter) {
287
+ this.logger.tapOutput(cs('❌ Docker adapter not initialized', 'red'));
288
+ return;
289
+ }
290
+
291
+ try {
292
+ const tapParser = await this.dockerAdapter.run(fileNameArg, fileIndex, totalFiles);
293
+ tapCombinator.addTapParser(tapParser);
294
+ } catch (error) {
295
+ this.logger.tapOutput(cs(`❌ Docker test failed: ${error.message}`, 'red'));
296
+ }
297
+ }
298
+
261
299
  public async runInNode(fileNameArg: string, index: number, total: number): Promise<TapParser> {
262
300
  this.logger.testFileStart(fileNameArg, 'node.js', index, total);
263
301
  const tapParser = new TapParser(fileNameArg + ':node', this.logger);