Skip to content

Commit c751477

Browse files
fix including media files in zip file
1 parent 229bf30 commit c751477

5 files changed

Lines changed: 122 additions & 59 deletions

File tree

src/cli.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Command } from 'commander';
2-
import logger from './logger';
2+
import logger, { enableDebugLogging } from './logger';
33
import Auth from './auth';
44
import Espresso from './providers/espresso';
55
import EspressoOptions, {
@@ -352,6 +352,7 @@ const maestroCommand = program
352352
// Authentication
353353
.option('--api-key <key>', 'TestingBot API key.')
354354
.option('--api-secret <secret>', 'TestingBot API secret.')
355+
.option('--debug', 'Enable debug logging of API responses.')
355356
.action(async (appFileArg, flowsArgs, args) => {
356357
try {
357358
let app: string;
@@ -420,6 +421,7 @@ const maestroCommand = program
420421
artifactsOutputDir: args.artifactsOutputDir,
421422
ignoreChecksumCheck: args.ignoreChecksumCheck,
422423
shardSplit: args.shardSplit,
424+
debug: args.debug,
423425
metadata,
424426
});
425427
const credentials = await Auth.getCredentials({
@@ -435,6 +437,9 @@ const maestroCommand = program
435437
' 4. Create ~/.testingbot file with content: key:secret',
436438
);
437439
}
440+
if (args.debug) {
441+
enableDebugLogging();
442+
}
438443
const maestro = new Maestro(credentials, options);
439444
const result = await maestro.run();
440445
if (!result.success) {

src/logger.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ const logger = tracer.colorConsole({
1414
],
1515
});
1616

17+
export function enableDebugLogging(): void {
18+
tracer.setLevel('debug');
19+
}
20+
1721
export default logger;

src/models/maestro_options.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export default class MaestroOptions {
6666
private _artifactsOutputDir?: string;
6767
private _ignoreChecksumCheck: boolean;
6868
private _shardSplit?: number;
69+
private _debug: boolean;
6970
// Metadata
7071
private _metadata?: RunMetadata;
7172

@@ -95,6 +96,7 @@ export default class MaestroOptions {
9596
artifactsOutputDir?: string;
9697
ignoreChecksumCheck?: boolean;
9798
shardSplit?: number;
99+
debug?: boolean;
98100
metadata?: RunMetadata;
99101
},
100102
) {
@@ -123,6 +125,7 @@ export default class MaestroOptions {
123125
this._artifactsOutputDir = options?.artifactsOutputDir;
124126
this._ignoreChecksumCheck = options?.ignoreChecksumCheck ?? false;
125127
this._shardSplit = options?.shardSplit;
128+
this._debug = options?.debug ?? false;
126129
this._metadata = options?.metadata;
127130
}
128131

@@ -222,6 +225,10 @@ export default class MaestroOptions {
222225
return this._shardSplit;
223226
}
224227

228+
public get debug(): boolean {
229+
return this._debug;
230+
}
231+
225232
public get metadata(): RunMetadata | undefined {
226233
return this._metadata;
227234
}

src/providers/maestro.ts

Lines changed: 81 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,26 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
454454
// If we have a single directory, use it as base; otherwise use common ancestor or flatten
455455
const baseDir = baseDirs.length === 1 ? baseDirs[0] : undefined;
456456

457+
// Discover dependencies (addMedia, runScript, runFlow, etc.) for all flow files
458+
// This ensures referenced files are included even when individual YAML files are passed
459+
const allFilesSet = new Set(allFlowFiles.map((f) => path.resolve(f)));
460+
for (const flowFile of [...allFlowFiles]) {
461+
const ext = path.extname(flowFile).toLowerCase();
462+
if (ext === '.yaml' || ext === '.yml') {
463+
const deps = await this.discoverDependencies(
464+
flowFile,
465+
baseDir || path.dirname(flowFile),
466+
);
467+
for (const dep of deps) {
468+
const resolved = path.resolve(dep);
469+
if (!allFilesSet.has(resolved)) {
470+
allFilesSet.add(resolved);
471+
allFlowFiles.push(dep);
472+
}
473+
}
474+
}
475+
}
476+
457477
if (!this.options.quiet) {
458478
this.logIncludedFiles(allFlowFiles, baseDir);
459479

@@ -918,19 +938,12 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
918938
if (typeof value === 'string') {
919939
if (this.looksLikePath(value)) {
920940
const resolvedPath = path.resolve(path.dirname(flowFile), value);
921-
// Check if the file is in included files OR exists on disk
922941
if (!includedFiles.has(resolvedPath)) {
923-
try {
924-
await fs.promises.access(resolvedPath);
925-
// File exists on disk but won't be included - also warn
926-
} catch {
927-
// File doesn't exist
928-
missingReferences.push({
929-
flowFile,
930-
referencedFile: value,
931-
resolvedPath,
932-
});
933-
}
942+
missingReferences.push({
943+
flowFile,
944+
referencedFile: value,
945+
resolvedPath,
946+
});
934947
}
935948
}
936949
} else if (Array.isArray(value)) {
@@ -958,15 +971,11 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
958971
if (typeof scriptFile === 'string') {
959972
const resolved = path.resolve(path.dirname(flowFile), scriptFile);
960973
if (!includedFiles.has(resolved)) {
961-
try {
962-
await fs.promises.access(resolved);
963-
} catch {
964-
missingReferences.push({
965-
flowFile,
966-
referencedFile: scriptFile,
967-
resolvedPath: resolved,
968-
});
969-
}
974+
missingReferences.push({
975+
flowFile,
976+
referencedFile: scriptFile,
977+
resolvedPath: resolved,
978+
});
970979
}
971980
}
972981
// Don't recurse into runScript - it only has file, env, when (no nested file refs)
@@ -983,15 +992,11 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
983992
if (typeof flowRef === 'string') {
984993
const resolved = path.resolve(path.dirname(flowFile), flowRef);
985994
if (!includedFiles.has(resolved)) {
986-
try {
987-
await fs.promises.access(resolved);
988-
} catch {
989-
missingReferences.push({
990-
flowFile,
991-
referencedFile: flowRef,
992-
resolvedPath: resolved,
993-
});
994-
}
995+
missingReferences.push({
996+
flowFile,
997+
referencedFile: flowRef,
998+
resolvedPath: resolved,
999+
});
9951000
}
9961001
}
9971002
// Only recurse into 'commands' if present (for inline commands)
@@ -1021,15 +1026,11 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
10211026
if (typeof mediaFile === 'string') {
10221027
const resolved = path.resolve(path.dirname(flowFile), mediaFile);
10231028
if (!includedFiles.has(resolved)) {
1024-
try {
1025-
await fs.promises.access(resolved);
1026-
} catch {
1027-
missingReferences.push({
1028-
flowFile,
1029-
referencedFile: mediaFile,
1030-
resolvedPath: resolved,
1031-
});
1032-
}
1029+
missingReferences.push({
1030+
flowFile,
1031+
referencedFile: mediaFile,
1032+
resolvedPath: resolved,
1033+
});
10331034
}
10341035
}
10351036
}
@@ -1040,15 +1041,11 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
10401041
handledKeys.add('file');
10411042
const resolved = path.resolve(path.dirname(flowFile), obj.file);
10421043
if (!includedFiles.has(resolved)) {
1043-
try {
1044-
await fs.promises.access(resolved);
1045-
} catch {
1046-
missingReferences.push({
1047-
flowFile,
1048-
referencedFile: obj.file,
1049-
resolvedPath: resolved,
1050-
});
1051-
}
1044+
missingReferences.push({
1045+
flowFile,
1046+
referencedFile: obj.file,
1047+
resolvedPath: resolved,
1048+
});
10521049
}
10531050
}
10541051

@@ -1113,8 +1110,9 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
11131110

11141111
private logIncludedFiles(files: string[], baseDir?: string): void {
11151112
// Get relative paths for display
1113+
const effectiveBase = baseDir || this.computeCommonDirectory(files);
11161114
const relativePaths = files
1117-
.map((f) => (baseDir ? path.relative(baseDir, f) : path.basename(f)))
1115+
.map((f) => path.relative(effectiveBase, f))
11181116
.sort();
11191117

11201118
// Group by file type
@@ -1179,21 +1177,43 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
11791177

11801178
archive.pipe(output);
11811179

1180+
// Compute effective base directory for archive paths
1181+
const effectiveBase = baseDir || this.computeCommonDirectory(files);
1182+
11821183
for (const file of files) {
1183-
// Determine the name in the archive
1184-
let archiveName: string;
1185-
if (baseDir) {
1186-
archiveName = path.relative(baseDir, file);
1187-
} else {
1188-
archiveName = path.basename(file);
1189-
}
1184+
const archiveName = path.relative(effectiveBase, file);
11901185
archive.file(file, { name: archiveName });
11911186
}
11921187

11931188
archive.finalize();
11941189
});
11951190
}
11961191

1192+
/**
1193+
* Compute the common parent directory of all files
1194+
*/
1195+
private computeCommonDirectory(files: string[]): string {
1196+
if (files.length === 0) return process.cwd();
1197+
if (files.length === 1) return path.dirname(files[0]);
1198+
1199+
const dirs = files.map((f) => path.dirname(path.resolve(f)));
1200+
const parts = dirs[0].split(path.sep);
1201+
let commonLength = parts.length;
1202+
1203+
for (let i = 1; i < dirs.length; i++) {
1204+
const dirParts = dirs[i].split(path.sep);
1205+
commonLength = Math.min(commonLength, dirParts.length);
1206+
for (let j = 0; j < commonLength; j++) {
1207+
if (parts[j] !== dirParts[j]) {
1208+
commonLength = j;
1209+
break;
1210+
}
1211+
}
1212+
}
1213+
1214+
return parts.slice(0, commonLength).join(path.sep) || path.sep;
1215+
}
1216+
11971217
private async runTests() {
11981218
try {
11991219
const capabilities = this.options.getCapabilities(this.detectedPlatform);
@@ -1272,6 +1292,11 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
12721292
// Check for version update notification
12731293
const latestVersion = response.headers?.['x-testingbotctl-version'];
12741294
utils.checkForUpdate(latestVersion);
1295+
1296+
if (this.options.debug) {
1297+
logger.debug(`API response: ${JSON.stringify(response.data, null, 2)}`);
1298+
}
1299+
12751300
return response.data;
12761301
});
12771302
} catch (error) {

tests/providers/maestro.test.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4229,7 +4229,7 @@ flows:
42294229
expect(missingRefs).toHaveLength(0);
42304230
});
42314231

4232-
it('should not report references that exist on disk', async () => {
4232+
it('should report references that exist on disk but are not in included files', async () => {
42334233
const flowContent = `
42344234
- runScript: ../config/setup.js
42354235
- tapOn: "Login"
@@ -4247,7 +4247,29 @@ flows:
42474247
projectDir,
42484248
);
42494249

4250-
// File exists on disk, so no missing reference reported
4250+
// File is not in included files, so it should be reported
4251+
expect(missingRefs).toHaveLength(1);
4252+
expect(missingRefs[0].referencedFile).toBe('../config/setup.js');
4253+
});
4254+
4255+
it('should not report references that are in included files', async () => {
4256+
const flowContent = `
4257+
- runScript: ../config/setup.js
4258+
- tapOn: "Login"
4259+
`;
4260+
const projectDir = path.resolve(path.sep, 'project');
4261+
const flowPath = path.join(projectDir, 'flows', 'login.yaml');
4262+
const scriptPath = path.resolve(projectDir, 'config', 'setup.js');
4263+
4264+
fs.promises.readFile = jest.fn().mockResolvedValue(flowContent);
4265+
4266+
const missingRefs = await maestro['findMissingReferences'](
4267+
[flowPath],
4268+
[flowPath, scriptPath], // Script is in included files
4269+
projectDir,
4270+
);
4271+
4272+
// File is in included files, so no missing reference reported
42514273
expect(missingRefs).toHaveLength(0);
42524274
});
42534275

0 commit comments

Comments
 (0)